Completed
Branch dev (e59588)
by
unknown
29:01 queued 21:36
created
admin_pages/messages/Messages_Admin_Page.core.php 2 patches
Indentation   +4645 added lines, -4645 removed lines patch added patch discarded remove patch
@@ -17,2688 +17,2688 @@  discard block
 block discarded – undo
17 17
  */
18 18
 class Messages_Admin_Page extends EE_Admin_Page
19 19
 {
20
-    /**
21
-     * @var EEM_Message
22
-     */
23
-    private $MSG_MODEL;
24
-
25
-    /**
26
-     * @var EEM_Message_Template
27
-     */
28
-    private $MTP_MODEL;
29
-
30
-    /**
31
-     * @var EEM_Message_Template_Group
32
-     */
33
-    private $MTG_MODEL;
34
-
35
-    /**
36
-     * @var EE_Message_Resource_Manager $_message_resource_manager
37
-     */
38
-    protected $_message_resource_manager;
39
-
40
-    /**
41
-     * @var string
42
-     */
43
-    protected $_active_message_type_name = '';
44
-
45
-    /**
46
-     * @var string
47
-     */
48
-    protected $_active_messenger_name = '';
49
-
50
-    /**
51
-     * @var EE_messenger $_active_messenger
52
-     */
53
-    protected $_active_messenger;
54
-
55
-    protected $_activate_meta_box_type;
56
-
57
-    protected $_current_message_meta_box;
58
-
59
-    protected $_current_message_meta_box_object;
60
-
61
-    protected $_context_switcher;
62
-
63
-    protected $_shortcodes           = [];
64
-
65
-    protected $_active_messengers    = [];
66
-
67
-    protected $_active_message_types = [];
68
-
69
-    /**
70
-     * @var EE_Message_Template_Group $_message_template_group
71
-     */
72
-    protected $_message_template_group;
73
-
74
-    protected $_m_mt_settings = [];
75
-
76
-
77
-    /**
78
-     * This is set via the _set_message_template_group method and holds whatever the template pack for the group is.
79
-     * IF there is no group then it gets automatically set to the Default template pack.
80
-     *
81
-     * @since 4.5.0
82
-     *
83
-     * @var EE_Messages_Template_Pack
84
-     */
85
-    protected $_template_pack;
86
-
87
-
88
-    /**
89
-     * This is set via the _set_message_template_group method and holds whatever the template pack variation for the
90
-     * group is.  If there is no group then it automatically gets set to default.
91
-     *
92
-     * @since 4.5.0
93
-     *
94
-     * @var string
95
-     */
96
-    protected $_variation;
97
-
98
-
99
-    /**
100
-     * @param bool $routing
101
-     * @throws EE_Error
102
-     * @throws ReflectionException
103
-     */
104
-    public function __construct($routing = true)
105
-    {
106
-        // make sure messages autoloader is running
107
-        EED_Messages::set_autoloaders();
108
-        parent::__construct($routing);
109
-    }
110
-
111
-
112
-    /**
113
-     * @return EEM_Message
114
-     * @throws EE_Error
115
-     */
116
-    public function getMsgModel()
117
-    {
118
-        if (! $this->MSG_MODEL instanceof EEM_Message) {
119
-            $this->MSG_MODEL = EEM_Message::instance();
120
-        }
121
-        return $this->MSG_MODEL;
122
-    }
123
-
124
-
125
-    /**
126
-     * @return EEM_Message_Template
127
-     * @throws EE_Error
128
-     */
129
-    public function getMtpModel()
130
-    {
131
-        if (! $this->MTP_MODEL instanceof EEM_Message_Template) {
132
-            $this->MTP_MODEL = EEM_Message_Template::instance();
133
-        }
134
-        return $this->MTP_MODEL;
135
-    }
136
-
137
-
138
-    /**
139
-     * @return EEM_Message_Template_Group
140
-     * @throws EE_Error
141
-     */
142
-    public function getMtgModel()
143
-    {
144
-        if (! $this->MTG_MODEL instanceof EEM_Message_Template_Group) {
145
-            $this->MTG_MODEL = EEM_Message_Template_Group::instance();
146
-        }
147
-        return $this->MTG_MODEL;
148
-    }
149
-
150
-
151
-    /**
152
-     * @throws EE_Error
153
-     * @throws ReflectionException
154
-     */
155
-    protected function _init_page_props()
156
-    {
157
-        $this->page_slug        = EE_MSG_PG_SLUG;
158
-        $this->page_label       = esc_html__('Messages Settings', 'event_espresso');
159
-        $this->_admin_base_url  = EE_MSG_ADMIN_URL;
160
-        $this->_admin_base_path = EE_MSG_ADMIN;
161
-
162
-        $messenger    = $this->request->getRequestParam('messenger', '');
163
-        $message_type = $this->request->getRequestParam('message_type', '');
164
-        $this->_active_messenger_name    = $this->request->getRequestParam('MTP_messenger', $messenger);
165
-        $this->_active_message_type_name = $this->request->getRequestParam('MTP_message_type', $message_type);
166
-
167
-        $this->_load_message_resource_manager();
168
-    }
169
-
170
-
171
-    /**
172
-     * loads messenger objects into the $_active_messengers property (so we can access the needed methods)
173
-     *
174
-     * @throws EE_Error
175
-     * @throws InvalidDataTypeException
176
-     * @throws InvalidInterfaceException
177
-     * @throws InvalidArgumentException
178
-     * @throws ReflectionException
179
-     */
180
-    protected function _load_message_resource_manager()
181
-    {
182
-        $this->_message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
183
-    }
184
-
185
-
186
-    /**
187
-     * @return array
188
-     * @throws EE_Error
189
-     * @throws InvalidArgumentException
190
-     * @throws InvalidDataTypeException
191
-     * @throws InvalidInterfaceException
192
-     * @deprecated 4.9.9.rc.014
193
-     */
194
-    public function get_messengers_for_list_table()
195
-    {
196
-        EE_Error::doing_it_wrong(
197
-            __METHOD__,
198
-            sprintf(
199
-                esc_html__(
200
-                    'This method is no longer in use.  There is no replacement for it. The method was used to generate a set of values for use in creating a messenger filter dropdown which is now generated differently via %s',
201
-                    'event_espresso'
202
-                ),
203
-                'Messages_Admin_Page::get_messengers_select_input()'
204
-            ),
205
-            '4.9.9.rc.014'
206
-        );
207
-
208
-        $m_values          = [];
209
-        $active_messengers = $this->getMsgModel()->get_all(['group_by' => 'MSG_messenger']);
210
-        // setup messengers for selects
211
-        $i = 1;
212
-        foreach ($active_messengers as $active_messenger) {
213
-            if ($active_messenger instanceof EE_Message) {
214
-                $m_values[ $i ]['id']   = $active_messenger->messenger();
215
-                $m_values[ $i ]['text'] = ucwords($active_messenger->messenger_label());
216
-                $i++;
217
-            }
218
-        }
219
-
220
-        return $m_values;
221
-    }
222
-
223
-
224
-    /**
225
-     * @return array
226
-     * @throws EE_Error
227
-     * @throws InvalidArgumentException
228
-     * @throws InvalidDataTypeException
229
-     * @throws InvalidInterfaceException
230
-     * @deprecated 4.9.9.rc.014
231
-     */
232
-    public function get_message_types_for_list_table()
233
-    {
234
-        EE_Error::doing_it_wrong(
235
-            __METHOD__,
236
-            sprintf(
237
-                esc_html__(
238
-                    'This method is no longer in use.  There is no replacement for it. The method was used to generate a set of values for use in creating a message type filter dropdown which is now generated differently via %s',
239
-                    'event_espresso'
240
-                ),
241
-                'Messages_Admin_Page::get_message_types_select_input()'
242
-            ),
243
-            '4.9.9.rc.014'
244
-        );
245
-
246
-        $mt_values       = [];
247
-        $active_messages = $this->getMsgModel()->get_all(['group_by' => 'MSG_message_type']);
248
-        $i               = 1;
249
-        foreach ($active_messages as $active_message) {
250
-            if ($active_message instanceof EE_Message) {
251
-                $mt_values[ $i ]['id']   = $active_message->message_type();
252
-                $mt_values[ $i ]['text'] = ucwords($active_message->message_type_label());
253
-                $i++;
254
-            }
255
-        }
256
-
257
-        return $mt_values;
258
-    }
259
-
260
-
261
-    /**
262
-     * @return array
263
-     * @throws EE_Error
264
-     * @throws InvalidArgumentException
265
-     * @throws InvalidDataTypeException
266
-     * @throws InvalidInterfaceException
267
-     * @deprecated 4.9.9.rc.014
268
-     */
269
-    public function get_contexts_for_message_types_for_list_table()
270
-    {
271
-        EE_Error::doing_it_wrong(
272
-            __METHOD__,
273
-            sprintf(
274
-                esc_html__(
275
-                    'This method is no longer in use.  There is no replacement for it. The method was used to generate a set of values for use in creating a message type context filter dropdown which is now generated differently via %s',
276
-                    'event_espresso'
277
-                ),
278
-                'Messages_Admin_Page::get_contexts_for_message_types_select_input()'
279
-            ),
280
-            '4.9.9.rc.014'
281
-        );
282
-
283
-        $contexts                = [];
284
-        $active_message_contexts = $this->getMsgModel()->get_all(['group_by' => 'MSG_context']);
285
-        foreach ($active_message_contexts as $active_message) {
286
-            if ($active_message instanceof EE_Message) {
287
-                $message_type = $active_message->message_type_object();
288
-                if ($message_type instanceof EE_message_type) {
289
-                    $message_type_contexts = $message_type->get_contexts();
290
-                    foreach ($message_type_contexts as $context => $context_details) {
291
-                        $contexts[ $context ] = $context_details['label'];
292
-                    }
293
-                }
294
-            }
295
-        }
296
-
297
-        return $contexts;
298
-    }
299
-
300
-
301
-    /**
302
-     * Generate select input with provided messenger options array.
303
-     *
304
-     * @param array $messenger_options Array of messengers indexed by messenger slug and values are the messenger
305
-     *                                 labels.
306
-     * @return string
307
-     * @throws EE_Error
308
-     */
309
-    public function get_messengers_select_input($messenger_options)
310
-    {
311
-        // if empty or just one value then just return an empty string
312
-        if (
313
-            empty($messenger_options)
314
-            || ! is_array($messenger_options)
315
-            || count($messenger_options) === 1
316
-        ) {
317
-            return '';
318
-        }
319
-        // merge in default
320
-        $messenger_options = array_merge(
321
-            ['none_selected' => esc_html__('Show All Messengers', 'event_espresso')],
322
-            $messenger_options
323
-        );
324
-        $input             = new EE_Select_Input(
325
-            $messenger_options,
326
-            [
327
-                'html_name'  => 'ee_messenger_filter_by',
328
-                'html_id'    => 'ee_messenger_filter_by',
329
-                'html_class' => 'wide',
330
-                'default'    => $this->request->getRequestParam('ee_messenger_filter_by', 'none_selected', 'title'),
331
-            ]
332
-        );
333
-
334
-        return $input->get_html_for_input();
335
-    }
336
-
337
-
338
-    /**
339
-     * Generate select input with provided message type options array.
340
-     *
341
-     * @param array $message_type_options Array of message types indexed by message type slug, and values are the
342
-     *                                    message type labels
343
-     * @return string
344
-     * @throws EE_Error
345
-     */
346
-    public function get_message_types_select_input($message_type_options)
347
-    {
348
-        // if empty or count of options is 1 then just return an empty string
349
-        if (
350
-            empty($message_type_options)
351
-            || ! is_array($message_type_options)
352
-            || count($message_type_options) === 1
353
-        ) {
354
-            return '';
355
-        }
356
-        // merge in default
357
-        $message_type_options = array_merge(
358
-            ['none_selected' => esc_html__('Show All Message Types', 'event_espresso')],
359
-            $message_type_options
360
-        );
361
-        $input                = new EE_Select_Input(
362
-            $message_type_options,
363
-            [
364
-                'html_name'  => 'ee_message_type_filter_by',
365
-                'html_id'    => 'ee_message_type_filter_by',
366
-                'html_class' => 'wide',
367
-                'default'    => $this->request->getRequestParam('ee_message_type_filter_by', 'none_selected', 'title'),
368
-            ]
369
-        );
370
-
371
-        return $input->get_html_for_input();
372
-    }
373
-
374
-
375
-    /**
376
-     * Generate select input with provide message type contexts array.
377
-     *
378
-     * @param array $context_options Array of message type contexts indexed by context slug, and values are the
379
-     *                               context label.
380
-     * @return string
381
-     * @throws EE_Error
382
-     */
383
-    public function get_contexts_for_message_types_select_input($context_options)
384
-    {
385
-        // if empty or count of options is one then just return empty string
386
-        if (
387
-            empty($context_options)
388
-            || ! is_array($context_options)
389
-            || count($context_options) === 1
390
-        ) {
391
-            return '';
392
-        }
393
-        // merge in default
394
-        $context_options = array_merge(
395
-            ['none_selected' => esc_html__('Show all Contexts', 'event_espresso')],
396
-            $context_options
397
-        );
398
-        $input           = new EE_Select_Input(
399
-            $context_options,
400
-            [
401
-                'html_name'  => 'ee_context_filter_by',
402
-                'html_id'    => 'ee_context_filter_by',
403
-                'html_class' => 'wide',
404
-                'default'    => $this->request->getRequestParam('ee_context_filter_by', 'none_selected', 'title'),
405
-            ]
406
-        );
407
-
408
-        return $input->get_html_for_input();
409
-    }
410
-
411
-
412
-    protected function _ajax_hooks()
413
-    {
414
-        add_action('wp_ajax_activate_messenger', [$this, 'activate_messenger_toggle']);
415
-        add_action('wp_ajax_activate_mt', [$this, 'activate_mt_toggle']);
416
-        add_action('wp_ajax_ee_msgs_save_settings', [$this, 'save_settings']);
417
-        add_action('wp_ajax_ee_msgs_update_mt_form', [$this, 'update_mt_form']);
418
-        add_action('wp_ajax_switch_template_pack', [$this, 'switch_template_pack']);
419
-        add_action('wp_ajax_toggle_context_template', [$this, 'toggle_context_template']);
420
-    }
421
-
422
-
423
-    protected function _define_page_props()
424
-    {
425
-        $this->_admin_page_title = $this->page_label;
426
-        $this->_labels           = [
427
-            'buttons'    => [
428
-                'add'    => esc_html__('Add New Message Template', 'event_espresso'),
429
-                'edit'   => esc_html__('Edit Message Template', 'event_espresso'),
430
-                'delete' => esc_html__('Delete Message Template', 'event_espresso'),
431
-            ],
432
-            'publishbox' => esc_html__('Update Actions', 'event_espresso'),
433
-        ];
434
-    }
435
-
436
-
437
-    /**
438
-     *        an array for storing key => value pairs of request actions and their corresponding methods
439
-     *
440
-     * @access protected
441
-     * @return void
442
-     */
443
-    protected function _set_page_routes()
444
-    {
445
-        $GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
446
-        $GRP_ID = $this->request->getRequestParam('id', $GRP_ID, 'int');
447
-        $MSG_ID = $this->request->getRequestParam('MSG_ID', 0, 'int');
448
-
449
-        $this->_page_routes = [
450
-            'default'                          => [
451
-                'func'       => '_message_queue_list_table',
452
-                'capability' => 'ee_read_global_messages',
453
-            ],
454
-            'global_mtps'                      => [
455
-                'func'       => '_ee_default_messages_overview_list_table',
456
-                'capability' => 'ee_read_global_messages',
457
-            ],
458
-            'custom_mtps'                      => [
459
-                'func'       => '_custom_mtps_preview',
460
-                'capability' => 'ee_read_messages',
461
-            ],
462
-            'add_new_message_template'         => [
463
-                'func'       => 'add_message_template',
464
-                'capability' => 'ee_edit_messages',
465
-                'noheader'   => true,
466
-            ],
467
-            'edit_message_template'            => [
468
-                'func'       => '_edit_message_template',
469
-                'capability' => 'ee_edit_message',
470
-                'obj_id'     => $GRP_ID,
471
-            ],
472
-            'preview_message'                  => [
473
-                'func'               => '_preview_message',
474
-                'capability'         => 'ee_read_message',
475
-                'obj_id'             => $GRP_ID,
476
-                'noheader'           => true,
477
-                'headers_sent_route' => 'display_preview_message',
478
-            ],
479
-            'display_preview_message'          => [
480
-                'func'       => '_display_preview_message',
481
-                'capability' => 'ee_read_message',
482
-                'obj_id'     => $GRP_ID,
483
-            ],
484
-            'insert_message_template'          => [
485
-                'func'       => '_insert_or_update_message_template',
486
-                'capability' => 'ee_edit_messages',
487
-                'args'       => ['new' => true],
488
-                'noheader'   => true,
489
-            ],
490
-            'update_message_template'          => [
491
-                'func'       => '_insert_or_update_message_template',
492
-                'capability' => 'ee_edit_message',
493
-                'obj_id'     => $GRP_ID,
494
-                'args'       => ['new' => false],
495
-                'noheader'   => true,
496
-            ],
497
-            'trash_message_template'           => [
498
-                'func'       => '_trash_or_restore_message_template',
499
-                'capability' => 'ee_delete_message',
500
-                'obj_id'     => $GRP_ID,
501
-                'args'       => ['trash' => true, 'all' => true],
502
-                'noheader'   => true,
503
-            ],
504
-            'trash_message_template_context'   => [
505
-                'func'       => '_trash_or_restore_message_template',
506
-                'capability' => 'ee_delete_message',
507
-                'obj_id'     => $GRP_ID,
508
-                'args'       => ['trash' => true],
509
-                'noheader'   => true,
510
-            ],
511
-            'restore_message_template'         => [
512
-                'func'       => '_trash_or_restore_message_template',
513
-                'capability' => 'ee_delete_message',
514
-                'obj_id'     => $GRP_ID,
515
-                'args'       => ['trash' => false, 'all' => true],
516
-                'noheader'   => true,
517
-            ],
518
-            'restore_message_template_context' => [
519
-                'func'       => '_trash_or_restore_message_template',
520
-                'capability' => 'ee_delete_message',
521
-                'obj_id'     => $GRP_ID,
522
-                'args'       => ['trash' => false],
523
-                'noheader'   => true,
524
-            ],
525
-            'delete_message_template'          => [
526
-                'func'       => '_delete_message_template',
527
-                'capability' => 'ee_delete_message',
528
-                'obj_id'     => $GRP_ID,
529
-                'noheader'   => true,
530
-            ],
531
-            'reset_to_default'                 => [
532
-                'func'       => '_reset_to_default_template',
533
-                'capability' => 'ee_edit_message',
534
-                'obj_id'     => $GRP_ID,
535
-                'noheader'   => true,
536
-            ],
537
-            'settings'                         => [
538
-                'func'       => '_settings',
539
-                'capability' => 'manage_options',
540
-            ],
541
-            'update_global_settings'           => [
542
-                'func'       => '_update_global_settings',
543
-                'capability' => 'manage_options',
544
-                'noheader'   => true,
545
-            ],
546
-            'generate_now'                     => [
547
-                'func'       => '_generate_now',
548
-                'capability' => 'ee_send_message',
549
-                'noheader'   => true,
550
-            ],
551
-            'generate_and_send_now'            => [
552
-                'func'       => '_generate_and_send_now',
553
-                'capability' => 'ee_send_message',
554
-                'noheader'   => true,
555
-            ],
556
-            'queue_for_resending'              => [
557
-                'func'       => '_queue_for_resending',
558
-                'capability' => 'ee_send_message',
559
-                'noheader'   => true,
560
-            ],
561
-            'send_now'                         => [
562
-                'func'       => '_send_now',
563
-                'capability' => 'ee_send_message',
564
-                'noheader'   => true,
565
-            ],
566
-            'delete_ee_message'                => [
567
-                'func'       => '_delete_ee_messages',
568
-                'capability' => 'ee_delete_messages',
569
-                'noheader'   => true,
570
-            ],
571
-            'delete_ee_messages'               => [
572
-                'func'       => '_delete_ee_messages',
573
-                'capability' => 'ee_delete_messages',
574
-                'noheader'   => true,
575
-                'obj_id'     => $MSG_ID,
576
-            ],
577
-        ];
578
-    }
579
-
580
-
581
-    protected function _set_page_config()
582
-    {
583
-        $this->_page_config = [
584
-            'default'                  => [
585
-                'nav'           => [
586
-                    'label' => esc_html__('Message Activity', 'event_espresso'),
587
-                    'icon' => 'dashicons-email',
588
-                    'order' => 10,
589
-                ],
590
-                'list_table'    => 'EE_Message_List_Table',
591
-                // 'qtips' => array( 'EE_Message_List_Table_Tips' ),
592
-                'require_nonce' => false,
593
-            ],
594
-            'global_mtps'              => [
595
-                'nav'           => [
596
-                    'label' => esc_html__('Default Message Templates', 'event_espresso'),
597
-                    'icon' => 'dashicons-layout',
598
-                    'order' => 20,
599
-                ],
600
-                'list_table'    => 'Messages_Template_List_Table',
601
-                'help_tabs'     => [
602
-                    'messages_overview_help_tab'                                => [
603
-                        'title'    => esc_html__('Messages Overview', 'event_espresso'),
604
-                        'filename' => 'messages_overview',
605
-                    ],
606
-                    'messages_overview_messages_table_column_headings_help_tab' => [
607
-                        'title'    => esc_html__('Messages Table Column Headings', 'event_espresso'),
608
-                        'filename' => 'messages_overview_table_column_headings',
609
-                    ],
610
-                    'messages_overview_messages_filters_help_tab'               => [
611
-                        'title'    => esc_html__('Message Filters', 'event_espresso'),
612
-                        'filename' => 'messages_overview_filters',
613
-                    ],
614
-                    'messages_overview_messages_views_help_tab'                 => [
615
-                        'title'    => esc_html__('Message Views', 'event_espresso'),
616
-                        'filename' => 'messages_overview_views',
617
-                    ],
618
-                    'message_overview_message_types_help_tab'                   => [
619
-                        'title'    => esc_html__('Message Types', 'event_espresso'),
620
-                        'filename' => 'messages_overview_types',
621
-                    ],
622
-                    'messages_overview_messengers_help_tab'                     => [
623
-                        'title'    => esc_html__('Messengers', 'event_espresso'),
624
-                        'filename' => 'messages_overview_messengers',
625
-                    ],
626
-                ],
627
-                'require_nonce' => false,
628
-            ],
629
-            'custom_mtps'              => [
630
-                'nav'           => [
631
-                    'label' => esc_html__('Custom Message Templates', 'event_espresso'),
632
-                    'icon' => 'dashicons-admin-customizer',
633
-                    'order' => 30,
634
-                ],
635
-                'help_tabs'     => [],
636
-                'require_nonce' => false,
637
-            ],
638
-            'add_new_message_template' => [
639
-                'nav'           => [
640
-                    'label'      => esc_html__('Add New Message Templates', 'event_espresso'),
641
-                    'icon' => 'dashicons-plus-alt',
642
-                    'order'      => 5,
643
-                    'persistent' => false,
644
-                ],
645
-                'require_nonce' => false,
646
-            ],
647
-            'edit_message_template'    => [
648
-                'labels'        => [
649
-                    'buttons'    => [
650
-                        'reset' => esc_html__('Reset Templates', 'event_espresso'),
651
-                    ],
652
-                    'publishbox' => esc_html__('Update Actions', 'event_espresso'),
653
-                ],
654
-                'nav'           => [
655
-                    'label'      => esc_html__('Edit Message Templates', 'event_espresso'),
656
-                    'icon' => 'dashicons-edit-large',
657
-                    'order'      => 5,
658
-                    'persistent' => false,
659
-                    'url'        => '',
660
-                ],
661
-                'metaboxes'     => ['_publish_post_box', '_register_edit_meta_boxes'],
662
-                'has_metaboxes' => true,
663
-                'help_tabs'     => [
664
-                    'edit_message_template'            => [
665
-                        'title'    => esc_html__('Message Template Editor', 'event_espresso'),
666
-                        'callback' => 'edit_message_template_help_tab',
667
-                    ],
668
-                    'message_templates_help_tab'       => [
669
-                        'title'    => esc_html__('Message Templates', 'event_espresso'),
670
-                        'filename' => 'messages_templates',
671
-                    ],
672
-                    'message_template_shortcodes'      => [
673
-                        'title'    => esc_html__('Message Shortcodes', 'event_espresso'),
674
-                        'callback' => 'message_template_shortcodes_help_tab',
675
-                    ],
676
-                    'message_preview_help_tab'         => [
677
-                        'title'    => esc_html__('Message Preview', 'event_espresso'),
678
-                        'filename' => 'messages_preview',
679
-                    ],
680
-                    'messages_overview_other_help_tab' => [
681
-                        'title'    => esc_html__('Messages Other', 'event_espresso'),
682
-                        'filename' => 'messages_overview_other',
683
-                    ],
684
-                ],
685
-                'require_nonce' => false,
686
-            ],
687
-            'display_preview_message'  => [
688
-                'nav'           => [
689
-                    'label'      => esc_html__('Message Preview', 'event_espresso'),
690
-                    'icon' => 'dashicons-visibility-bar',
691
-                    'order'      => 5,
692
-                    'url'        => '',
693
-                    'persistent' => false,
694
-                ],
695
-                'help_tabs'     => [
696
-                    'preview_message' => [
697
-                        'title'    => esc_html__('About Previews', 'event_espresso'),
698
-                        'callback' => 'preview_message_help_tab',
699
-                    ],
700
-                ],
701
-                'require_nonce' => false,
702
-            ],
703
-            'settings'                 => [
704
-                'nav'           => [
705
-                    'label' => esc_html__('Settings', 'event_espresso'),
706
-                    'icon' => 'dashicons-admin-generic',
707
-                    'order' => 40,
708
-                ],
709
-                'metaboxes'     => ['_messages_settings_metaboxes'],
710
-                'help_tabs'     => [
711
-                    'messages_settings_help_tab'               => [
712
-                        'title'    => esc_html__('Messages Settings', 'event_espresso'),
713
-                        'filename' => 'messages_settings',
714
-                    ],
715
-                    'messages_settings_message_types_help_tab' => [
716
-                        'title'    => esc_html__('Activating / Deactivating Message Types', 'event_espresso'),
717
-                        'filename' => 'messages_settings_message_types',
718
-                    ],
719
-                    'messages_settings_messengers_help_tab'    => [
720
-                        'title'    => esc_html__('Activating / Deactivating Messengers', 'event_espresso'),
721
-                        'filename' => 'messages_settings_messengers',
722
-                    ],
723
-                ],
724
-                'require_nonce' => false,
725
-            ],
726
-        ];
727
-    }
728
-
729
-
730
-    protected function _add_screen_options()
731
-    {
732
-        // todo
733
-    }
734
-
735
-
736
-    protected function _add_screen_options_global_mtps()
737
-    {
738
-        /**
739
-         * Note: the reason for the value swap here on $this->_admin_page_title is because $this->_per_page_screen_options
740
-         * uses the $_admin_page_title property and we want different outputs in the different spots.
741
-         */
742
-        $page_title              = $this->_admin_page_title;
743
-        $this->_admin_page_title = esc_html__('Global Message Templates', 'event_espresso');
744
-        $this->_per_page_screen_option();
745
-        $this->_admin_page_title = $page_title;
746
-    }
747
-
748
-
749
-    protected function _add_screen_options_default()
750
-    {
751
-        $this->_admin_page_title = esc_html__('Message Activity', 'event_espresso');
752
-        $this->_per_page_screen_option();
753
-    }
754
-
755
-
756
-    // none of the below group are currently used for Messages
757
-    protected function _add_feature_pointers()
758
-    {
759
-    }
760
-
761
-
762
-    public function admin_init()
763
-    {
764
-    }
765
-
766
-
767
-    public function admin_notices()
768
-    {
769
-    }
770
-
771
-
772
-    public function admin_footer_scripts()
773
-    {
774
-    }
775
-
776
-
777
-    public function messages_help_tab()
778
-    {
779
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_help_tab.template.php');
780
-    }
781
-
782
-
783
-    public function messengers_help_tab()
784
-    {
785
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messenger_help_tab.template.php');
786
-    }
787
-
788
-
789
-    public function message_types_help_tab()
790
-    {
791
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_message_type_help_tab.template.php');
792
-    }
793
-
794
-
795
-    public function messages_overview_help_tab()
796
-    {
797
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_overview_help_tab.template.php');
798
-    }
799
-
800
-
801
-    public function message_templates_help_tab()
802
-    {
803
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_message_templates_help_tab.template.php');
804
-    }
805
-
806
-
807
-    public function edit_message_template_help_tab()
808
-    {
809
-        $args['img1'] = '<img src="' . EE_MSG_ASSETS_URL . 'images/editor.png' . '" alt="'
810
-                        . esc_attr__('Editor Title', 'event_espresso')
811
-                        . '" />';
812
-        $args['img2'] = '<img src="' . EE_MSG_ASSETS_URL . 'images/switch-context.png' . '" alt="'
813
-                        . esc_attr__('Context Switcher and Preview', 'event_espresso')
814
-                        . '" />';
815
-        $args['img3'] = '<img class="left" src="' . EE_MSG_ASSETS_URL . 'images/form-fields.png' . '" alt="'
816
-                        . esc_attr__('Message Template Form Fields', 'event_espresso')
817
-                        . '" />';
818
-        $args['img4'] = '<img class="right" src="' . EE_MSG_ASSETS_URL . 'images/shortcodes-metabox.png' . '" alt="'
819
-                        . esc_attr__('Shortcodes Metabox', 'event_espresso')
820
-                        . '" />';
821
-        $args['img5'] = '<img class="right" src="' . EE_MSG_ASSETS_URL . 'images/publish-meta-box.png' . '" alt="'
822
-                        . esc_attr__('Publish Metabox', 'event_espresso')
823
-                        . '" />';
824
-        EEH_Template::display_template(
825
-            EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_templates_editor_help_tab.template.php',
826
-            $args
827
-        );
828
-    }
829
-
830
-
831
-    /**
832
-     * @throws ReflectionException
833
-     * @throws EE_Error
834
-     */
835
-    public function message_template_shortcodes_help_tab()
836
-    {
837
-        $this->_set_shortcodes();
838
-        $args['shortcodes'] = $this->_shortcodes;
839
-        EEH_Template::display_template(
840
-            EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_shortcodes_help_tab.template.php',
841
-            $args
842
-        );
843
-    }
844
-
845
-
846
-    public function preview_message_help_tab()
847
-    {
848
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_preview_help_tab.template.php');
849
-    }
850
-
851
-
852
-    public function settings_help_tab()
853
-    {
854
-        $args['img1'] = '<img class="inline-text" src="' . EE_MSG_ASSETS_URL . 'images/email-tab-active.png'
855
-                        . '" alt="' . esc_attr__('Active Email Tab', 'event_espresso') . '" />';
856
-        $args['img2'] = '<img class="inline-text" src="' . EE_MSG_ASSETS_URL . 'images/email-tab-inactive.png'
857
-                        . '" alt="' . esc_attr__('Inactive Email Tab', 'event_espresso') . '" />';
858
-        $args['img3'] = '<div class="ee-switch">'
859
-                        . '<input class="ee-switch__input" id="ee-on-off-toggle-on" type="checkbox" checked>'
860
-                        . '<label class="ee-switch__toggle" for="ee-on-off-toggle-on"></label>'
861
-                        . '</div>';
862
-        $args['img4'] = '<div class="switch">'
863
-                        . '<input class="ee-switch__input" id="ee-on-off-toggle-off" type="checkbox">'
864
-                        . '<label class="ee-switch__toggle" for="ee-on-off-toggle-off"></label>'
865
-                        . '</div>';
866
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_settings_help_tab.template.php', $args);
867
-    }
868
-
869
-
870
-    public function load_scripts_styles()
871
-    {
872
-        wp_register_style('espresso_ee_msg', EE_MSG_ASSETS_URL . 'ee_message_admin.css', EVENT_ESPRESSO_VERSION);
873
-        wp_enqueue_style('espresso_ee_msg');
874
-
875
-        wp_register_script(
876
-            'ee-messages-settings',
877
-            EE_MSG_ASSETS_URL . 'ee-messages-settings.js',
878
-            ['jquery-ui-droppable', 'ee-serialize-full-array'],
879
-            EVENT_ESPRESSO_VERSION,
880
-            true
881
-        );
882
-        wp_register_script(
883
-            'ee-msg-list-table-js',
884
-            EE_MSG_ASSETS_URL . 'ee_message_admin_list_table.js',
885
-            ['ee-dialog'],
886
-            EVENT_ESPRESSO_VERSION
887
-        );
888
-    }
889
-
890
-
891
-    public function load_scripts_styles_default()
892
-    {
893
-        wp_enqueue_script('ee-msg-list-table-js');
894
-    }
895
-
896
-
897
-    public function wp_editor_css($mce_css)
898
-    {
899
-        // if we're on the edit_message_template route
900
-        if ($this->_req_action === 'edit_message_template' && $this->_active_messenger instanceof EE_messenger) {
901
-            $message_type_name = $this->_active_message_type_name;
902
-
903
-            // we're going to REPLACE the existing mce css
904
-            // we need to get the css file location from the active messenger
905
-            $mce_css = $this->_active_messenger->get_variation(
906
-                $this->_template_pack,
907
-                $message_type_name,
908
-                true,
909
-                'wpeditor',
910
-                $this->_variation
911
-            );
912
-        }
913
-
914
-        return $mce_css;
915
-    }
916
-
917
-
918
-    /**
919
-     * @throws EE_Error
920
-     * @throws ReflectionException
921
-     */
922
-    public function load_scripts_styles_edit_message_template()
923
-    {
924
-
925
-        $this->_set_shortcodes();
926
-
927
-        EE_Registry::$i18n_js_strings['confirm_default_reset']        = sprintf(
928
-            esc_html__(
929
-                'Are you sure you want to reset the %s %s message templates?  Remember continuing will reset the templates for all contexts in this messenger and message type group.',
930
-                'event_espresso'
931
-            ),
932
-            $this->_message_template_group->messenger_obj()->label['singular'],
933
-            $this->_message_template_group->message_type_obj()->label['singular']
934
-        );
935
-        EE_Registry::$i18n_js_strings['confirm_switch_template_pack'] = esc_html__(
936
-            'Switching the template pack for a messages template will reset the content for the template so the new layout is loaded.  Any custom content in the existing template will be lost. Are you sure you wish to do this?',
937
-            'event_espresso'
938
-        );
939
-        EE_Registry::$i18n_js_strings['server_error']                 = esc_html__(
940
-            'An unknown error occurred on the server while attempting to process your request. Please refresh the page and try again or contact support.',
941
-            'event_espresso'
942
-        );
943
-
944
-        wp_register_script(
945
-            'ee_msgs_edit_js',
946
-            EE_MSG_ASSETS_URL . 'ee_message_editor.js',
947
-            ['jquery'],
948
-            EVENT_ESPRESSO_VERSION
949
-        );
950
-
951
-        wp_enqueue_script('ee_admin_js');
952
-        wp_enqueue_script('ee_msgs_edit_js');
953
-
954
-        // add in special css for tiny_mce
955
-        add_filter('mce_css', [$this, 'wp_editor_css']);
956
-    }
957
-
958
-
959
-    /**
960
-     * @throws EE_Error
961
-     * @throws ReflectionException
962
-     */
963
-    public function load_scripts_styles_display_preview_message()
964
-    {
965
-        $this->_set_message_template_group();
966
-        if ($this->_active_messenger_name) {
967
-            $this->_active_messenger = $this->_message_resource_manager->get_active_messenger(
968
-                $this->_active_messenger_name
969
-            );
970
-        }
971
-
972
-        wp_enqueue_style(
973
-            'espresso_preview_css',
974
-            $this->_active_messenger->get_variation(
975
-                $this->_template_pack,
976
-                $this->_active_message_type_name,
977
-                true,
978
-                'preview',
979
-                $this->_variation
980
-            )
981
-        );
982
-    }
983
-
984
-
985
-    public function load_scripts_styles_settings()
986
-    {
987
-        wp_register_style(
988
-            'ee-message-settings',
989
-            EE_MSG_ASSETS_URL . 'ee_message_settings.css',
990
-            [],
991
-            EVENT_ESPRESSO_VERSION
992
-        );
993
-        wp_enqueue_style('ee-text-links');
994
-        wp_enqueue_style('ee-message-settings');
995
-        wp_enqueue_script('ee-messages-settings');
996
-    }
997
-
998
-
999
-    /**
1000
-     * set views array for List Table
1001
-     */
1002
-    public function _set_list_table_views_global_mtps()
1003
-    {
1004
-        $this->_views = [
1005
-            'in_use' => [
1006
-                'slug'  => 'in_use',
1007
-                'label' => esc_html__('In Use', 'event_espresso'),
1008
-                'count' => 0,
1009
-            ],
1010
-        ];
1011
-    }
1012
-
1013
-
1014
-    /**
1015
-     * Set views array for the Custom Template List Table
1016
-     */
1017
-    public function _set_list_table_views_custom_mtps()
1018
-    {
1019
-        $this->_set_list_table_views_global_mtps();
1020
-        $this->_views['in_use']['bulk_action'] = [
1021
-            'trash_message_template' => esc_html__('Move to Trash', 'event_espresso'),
1022
-        ];
1023
-    }
1024
-
1025
-
1026
-    /**
1027
-     * set views array for message queue list table
1028
-     *
1029
-     * @throws InvalidDataTypeException
1030
-     * @throws InvalidInterfaceException
1031
-     * @throws InvalidArgumentException
1032
-     * @throws EE_Error
1033
-     * @throws ReflectionException
1034
-     */
1035
-    public function _set_list_table_views_default()
1036
-    {
1037
-        EE_Registry::instance()->load_helper('Template');
1038
-
1039
-        $common_bulk_actions = EE_Registry::instance()->CAP->current_user_can(
1040
-            'ee_send_message',
1041
-            'message_list_table_bulk_actions'
1042
-        )
1043
-            ? [
1044
-                'generate_now'          => esc_html__('Generate Now', 'event_espresso'),
1045
-                'generate_and_send_now' => esc_html__('Generate and Send Now', 'event_espresso'),
1046
-                'queue_for_resending'   => esc_html__('Queue for Resending', 'event_espresso'),
1047
-                'send_now'              => esc_html__('Send Now', 'event_espresso'),
1048
-            ]
1049
-            : [];
1050
-
1051
-        $delete_bulk_action = EE_Registry::instance()->CAP->current_user_can(
1052
-            'ee_delete_messages',
1053
-            'message_list_table_bulk_actions'
1054
-        )
1055
-            ? ['delete_ee_messages' => esc_html__('Delete Messages', 'event_espresso')]
1056
-            : [];
1057
-
1058
-
1059
-        $this->_views = [
1060
-            'all' => [
1061
-                'slug'        => 'all',
1062
-                'label'       => esc_html__('All', 'event_espresso'),
1063
-                'count'       => 0,
1064
-                'bulk_action' => array_merge($common_bulk_actions, $delete_bulk_action),
1065
-            ],
1066
-        ];
1067
-
1068
-
1069
-        foreach ($this->getMsgModel()->all_statuses() as $status) {
1070
-            if ($status === EEM_Message::status_debug_only && ! EEM_Message::debug()) {
1071
-                continue;
1072
-            }
1073
-            $status_bulk_actions = $common_bulk_actions;
1074
-            // unset bulk actions not applying to status
1075
-            if (! empty($status_bulk_actions)) {
1076
-                switch ($status) {
1077
-                    case EEM_Message::status_idle:
1078
-                    case EEM_Message::status_resend:
1079
-                        $status_bulk_actions['send_now'] = $common_bulk_actions['send_now'];
1080
-                        break;
1081
-
1082
-                    case EEM_Message::status_failed:
1083
-                    case EEM_Message::status_debug_only:
1084
-                    case EEM_Message::status_messenger_executing:
1085
-                        $status_bulk_actions = [];
1086
-                        break;
1087
-
1088
-                    case EEM_Message::status_incomplete:
1089
-                        unset($status_bulk_actions['queue_for_resending'], $status_bulk_actions['send_now']);
1090
-                        break;
1091
-
1092
-                    case EEM_Message::status_retry:
1093
-                    case EEM_Message::status_sent:
1094
-                        unset($status_bulk_actions['generate_now'], $status_bulk_actions['generate_and_send_now']);
1095
-                        break;
1096
-                }
1097
-            }
1098
-
1099
-            // skip adding messenger executing status to views because it will be included with the Failed view.
1100
-            if ($status === EEM_Message::status_messenger_executing) {
1101
-                continue;
1102
-            }
1103
-
1104
-            $this->_views[ strtolower($status) ] = [
1105
-                'slug'        => strtolower($status),
1106
-                'label'       => EEH_Template::pretty_status($status, false, 'sentence'),
1107
-                'count'       => 0,
1108
-                'bulk_action' => array_merge($status_bulk_actions, $delete_bulk_action),
1109
-            ];
1110
-        }
1111
-    }
1112
-
1113
-
1114
-    /**
1115
-     * @throws EE_Error
1116
-     */
1117
-    protected function _ee_default_messages_overview_list_table()
1118
-    {
1119
-        $this->_admin_page_title = esc_html__('Default Message Templates', 'event_espresso');
1120
-        $this->display_admin_list_table_page_with_no_sidebar();
1121
-    }
1122
-
1123
-
1124
-    /**
1125
-     * @throws EE_Error
1126
-     * @throws ReflectionException
1127
-     */
1128
-    protected function _message_queue_list_table()
1129
-    {
1130
-        $this->_search_btn_label                   = esc_html__('Message Activity', 'event_espresso');
1131
-        $this->_template_args['per_column']        = 6;
1132
-        $this->_template_args['after_list_table']  = $this->_display_legend($this->_message_legend_items());
1133
-        $message_results = trim(EEM_Message::instance()->get_pretty_label_for_results());
1134
-        $this->_template_args['before_list_table'] = ! empty($message_results) ? "<h3>{$message_results}</h3>" : '';
1135
-        $this->display_admin_list_table_page_with_no_sidebar();
1136
-    }
1137
-
1138
-
1139
-    /**
1140
-     * @throws EE_Error
1141
-     */
1142
-    protected function _message_legend_items()
1143
-    {
1144
-
1145
-        $action_css_classes = EEH_MSG_Template::get_message_action_icons();
1146
-        $action_items       = [];
1147
-
1148
-        foreach ($action_css_classes as $action_item => $action_details) {
1149
-            if ($action_item === 'see_notifications_for') {
1150
-                continue;
1151
-            }
1152
-            $action_items[ $action_item ] = [
1153
-                'class' => $action_details['css_class'],
1154
-                'desc'  => $action_details['label'],
1155
-            ];
1156
-        }
1157
-
1158
-        /** @var array $status_items status legend setup */
1159
-        $status_items = [
1160
-            'sent_status'                => [
1161
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_sent,
1162
-                'desc'  => EEH_Template::pretty_status(EEM_Message::status_sent, false, 'sentence'),
1163
-            ],
1164
-            'idle_status'                => [
1165
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_idle,
1166
-                'desc'  => EEH_Template::pretty_status(EEM_Message::status_idle, false, 'sentence'),
1167
-            ],
1168
-            'failed_status'              => [
1169
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_failed,
1170
-                'desc'  => EEH_Template::pretty_status(EEM_Message::status_failed, false, 'sentence'),
1171
-            ],
1172
-            'messenger_executing_status' => [
1173
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_messenger_executing,
1174
-                'desc'  => EEH_Template::pretty_status(EEM_Message::status_messenger_executing, false, 'sentence'),
1175
-            ],
1176
-            'resend_status'              => [
1177
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_resend,
1178
-                'desc'  => EEH_Template::pretty_status(EEM_Message::status_resend, false, 'sentence'),
1179
-            ],
1180
-            'incomplete_status'          => [
1181
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_incomplete,
1182
-                'desc'  => EEH_Template::pretty_status(EEM_Message::status_incomplete, false, 'sentence'),
1183
-            ],
1184
-            'retry_status'               => [
1185
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_retry,
1186
-                'desc'  => EEH_Template::pretty_status(EEM_Message::status_retry, false, 'sentence'),
1187
-            ],
1188
-        ];
1189
-        if (EEM_Message::debug()) {
1190
-            $status_items['debug_only_status'] = [
1191
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_debug_only,
1192
-                'desc'  => EEH_Template::pretty_status(EEM_Message::status_debug_only, false, 'sentence'),
1193
-            ];
1194
-        }
1195
-
1196
-        return array_merge($action_items, $status_items);
1197
-    }
1198
-
1199
-
1200
-    /**
1201
-     * @throws EE_Error
1202
-     */
1203
-    protected function _custom_mtps_preview()
1204
-    {
1205
-        $this->_admin_page_title              = esc_html__('Custom Message Templates (Preview)', 'event_espresso');
1206
-        $this->_template_args['preview_img']  = '<img src="' . EE_MSG_ASSETS_URL . 'images/custom_mtps_preview.png"'
1207
-                                                . ' alt="' . esc_attr__(
1208
-                                                    'Preview Custom Message Templates screenshot',
1209
-                                                    'event_espresso'
1210
-                                                ) . '" />';
1211
-        $this->_template_args['preview_text'] = '<strong>'
1212
-                                                . esc_html__(
1213
-                                                    'Custom Message Templates is a feature that is only available in the premium version of Event Espresso 4 which is available with a support license purchase on EventEspresso.com. With the Custom Message Templates feature, you are able to create custom message templates and assign them on a per-event basis.',
1214
-                                                    'event_espresso'
1215
-                                                )
1216
-                                                . '</strong>';
1217
-
1218
-        $this->display_admin_caf_preview_page('custom_message_types', false);
1219
-    }
1220
-
1221
-
1222
-    /**
1223
-     * get_message_templates
1224
-     * This gets all the message templates for listing on the overview list.
1225
-     *
1226
-     * @access public
1227
-     * @param int    $per_page the amount of templates groups to show per page
1228
-     * @param string $type     the current _view we're getting templates for
1229
-     * @param bool   $count    return count?
1230
-     * @param bool   $all      disregard any paging info (get all data);
1231
-     * @param bool   $global   whether to return just global (true) or custom templates (false)
1232
-     * @return array
1233
-     * @throws EE_Error
1234
-     * @throws InvalidArgumentException
1235
-     * @throws InvalidDataTypeException
1236
-     * @throws InvalidInterfaceException
1237
-     */
1238
-    public function get_message_templates(
1239
-        $per_page = 10,
1240
-        $type = 'in_use',
1241
-        $count = false,
1242
-        $all = false,
1243
-        $global = true
1244
-    ) {
1245
-        $orderby = $this->request->getRequestParam('orderby', 'GRP_ID');
1246
-        $this->request->setRequestParam('orderby', $orderby);
1247
-
1248
-        $order        = $this->request->getRequestParam('order', 'ASC');
1249
-        $current_page = $this->request->getRequestParam('paged', 1, 'int');
1250
-        $per_page     = $this->request->getRequestParam('perpage', $per_page, 'int');
1251
-
1252
-        $offset = ($current_page - 1) * $per_page;
1253
-        $limit  = $all ? null : [$offset, $per_page];
1254
-
1255
-        // options will match what is in the _views array property
1256
-        return $type === 'in_use'
1257
-            ? $this->getMtgModel()->get_all_active_message_templates(
1258
-                $orderby,
1259
-                $order,
1260
-                $limit,
1261
-                $count,
1262
-                $global,
1263
-                true
1264
-            )
1265
-            : $this->getMtgModel()->get_all_trashed_grouped_message_templates(
1266
-                $orderby,
1267
-                $order,
1268
-                $limit,
1269
-                $count,
1270
-                $global
1271
-            );
1272
-    }
1273
-
1274
-
1275
-    /**
1276
-     * filters etc might need a list of installed message_types
1277
-     *
1278
-     * @return array an array of message type objects
1279
-     */
1280
-    public function get_installed_message_types()
1281
-    {
1282
-        $installed_message_types = $this->_message_resource_manager->installed_message_types();
1283
-        $installed               = [];
1284
-
1285
-        foreach ($installed_message_types as $message_type) {
1286
-            $installed[ $message_type->name ] = $message_type;
1287
-        }
1288
-
1289
-        return $installed;
1290
-    }
1291
-
1292
-
1293
-    /**
1294
-     * This is used when creating a custom template. All Custom Templates start based off another template.
1295
-     *
1296
-     * @param string $message_type
1297
-     * @param string $messenger
1298
-     * @param string $GRP_ID
1299
-     *
1300
-     * @throws EE_error
1301
-     * @throws ReflectionException
1302
-     */
1303
-    public function add_message_template($message_type = '', $messenger = '', $GRP_ID = '')
1304
-    {
1305
-        // set values override any request data
1306
-        $message_type = ! empty($message_type) ? $message_type : $this->_active_message_type_name;
1307
-        $messenger    = ! empty($messenger) ? $messenger : $this->_active_messenger_name;
1308
-        $GRP_ID       = ! empty($GRP_ID) ? $GRP_ID : $this->request->getRequestParam('GRP_ID', 0, 'int');
1309
-
1310
-        // we need messenger and message type.  They should be coming from the event editor. If not here then return error
1311
-        if (empty($message_type) || empty($messenger)) {
1312
-            throw new EE_Error(
1313
-                esc_html__(
1314
-                    'Sorry, but we can\'t create new templates because we\'re missing the messenger or message type',
1315
-                    'event_espresso'
1316
-                )
1317
-            );
1318
-        }
1319
-
1320
-        // we need the GRP_ID for the template being used as the base for the new template
1321
-        if (empty($GRP_ID)) {
1322
-            throw new EE_Error(
1323
-                esc_html__(
1324
-                    'In order to create a custom message template the GRP_ID of the template being used as a base is needed',
1325
-                    'event_espresso'
1326
-                )
1327
-            );
1328
-        }
1329
-
1330
-        // let's just make sure the template gets generated!
1331
-
1332
-        // we need to reassign some variables for what the insert is expecting
1333
-        $this->request->setRequestParam('MTP_messenger', $messenger);
1334
-        $this->request->setRequestParam('MTP_message_type', $message_type);
1335
-        $this->request->setRequestParam('GRP_ID', $GRP_ID);
1336
-
1337
-        $this->_insert_or_update_message_template(true);
1338
-    }
1339
-
1340
-
1341
-    /**
1342
-     * @param string $message_type     message type slug
1343
-     * @param string $messenger        messenger slug
1344
-     * @param int    $GRP_ID           GRP_ID for the related message template group this new template will be based
1345
-     *                                 off of.
1346
-     * @throws EE_error
1347
-     * @throws ReflectionException
1348
-     * @deprecated 4.10.29.p
1349
-     */
1350
-    protected function _add_message_template($message_type, $messenger, $GRP_ID)
1351
-    {
1352
-        $this->add_message_template($message_type, $messenger, $GRP_ID);
1353
-    }
1354
-
1355
-
1356
-    /**
1357
-     * _edit_message_template
1358
-     *
1359
-     * @access protected
1360
-     * @return void
1361
-     * @throws InvalidIdentifierException
1362
-     * @throws DomainException
1363
-     * @throws EE_Error
1364
-     * @throws InvalidArgumentException
1365
-     * @throws ReflectionException
1366
-     * @throws InvalidDataTypeException
1367
-     * @throws InvalidInterfaceException
1368
-     */
1369
-    protected function _edit_message_template()
1370
-    {
1371
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1372
-        $template_fields = '';
1373
-        $sidebar_fields  = '';
1374
-        // we filter the tinyMCE settings to remove the validation since message templates by their nature will not have
1375
-        // valid html in the templates.
1376
-        add_filter('tiny_mce_before_init', [$this, 'filter_tinymce_init'], 10, 2);
1377
-
1378
-        $GRP_ID = $this->request->getRequestParam('id', 0, 'int');
1379
-        $EVT_ID = $this->request->getRequestParam('evt_id', 0, 'int');
1380
-
1381
-        $this->_set_shortcodes(); // this also sets the _message_template property.
1382
-        $message_template_group = $this->_message_template_group;
1383
-        $c_label                = $message_template_group->context_label();
1384
-        $c_config               = $message_template_group->contexts_config();
1385
-
1386
-        reset($c_config);
1387
-        $context = $this->request->getRequestParam('context', key($c_config));
1388
-        $context = strtolower($context);
1389
-
1390
-        $action = empty($GRP_ID) ? 'insert_message_template' : 'update_message_template';
1391
-
1392
-        $edit_message_template_form_url = add_query_arg(
1393
-            ['action' => $action, 'noheader' => true],
1394
-            EE_MSG_ADMIN_URL
1395
-        );
1396
-
1397
-        // set active messenger for this view
1398
-        $this->_active_messenger         = $this->_message_resource_manager->get_active_messenger(
1399
-            $message_template_group->messenger()
1400
-        );
1401
-        $this->_active_message_type_name = $message_template_group->message_type();
1402
-
1403
-
1404
-        // Do we have any validation errors?
1405
-        $validators = $this->_get_transient();
1406
-        $v_fields   = ! empty($validators) ? array_keys($validators) : [];
1407
-
1408
-
1409
-        // we need to assemble the title from Various details
1410
-        $context_label = sprintf(
1411
-            esc_html__('(%s %s)', 'event_espresso'),
1412
-            $c_config[ $context ]['label'],
1413
-            ucwords($c_label['label'])
1414
-        );
1415
-
1416
-        $title = sprintf(
1417
-            esc_html__(' %s %s Template %s', 'event_espresso'),
1418
-            ucwords($message_template_group->messenger_obj()->label['singular']),
1419
-            ucwords($message_template_group->message_type_obj()->label['singular']),
1420
-            $context_label
1421
-        );
1422
-
1423
-        $this->_template_args['GRP_ID']           = $GRP_ID;
1424
-        $this->_template_args['message_template'] = $message_template_group;
1425
-        $this->_template_args['is_extra_fields']  = false;
1426
-
1427
-
1428
-        // let's get EEH_MSG_Template so we can get template form fields
1429
-        $template_field_structure = EEH_MSG_Template::get_fields(
1430
-            $message_template_group->messenger(),
1431
-            $message_template_group->message_type()
1432
-        );
1433
-
1434
-        if (! $template_field_structure) {
1435
-            $template_field_structure = false;
1436
-            $template_fields          = esc_html__(
1437
-                'There was an error in assembling the fields for this display (you should see an error message)',
1438
-                'event_espresso'
1439
-            );
1440
-        }
1441
-
1442
-
1443
-        $message_templates = $message_template_group->context_templates();
1444
-
1445
-
1446
-        // if we have the extra key.. then we need to remove the content index from the template_field_structure as it
1447
-        // will get handled in the "extra" array.
1448
-        if (is_array($template_field_structure[ $context ]) && isset($template_field_structure[ $context ]['extra'])) {
1449
-            foreach ($template_field_structure[ $context ]['extra'] as $reference_field => $new_fields) {
1450
-                unset($template_field_structure[ $context ][ $reference_field ]);
1451
-            }
1452
-        }
1453
-
1454
-        // let's loop through the template_field_structure and actually assemble the input fields!
1455
-        if (! empty($template_field_structure)) {
1456
-            foreach ($template_field_structure[ $context ] as $template_field => $field_setup_array) {
1457
-                // if this is an 'extra' template field then we need to remove any existing fields that are keyed up in
1458
-                // the extra array and reset them.
1459
-                if ($template_field === 'extra') {
1460
-                    $this->_template_args['is_extra_fields'] = true;
1461
-                    foreach ($field_setup_array as $reference_field => $new_fields_array) {
1462
-                        $message_template = $message_templates[ $context ][ $reference_field ];
1463
-                        $content          = $message_template instanceof EE_Message_Template
1464
-                            ? $message_template->get('MTP_content')
1465
-                            : '';
1466
-                        foreach ($new_fields_array as $extra_field => $extra_array) {
1467
-                            // let's verify if we need this extra field via the shortcodes parameter.
1468
-                            $continue = false;
1469
-                            if (isset($extra_array['shortcodes_required'])) {
1470
-                                foreach ((array) $extra_array['shortcodes_required'] as $shortcode) {
1471
-                                    if (! array_key_exists($shortcode, $this->_shortcodes)) {
1472
-                                        $continue = true;
1473
-                                    }
1474
-                                }
1475
-                                if ($continue) {
1476
-                                    continue;
1477
-                                }
1478
-                            }
1479
-
1480
-                            $field_id = $reference_field . '-' . $extra_field . '-content';
1481
-
1482
-                            $template_form_fields[ $field_id ]         = $extra_array;
1483
-                            $template_form_fields[ $field_id ]['name'] = 'MTP_template_fields['
1484
-                                                                         . $reference_field
1485
-                                                                         . '][content]['
1486
-                                                                         . $extra_field . ']';
1487
-                            $css_class                                 = isset($extra_array['css_class'])
1488
-                                ? $extra_array['css_class']
1489
-                                : '';
1490
-
1491
-                            $template_form_fields[ $field_id ]['css_class'] = ! empty($v_fields)
1492
-                                                                              && in_array($extra_field, $v_fields, true)
1493
-                                                                              && (
1494
-                                                                                  is_array($validators[ $extra_field ])
1495
-                                                                                  && isset($validators[ $extra_field ]['msg'])
1496
-                                                                              )
1497
-                                ? 'validate-error ' . $css_class
1498
-                                : $css_class;
1499
-
1500
-                            $template_form_fields[ $field_id ]['value'] = ! empty($message_templates)
1501
-                                                                          && isset($content[ $extra_field ])
1502
-                                ? $content[ $extra_field ]
1503
-                                : '';
1504
-
1505
-                            // do we have a validation error?  if we do then let's use that value instead
1506
-                            $template_form_fields[ $field_id ]['value'] = isset($validators[ $extra_field ])
1507
-                                ? $validators[ $extra_field ]['value']
1508
-                                : $template_form_fields[ $field_id ]['value'];
1509
-
1510
-
1511
-                            $template_form_fields[ $field_id ]['db-col'] = 'MTP_content';
1512
-
1513
-                            // shortcode selector
1514
-                            $field_name_to_use                                   = $extra_field === 'main'
1515
-                                ? 'content'
1516
-                                : $extra_field;
1517
-                            $template_form_fields[ $field_id ]['append_content'] = $this->_get_shortcode_selector(
1518
-                                $field_name_to_use,
1519
-                                $field_id
1520
-                            );
1521
-                        }
1522
-                        $template_field_MTP_id           = $reference_field . '-MTP_ID';
1523
-                        $template_field_template_name_id = $reference_field . '-name';
1524
-
1525
-                        $template_form_fields[ $template_field_MTP_id ] = [
1526
-                            'name'       => 'MTP_template_fields[' . $reference_field . '][MTP_ID]',
1527
-                            'label'      => null,
1528
-                            'input'      => 'hidden',
1529
-                            'type'       => 'int',
1530
-                            'required'   => false,
1531
-                            'validation' => false,
1532
-                            'value'      => ! empty($message_templates) ? $message_template->ID() : '',
1533
-                            'css_class'  => '',
1534
-                            'format'     => '%d',
1535
-                            'db-col'     => 'MTP_ID',
1536
-                        ];
1537
-
1538
-                        $template_form_fields[ $template_field_template_name_id ] = [
1539
-                            'name'       => 'MTP_template_fields[' . $reference_field . '][name]',
1540
-                            'label'      => null,
1541
-                            'input'      => 'hidden',
1542
-                            'type'       => 'string',
1543
-                            'required'   => false,
1544
-                            'validation' => true,
1545
-                            'value'      => $reference_field,
1546
-                            'css_class'  => '',
1547
-                            'format'     => '%s',
1548
-                            'db-col'     => 'MTP_template_field',
1549
-                        ];
1550
-                    }
1551
-                    continue; // skip the next stuff, we got the necessary fields here for this dataset.
1552
-                } else {
1553
-                    $field_id                                   = $template_field . '-content';
1554
-                    $template_form_fields[ $field_id ]          = $field_setup_array;
1555
-                    $template_form_fields[ $field_id ]['name']  =
1556
-                        'MTP_template_fields[' . $template_field . '][content]';
1557
-                    $message_template                           =
1558
-                        isset($message_templates[ $context ][ $template_field ])
1559
-                            ? $message_templates[ $context ][ $template_field ]
1560
-                            : null;
1561
-                    $template_form_fields[ $field_id ]['value'] = ! empty($message_templates)
1562
-                                                                  && is_array($message_templates[ $context ])
1563
-                                                                  && $message_template instanceof EE_Message_Template
1564
-                        ? $message_template->get('MTP_content')
1565
-                        : '';
1566
-
1567
-                    // do we have a validator error for this field?  if we do then we'll use that value instead
1568
-                    $template_form_fields[ $field_id ]['value'] = isset($validators[ $template_field ])
1569
-                        ? $validators[ $template_field ]['value']
1570
-                        : $template_form_fields[ $field_id ]['value'];
1571
-
1572
-
1573
-                    $template_form_fields[ $field_id ]['db-col']    = 'MTP_content';
1574
-                    $css_class                                      = isset($field_setup_array['css_class'])
1575
-                        ? $field_setup_array['css_class']
1576
-                        : '';
1577
-                    $template_form_fields[ $field_id ]['css_class'] = ! empty($v_fields)
1578
-                                                                      && in_array($template_field, $v_fields, true)
1579
-                                                                      && isset($validators[ $template_field ]['msg'])
1580
-                        ? 'validate-error ' . $css_class
1581
-                        : $css_class;
1582
-
1583
-                    // shortcode selector
1584
-                    $template_form_fields[ $field_id ]['append_content'] = $this->_get_shortcode_selector(
1585
-                        $template_field,
1586
-                        $field_id
1587
-                    );
1588
-                }
1589
-
1590
-                // k took care of content field(s) now let's take care of others.
1591
-
1592
-                $template_field_MTP_id                 = $template_field . '-MTP_ID';
1593
-                $template_field_field_template_name_id = $template_field . '-name';
1594
-
1595
-                // foreach template field there are actually two form fields created
1596
-                $template_form_fields[ $template_field_MTP_id ] = [
1597
-                    'name'       => 'MTP_template_fields[' . $template_field . '][MTP_ID]',
1598
-                    'label'      => null,
1599
-                    'input'      => 'hidden',
1600
-                    'type'       => 'int',
1601
-                    'required'   => false,
1602
-                    'validation' => true,
1603
-                    'value'      => $message_template instanceof EE_Message_Template ? $message_template->ID() : '',
1604
-                    'css_class'  => '',
1605
-                    'format'     => '%d',
1606
-                    'db-col'     => 'MTP_ID',
1607
-                ];
1608
-
1609
-                $template_form_fields[ $template_field_field_template_name_id ] = [
1610
-                    'name'       => 'MTP_template_fields[' . $template_field . '][name]',
1611
-                    'label'      => null,
1612
-                    'input'      => 'hidden',
1613
-                    'type'       => 'string',
1614
-                    'required'   => false,
1615
-                    'validation' => true,
1616
-                    'value'      => $template_field,
1617
-                    'css_class'  => '',
1618
-                    'format'     => '%s',
1619
-                    'db-col'     => 'MTP_template_field',
1620
-                ];
1621
-            }
1622
-
1623
-            // add other fields
1624
-            $template_form_fields['ee-msg-current-context'] = [
1625
-                'name'       => 'MTP_context',
1626
-                'label'      => null,
1627
-                'input'      => 'hidden',
1628
-                'type'       => 'string',
1629
-                'required'   => false,
1630
-                'validation' => true,
1631
-                'value'      => $context,
1632
-                'css_class'  => '',
1633
-                'format'     => '%s',
1634
-                'db-col'     => 'MTP_context',
1635
-            ];
1636
-
1637
-            $template_form_fields['ee-msg-grp-id'] = [
1638
-                'name'       => 'GRP_ID',
1639
-                'label'      => null,
1640
-                'input'      => 'hidden',
1641
-                'type'       => 'int',
1642
-                'required'   => false,
1643
-                'validation' => true,
1644
-                'value'      => $GRP_ID,
1645
-                'css_class'  => '',
1646
-                'format'     => '%d',
1647
-                'db-col'     => 'GRP_ID',
1648
-            ];
1649
-
1650
-            $template_form_fields['ee-msg-messenger'] = [
1651
-                'name'       => 'MTP_messenger',
1652
-                'label'      => null,
1653
-                'input'      => 'hidden',
1654
-                'type'       => 'string',
1655
-                'required'   => false,
1656
-                'validation' => true,
1657
-                'value'      => $message_template_group->messenger(),
1658
-                'css_class'  => '',
1659
-                'format'     => '%s',
1660
-                'db-col'     => 'MTP_messenger',
1661
-            ];
1662
-
1663
-            $template_form_fields['ee-msg-message-type'] = [
1664
-                'name'       => 'MTP_message_type',
1665
-                'label'      => null,
1666
-                'input'      => 'hidden',
1667
-                'type'       => 'string',
1668
-                'required'   => false,
1669
-                'validation' => true,
1670
-                'value'      => $message_template_group->message_type(),
1671
-                'css_class'  => '',
1672
-                'format'     => '%s',
1673
-                'db-col'     => 'MTP_message_type',
1674
-            ];
1675
-
1676
-            $sidebar_form_fields['ee-msg-is-global'] = [
1677
-                'name'       => 'MTP_is_global',
1678
-                'label'      => esc_html__('Global Template', 'event_espresso'),
1679
-                'input'      => 'hidden',
1680
-                'type'       => 'int',
1681
-                'required'   => false,
1682
-                'validation' => true,
1683
-                'value'      => $message_template_group->get('MTP_is_global'),
1684
-                'css_class'  => '',
1685
-                'format'     => '%d',
1686
-                'db-col'     => 'MTP_is_global',
1687
-            ];
1688
-
1689
-            $sidebar_form_fields['ee-msg-is-override'] = [
1690
-                'name'       => 'MTP_is_override',
1691
-                'label'      => esc_html__('Override all custom', 'event_espresso'),
1692
-                'input'      => $message_template_group->is_global() ? 'checkbox' : 'hidden',
1693
-                'type'       => 'int',
1694
-                'required'   => false,
1695
-                'validation' => true,
1696
-                'value'      => $message_template_group->get('MTP_is_override'),
1697
-                'css_class'  => '',
1698
-                'format'     => '%d',
1699
-                'db-col'     => 'MTP_is_override',
1700
-            ];
1701
-
1702
-            $sidebar_form_fields['ee-msg-is-active'] = [
1703
-                'name'       => 'MTP_is_active',
1704
-                'label'      => esc_html__('Active Template', 'event_espresso'),
1705
-                'input'      => 'hidden',
1706
-                'type'       => 'int',
1707
-                'required'   => false,
1708
-                'validation' => true,
1709
-                'value'      => $message_template_group->is_active(),
1710
-                'css_class'  => '',
1711
-                'format'     => '%d',
1712
-                'db-col'     => 'MTP_is_active',
1713
-            ];
1714
-
1715
-            $sidebar_form_fields['ee-msg-deleted'] = [
1716
-                'name'       => 'MTP_deleted',
1717
-                'label'      => null,
1718
-                'input'      => 'hidden',
1719
-                'type'       => 'int',
1720
-                'required'   => false,
1721
-                'validation' => true,
1722
-                'value'      => $message_template_group->get('MTP_deleted'),
1723
-                'css_class'  => '',
1724
-                'format'     => '%d',
1725
-                'db-col'     => 'MTP_deleted',
1726
-            ];
1727
-            $sidebar_form_fields['ee-msg-author']  = [
1728
-                'name'       => 'MTP_user_id',
1729
-                'label'      => esc_html__('Author', 'event_espresso'),
1730
-                'input'      => 'hidden',
1731
-                'type'       => 'int',
1732
-                'required'   => false,
1733
-                'validation' => false,
1734
-                'value'      => $message_template_group->user(),
1735
-                'format'     => '%d',
1736
-                'db-col'     => 'MTP_user_id',
1737
-            ];
1738
-
1739
-            $sidebar_form_fields['ee-msg-route'] = [
1740
-                'name'  => 'action',
1741
-                'input' => 'hidden',
1742
-                'type'  => 'string',
1743
-                'value' => $action,
1744
-            ];
1745
-
1746
-            $sidebar_form_fields['ee-msg-id']        = [
1747
-                'name'  => 'id',
1748
-                'input' => 'hidden',
1749
-                'type'  => 'int',
1750
-                'value' => $GRP_ID,
1751
-            ];
1752
-            $sidebar_form_fields['ee-msg-evt-nonce'] = [
1753
-                'name'  => $action . '_nonce',
1754
-                'input' => 'hidden',
1755
-                'type'  => 'string',
1756
-                'value' => wp_create_nonce($action . '_nonce'),
1757
-            ];
1758
-
1759
-            $template_switch = $this->request->getRequestParam('template_switch');
1760
-            if ($template_switch) {
1761
-                $sidebar_form_fields['ee-msg-template-switch'] = [
1762
-                    'name'  => 'template_switch',
1763
-                    'input' => 'hidden',
1764
-                    'type'  => 'int',
1765
-                    'value' => 1,
1766
-                ];
1767
-            }
1768
-
1769
-
1770
-            $template_fields = $this->_generate_admin_form_fields($template_form_fields);
1771
-            $sidebar_fields  = $this->_generate_admin_form_fields($sidebar_form_fields);
1772
-        } //end if ( !empty($template_field_structure) )
1773
-
1774
-        // set extra content for publish box
1775
-        $this->_template_args['publish_box_extra_content'] = $sidebar_fields;
1776
-        $this->_set_publish_post_box_vars(
1777
-            'id',
1778
-            $GRP_ID,
1779
-            false,
1780
-            add_query_arg(
1781
-                ['action' => 'global_mtps'],
1782
-                $this->_admin_base_url
1783
-            )
1784
-        );
1785
-
1786
-        // add preview button
1787
-        $preview_url    = parent::add_query_args_and_nonce(
1788
-            [
1789
-                'message_type' => $message_template_group->message_type(),
1790
-                'messenger'    => $message_template_group->messenger(),
1791
-                'context'      => $context,
1792
-                'GRP_ID'       => $GRP_ID,
1793
-                'evt_id'       => $EVT_ID ?: false,
1794
-                'action'       => 'preview_message',
1795
-            ],
1796
-            $this->_admin_base_url
1797
-        );
1798
-        $preview_button = '<a href="' . $preview_url . '" class="button--secondary messages-preview-button">'
1799
-                          . esc_html__('Preview', 'event_espresso')
1800
-                          . '</a>';
1801
-
1802
-
1803
-        // setup context switcher
1804
-        $this->_set_context_switcher(
1805
-            $message_template_group,
1806
-            [
1807
-                'page'    => 'espresso_messages',
1808
-                'action'  => 'edit_message_template',
1809
-                'id'      => $GRP_ID,
1810
-                'evt_id'  => $EVT_ID,
1811
-                'context' => $context,
1812
-                'extra'   => $preview_button,
1813
-            ]
1814
-        );
1815
-
1816
-
1817
-        // main box
1818
-        $this->_template_args['template_fields']                         = $template_fields;
1819
-        $this->_template_args['sidebar_box_id']                          = 'details';
1820
-        $this->_template_args['action']                                  = $action;
1821
-        $this->_template_args['context']                                 = $context;
1822
-        $this->_template_args['edit_message_template_form_url']          = $edit_message_template_form_url;
1823
-        $this->_template_args['learn_more_about_message_templates_link'] =
1824
-            $this->_learn_more_about_message_templates_link();
1825
-
1826
-
1827
-        $this->_template_args['before_admin_page_content'] = '<div class="ee-msg-admin-header">';
1828
-        $this->_template_args['before_admin_page_content'] .= $this->add_active_context_element(
1829
-            $message_template_group,
1830
-            $context,
1831
-            $context_label
1832
-        );
1833
-        $this->_template_args['before_admin_page_content'] .= $this->add_context_switcher();
1834
-        $this->_template_args['before_admin_page_content'] .= '</div>';
1835
-        $this->_template_args['before_admin_page_content'] .= $this->_add_form_element_before();
1836
-        $this->_template_args['after_admin_page_content']  = $this->_add_form_element_after();
1837
-
1838
-        $this->_template_path = $this->_template_args['GRP_ID']
1839
-            ? EE_MSG_TEMPLATE_PATH . 'ee_msg_details_main_edit_meta_box.template.php'
1840
-            : EE_MSG_TEMPLATE_PATH . 'ee_msg_details_main_add_meta_box.template.php';
1841
-
1842
-        // send along EE_Message_Template_Group object for further template use.
1843
-        $this->_template_args['MTP'] = $message_template_group;
1844
-
1845
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
1846
-            $this->_template_path,
1847
-            $this->_template_args,
1848
-            true
1849
-        );
1850
-
1851
-
1852
-        // finally, let's set the admin_page title
1853
-        $this->_admin_page_title = sprintf(esc_html__('Editing %s', 'event_espresso'), $title);
1854
-
1855
-
1856
-        // we need to take care of setting the shortcodes property for use elsewhere.
1857
-        $this->_set_shortcodes();
1858
-
1859
-
1860
-        // final template wrapper
1861
-        $this->display_admin_page_with_sidebar();
1862
-    }
1863
-
1864
-
1865
-    public function filter_tinymce_init($mceInit, $editor_id)
1866
-    {
1867
-        return $mceInit;
1868
-    }
1869
-
1870
-
1871
-    public function add_context_switcher()
1872
-    {
1873
-        return $this->_context_switcher;
1874
-    }
1875
-
1876
-
1877
-    /**
1878
-     * Adds the activation/deactivation toggle for the message template context.
1879
-     *
1880
-     * @param EE_Message_Template_Group $message_template_group
1881
-     * @param string                    $context
1882
-     * @param string                    $context_label
1883
-     * @return string
1884
-     * @throws DomainException
1885
-     * @throws EE_Error
1886
-     * @throws InvalidIdentifierException
1887
-     * @throws ReflectionException
1888
-     */
1889
-    protected function add_active_context_element(
1890
-        EE_Message_Template_Group $message_template_group,
1891
-        $context,
1892
-        $context_label
1893
-    ) {
1894
-        $template_args = [
1895
-            'context'                   => $context,
1896
-            'nonce'                     => wp_create_nonce('activate_' . $context . '_toggle_nonce'),
1897
-            'is_active'                 => $message_template_group->is_context_active($context),
1898
-            'on_off_action'             => $message_template_group->is_context_active($context)
1899
-                ? 'context-off'
1900
-                : 'context-on',
1901
-            'context_label'             => str_replace(['(', ')'], '', $context_label),
1902
-            'message_template_group_id' => $message_template_group->ID(),
1903
-        ];
1904
-        return EEH_Template::display_template(
1905
-            EE_MSG_TEMPLATE_PATH . 'ee_msg_editor_active_context_element.template.php',
1906
-            $template_args,
1907
-            true
1908
-        );
1909
-    }
1910
-
1911
-
1912
-    /**
1913
-     * Ajax callback for `toggle_context_template` ajax action.
1914
-     * Handles toggling the message context on or off.
1915
-     *
1916
-     * @throws EE_Error
1917
-     * @throws InvalidArgumentException
1918
-     * @throws InvalidDataTypeException
1919
-     * @throws InvalidIdentifierException
1920
-     * @throws InvalidInterfaceException
1921
-     */
1922
-    public function toggle_context_template()
1923
-    {
1924
-        $success = true;
1925
-        // check for required data
1926
-        if (
1927
-            ! (
1928
-                $this->request->requestParamIsSet('message_template_group_id')
1929
-                && $this->request->requestParamIsSet('context')
1930
-                && $this->request->requestParamIsSet('status')
1931
-            )
1932
-        ) {
1933
-            EE_Error::add_error(
1934
-                esc_html__('Required data for doing this action is not available.', 'event_espresso'),
1935
-                __FILE__,
1936
-                __FUNCTION__,
1937
-                __LINE__
1938
-            );
1939
-            $success = false;
1940
-        }
1941
-
1942
-        $nonce   = $this->request->getRequestParam('toggle_context_nonce', '');
1943
-        $context = $this->request->getRequestParam('context', '');
1944
-        $status  = $this->request->getRequestParam('status', '');
1945
-
1946
-        $this->_verify_nonce($nonce, "activate_{$context}_toggle_nonce");
1947
-
1948
-        if ($status !== 'off' && $status !== 'on') {
1949
-            EE_Error::add_error(
1950
-                sprintf(
1951
-                    esc_html__('The given status (%s) is not valid. Must be "off" or "on"', 'event_espresso'),
1952
-                    $status
1953
-                ),
1954
-                __FILE__,
1955
-                __FUNCTION__,
1956
-                __LINE__
1957
-            );
1958
-            $success = false;
1959
-        }
1960
-        $message_template_group_id = $this->request->getRequestParam('message_template_group_id', 0, 'int');
1961
-        $message_template_group    = $this->getMtgModel()->get_one_by_ID($message_template_group_id);
1962
-        if (! $message_template_group instanceof EE_Message_Template_Group) {
1963
-            EE_Error::add_error(
1964
-                sprintf(
1965
-                    esc_html__(
1966
-                        'Unable to change the active state because the given id "%1$d" does not match a valid "%2$s"',
1967
-                        'event_espresso'
1968
-                    ),
1969
-                    $message_template_group_id,
1970
-                    'EE_Message_Template_Group'
1971
-                ),
1972
-                __FILE__,
1973
-                __FUNCTION__,
1974
-                __LINE__
1975
-            );
1976
-            $success = false;
1977
-        }
1978
-        if ($success) {
1979
-            $success = $status === 'off'
1980
-                ? $message_template_group->deactivate_context($context)
1981
-                : $message_template_group->activate_context($context);
1982
-        }
1983
-        $this->_template_args['success'] = $success;
1984
-        $this->_return_json();
1985
-    }
1986
-
1987
-
1988
-    public function _add_form_element_before()
1989
-    {
1990
-        return '<form method="post" action="'
1991
-               . $this->_template_args['edit_message_template_form_url']
1992
-               . '" id="ee-msg-edit-frm">';
1993
-    }
1994
-
1995
-
1996
-    public function _add_form_element_after()
1997
-    {
1998
-        return '</form>';
1999
-    }
2000
-
2001
-
2002
-    /**
2003
-     * This executes switching the template pack for a message template.
2004
-     *
2005
-     * @throws EE_Error
2006
-     * @throws InvalidDataTypeException
2007
-     * @throws InvalidInterfaceException
2008
-     * @throws InvalidArgumentException
2009
-     * @throws ReflectionException
2010
-     * @since 4.5.0
2011
-     */
2012
-    public function switch_template_pack()
2013
-    {
2014
-
2015
-        $GRP_ID        = $this->request->getRequestParam('GRP_ID', 0, 'int');
2016
-        $template_pack = $this->request->getRequestParam('template_pack', '');
2017
-
2018
-        // verify we have needed values.
2019
-        if (empty($GRP_ID) || empty($template_pack)) {
2020
-            $this->_template_args['error'] = true;
2021
-            EE_Error::add_error(
2022
-                esc_html__('The required date for switching templates is not available.', 'event_espresso'),
2023
-                __FILE__,
2024
-                __FUNCTION__,
2025
-                __LINE__
2026
-            );
2027
-        } else {
2028
-            // get template, set the new template_pack and then reset to default
2029
-            /** @var EE_Message_Template_Group $message_template_group */
2030
-            $message_template_group = $this->getMtgModel()->get_one_by_ID($GRP_ID);
2031
-
2032
-            $message_template_group->set_template_pack_name($template_pack);
2033
-            $this->request->setRequestParam('msgr', $message_template_group->messenger());
2034
-            $this->request->setRequestParam('mt', $message_template_group->message_type());
2035
-
2036
-            $query_args = $this->_reset_to_default_template();
2037
-
2038
-            if (empty($query_args['id'])) {
2039
-                EE_Error::add_error(
2040
-                    esc_html__(
2041
-                        'Something went wrong with switching the template pack. Please try again or contact EE support',
2042
-                        'event_espresso'
2043
-                    ),
2044
-                    __FILE__,
2045
-                    __FUNCTION__,
2046
-                    __LINE__
2047
-                );
2048
-                $this->_template_args['error'] = true;
2049
-            } else {
2050
-                $template_label       = $message_template_group->get_template_pack()->label;
2051
-                $template_pack_labels = $message_template_group->messenger_obj()->get_supports_labels();
2052
-                EE_Error::add_success(
2053
-                    sprintf(
2054
-                        esc_html__(
2055
-                            'This message template has been successfully switched to use the %1$s %2$s.  Please wait while the page reloads with your new template.',
2056
-                            'event_espresso'
2057
-                        ),
2058
-                        $template_label,
2059
-                        $template_pack_labels->template_pack
2060
-                    )
2061
-                );
2062
-                // generate the redirect url for js.
2063
-                $url = self::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2064
-
2065
-                $this->_template_args['data']['redirect_url'] = $url;
2066
-                $this->_template_args['success']              = true;
2067
-            }
2068
-
2069
-            $this->_return_json();
2070
-        }
2071
-    }
2072
-
2073
-
2074
-    /**
2075
-     * This handles resetting the template for the given messenger/message_type so that users can start from scratch if
2076
-     * they want.
2077
-     *
2078
-     * @access protected
2079
-     * @return array|void
2080
-     * @throws EE_Error
2081
-     * @throws InvalidArgumentException
2082
-     * @throws InvalidDataTypeException
2083
-     * @throws InvalidInterfaceException
2084
-     * @throws ReflectionException
2085
-     */
2086
-    protected function _reset_to_default_template()
2087
-    {
2088
-        $templates    = [];
2089
-        $GRP_ID       = $this->request->getRequestParam('GRP_ID', 0, 'int');
2090
-        $messenger    = $this->request->getRequestParam('msgr');
2091
-        $message_type = $this->request->getRequestParam('mt');
2092
-        // we need to make sure we've got the info we need.
2093
-        if (! ($GRP_ID && $messenger && $message_type)) {
2094
-            EE_Error::add_error(
2095
-                esc_html__(
2096
-                    'In order to reset the template to its default we require the messenger, message type, and message template GRP_ID to know what is being reset.  At least one of these is missing.',
2097
-                    'event_espresso'
2098
-                ),
2099
-                __FILE__,
2100
-                __FUNCTION__,
2101
-                __LINE__
2102
-            );
2103
-        }
2104
-
2105
-        // all templates will be reset to whatever the defaults are
2106
-        // for the global template matching the messenger and message type.
2107
-        $success = ! empty($GRP_ID);
2108
-
2109
-        if ($success) {
2110
-            // let's first determine if the incoming template is a global template,
2111
-            // if it isn't then we need to get the global template matching messenger and message type.
2112
-            // $MTPG = $this->getMtgModel()->get_one_by_ID( $GRP_ID );
2113
-
2114
-
2115
-            // note this is ONLY deleting the template fields (Message Template rows) NOT the message template group.
2116
-            $success = $this->_delete_mtp_permanently($GRP_ID, false);
2117
-
2118
-            if ($success) {
2119
-                // if successfully deleted, lets generate the new ones.
2120
-                // Note. We set GLOBAL to true, because resets on ANY template
2121
-                // will use the related global template defaults for regeneration.
2122
-                // This means that if a custom template is reset it resets to whatever the related global template is.
2123
-                // HOWEVER, we DO keep the template pack and template variation set
2124
-                // for the current custom template when resetting.
2125
-                $templates = $this->_generate_new_templates($messenger, $message_type, $GRP_ID, true);
2126
-            }
2127
-        }
2128
-
2129
-        // any error messages?
2130
-        if (! $success) {
2131
-            EE_Error::add_error(
2132
-                esc_html__(
2133
-                    'Something went wrong with deleting existing templates. Unable to reset to default',
2134
-                    'event_espresso'
2135
-                ),
2136
-                __FILE__,
2137
-                __FUNCTION__,
2138
-                __LINE__
2139
-            );
2140
-        }
2141
-
2142
-        // all good, let's add a success message!
2143
-        if ($success && ! empty($templates)) {
2144
-            // the info for the template we generated is the first element in the returned array
2145
-            EE_Error::overwrite_success();
2146
-            EE_Error::add_success(esc_html__('Templates have been reset to defaults.', 'event_espresso'));
2147
-        }
2148
-
2149
-
2150
-        $query_args = [
2151
-            'id'      => isset($templates['GRP_ID']) ? $templates['GRP_ID'] : null,
2152
-            'context' => isset($templates['MTP_context']) ? $templates['MTP_context'] : null,
2153
-            'action'  => isset($templates['GRP_ID']) ? 'edit_message_template' : 'global_mtps',
2154
-        ];
2155
-
2156
-        // if called via ajax then we return query args otherwise redirect
2157
-        if ($this->request->isAjax()) {
2158
-            return $query_args;
2159
-        }
2160
-        $this->_redirect_after_action(false, '', '', $query_args, true);
2161
-    }
2162
-
2163
-
2164
-    /**
2165
-     * Retrieve and set the message preview for display.
2166
-     *
2167
-     * @param bool $send if TRUE then we are doing an actual TEST send with the results of the preview.
2168
-     * @return string
2169
-     * @throws ReflectionException
2170
-     * @throws EE_Error
2171
-     * @throws InvalidArgumentException
2172
-     * @throws InvalidDataTypeException
2173
-     * @throws InvalidInterfaceException
2174
-     */
2175
-    public function _preview_message($send = false)
2176
-    {
2177
-        // first make sure we've got the necessary parameters
2178
-        $GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
2179
-        if (! ($GRP_ID && $this->_active_messenger_name && $this->_active_message_type_name)) {
2180
-            EE_Error::add_error(
2181
-                esc_html__('Missing necessary parameters for displaying preview', 'event_espresso'),
2182
-                __FILE__,
2183
-                __FUNCTION__,
2184
-                __LINE__
2185
-            );
2186
-        }
2187
-
2188
-        $context = $this->request->getRequestParam('context');
2189
-        // get the preview!
2190
-        $preview = EED_Messages::preview_message(
2191
-            $this->_active_message_type_name,
2192
-            $context,
2193
-            $this->_active_messenger_name,
2194
-            $send
2195
-        );
2196
-
2197
-        if ($send) {
2198
-            return $preview;
2199
-        }
2200
-
2201
-        // if we have an evt_id set on the request, use it.
2202
-        $EVT_ID = $this->request->getRequestParam('evt_id', 0, 'int');
2203
-
2204
-        // let's add a button to go back to the edit view
2205
-        $query_args             = [
2206
-            'id'      => $GRP_ID,
2207
-            'evt_id'  => $EVT_ID,
2208
-            'context' => $context,
2209
-            'action'  => 'edit_message_template',
2210
-        ];
2211
-        $go_back_url            = parent::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2212
-        $preview_button         = '<a href="'
2213
-                                  . $go_back_url
2214
-                                  . '" class="button--secondary messages-preview-go-back-button">'
2215
-                                  . esc_html__('Go Back to Edit', 'event_espresso')
2216
-                                  . '</a>';
2217
-        $message_types          = $this->get_installed_message_types();
2218
-        $active_messenger       = $this->_message_resource_manager->get_active_messenger($this->_active_messenger_name);
2219
-        $active_messenger_label = $active_messenger instanceof EE_messenger
2220
-            ? ucwords($active_messenger->label['singular'])
2221
-            : esc_html__('Unknown Messenger', 'event_espresso');
2222
-        // let's provide a helpful title for context
2223
-        $preview_title = sprintf(
2224
-            esc_html__('Viewing Preview for %s %s Message Template', 'event_espresso'),
2225
-            $active_messenger_label,
2226
-            ucwords($message_types[ $this->_active_message_type_name ]->label['singular'])
2227
-        );
2228
-        if (empty($preview)) {
2229
-            $this->noEventsErrorMessage();
2230
-        }
2231
-        // setup display of preview.
2232
-        $this->_admin_page_title                    = $preview_title;
2233
-        $this->_template_args['admin_page_title']   = $preview_title;
2234
-        $this->_template_args['admin_page_content'] = $preview_button . '<br />' . $preview;
2235
-        $this->_template_args['data']['force_json'] = true;
2236
-
2237
-        return '';
2238
-    }
2239
-
2240
-
2241
-    /**
2242
-     * Used to set an error if there are no events available for generating a preview/test send.
2243
-     *
2244
-     * @param bool $test_send Whether the error should be generated for the context of a test send.
2245
-     */
2246
-    protected function noEventsErrorMessage($test_send = false)
2247
-    {
2248
-        $events_url = parent::add_query_args_and_nonce(
2249
-            [
2250
-                'action' => 'default',
2251
-                'page'   => 'espresso_events',
2252
-            ],
2253
-            admin_url('admin.php')
2254
-        );
2255
-        $message    = $test_send
2256
-            ? esc_html__(
2257
-                'A test message could not be sent for this message template because there are no events created yet. The preview system uses actual events for generating the test message. %1$sGo see your events%2$s!',
2258
-                'event_espresso'
2259
-            )
2260
-            : esc_html__(
2261
-                'There is no preview for this message template available because there are no events created yet. The preview system uses actual events for generating the preview. %1$sGo see your events%2$s!',
2262
-                'event_espresso'
2263
-            );
2264
-
2265
-        EE_Error::add_attention(
2266
-            sprintf(
2267
-                $message,
2268
-                "<a href='{$events_url}'>",
2269
-                '</a>'
2270
-            )
2271
-        );
2272
-    }
2273
-
2274
-
2275
-    /**
2276
-     * The initial _preview_message is on a no headers route.  It will optionally call this if necessary otherwise it
2277
-     * gets called automatically.
2278
-     *
2279
-     * @return void
2280
-     * @throws EE_Error
2281
-     * @since 4.5.0
2282
-     *
2283
-     */
2284
-    protected function _display_preview_message()
2285
-    {
2286
-        $this->display_admin_page_with_no_sidebar();
2287
-    }
2288
-
2289
-
2290
-    /**
2291
-     * registers metaboxes that should show up on the "edit_message_template" page
2292
-     *
2293
-     * @access protected
2294
-     * @return void
2295
-     */
2296
-    protected function _register_edit_meta_boxes()
2297
-    {
2298
-        $this->addMetaBox(
2299
-            'mtp_valid_shortcodes',
2300
-            esc_html__('Valid Shortcodes', 'event_espresso'),
2301
-            [$this, 'shortcode_meta_box'],
2302
-            $this->_current_screen->id,
2303
-            'side'
2304
-        );
2305
-        $this->addMetaBox(
2306
-            'mtp_extra_actions',
2307
-            esc_html__('Extra Actions', 'event_espresso'),
2308
-            [$this, 'extra_actions_meta_box'],
2309
-            $this->_current_screen->id,
2310
-            'side',
2311
-            'high'
2312
-        );
2313
-        $this->addMetaBox(
2314
-            'mtp_templates',
2315
-            esc_html__('Template Styles', 'event_espresso'),
2316
-            [$this, 'template_pack_meta_box'],
2317
-            $this->_current_screen->id,
2318
-            'side',
2319
-            'high'
2320
-        );
2321
-    }
2322
-
2323
-
2324
-    /**
2325
-     * metabox content for all template pack and variation selection.
2326
-     *
2327
-     * @return void
2328
-     * @throws DomainException
2329
-     * @throws EE_Error
2330
-     * @throws InvalidArgumentException
2331
-     * @throws ReflectionException
2332
-     * @throws InvalidDataTypeException
2333
-     * @throws InvalidInterfaceException
2334
-     * @since 4.5.0
2335
-     */
2336
-    public function template_pack_meta_box()
2337
-    {
2338
-        $this->_set_message_template_group();
2339
-
2340
-        $tp_collection = EEH_MSG_Template::get_template_pack_collection();
2341
-
2342
-        $tp_select_values = [];
2343
-
2344
-        foreach ($tp_collection as $tp) {
2345
-            // only include template packs that support this messenger and message type!
2346
-            $supports = $tp->get_supports();
2347
-            if (
2348
-                ! isset($supports[ $this->_message_template_group->messenger() ])
2349
-                || ! in_array(
2350
-                    $this->_message_template_group->message_type(),
2351
-                    $supports[ $this->_message_template_group->messenger() ],
2352
-                    true
2353
-                )
2354
-            ) {
2355
-                // not supported
2356
-                continue;
2357
-            }
2358
-
2359
-            $tp_select_values[] = [
2360
-                'text' => $tp->label,
2361
-                'id'   => $tp->dbref,
2362
-            ];
2363
-        }
2364
-
2365
-        // if empty $tp_select_values then we make sure default is set because EVERY message type should be supported by
2366
-        // the default template pack.  This still allows for the odd template pack to override.
2367
-        if (empty($tp_select_values)) {
2368
-            $tp_select_values[] = [
2369
-                'text' => esc_html__('Default', 'event_espresso'),
2370
-                'id'   => 'default',
2371
-            ];
2372
-        }
2373
-
2374
-        // setup variation select values for the currently selected template.
2375
-        $variations               = $this->_message_template_group->get_template_pack()->get_variations(
2376
-            $this->_message_template_group->messenger(),
2377
-            $this->_message_template_group->message_type()
2378
-        );
2379
-        $variations_select_values = [];
2380
-        foreach ($variations as $variation => $label) {
2381
-            $variations_select_values[] = [
2382
-                'text' => $label,
2383
-                'id'   => $variation,
2384
-            ];
2385
-        }
2386
-
2387
-        $template_pack_labels = $this->_message_template_group->messenger_obj()->get_supports_labels();
2388
-
2389
-        $template_args['template_packs_selector']        = EEH_Form_Fields::select_input(
2390
-            'MTP_template_pack',
2391
-            $tp_select_values,
2392
-            $this->_message_template_group->get_template_pack_name()
2393
-        );
2394
-        $template_args['variations_selector']            = EEH_Form_Fields::select_input(
2395
-            'MTP_template_variation',
2396
-            $variations_select_values,
2397
-            $this->_message_template_group->get_template_pack_variation()
2398
-        );
2399
-        $template_args['template_pack_label']            = $template_pack_labels->template_pack;
2400
-        $template_args['template_variation_label']       = $template_pack_labels->template_variation;
2401
-        $template_args['template_pack_description']      = $template_pack_labels->template_pack_description;
2402
-        $template_args['template_variation_description'] = $template_pack_labels->template_variation_description;
2403
-
2404
-        $template = EE_MSG_TEMPLATE_PATH . 'template_pack_and_variations_metabox.template.php';
2405
-
2406
-        EEH_Template::display_template($template, $template_args);
2407
-    }
2408
-
2409
-
2410
-    /**
2411
-     * This meta box holds any extra actions related to Message Templates
2412
-     * For now, this includes Resetting templates to defaults and sending a test email.
2413
-     *
2414
-     * @access  public
2415
-     * @return void
2416
-     * @throws EE_Error
2417
-     */
2418
-    public function extra_actions_meta_box()
2419
-    {
2420
-        $template_form_fields = [];
2421
-
2422
-        $extra_args = [
2423
-            'msgr'   => $this->_message_template_group->messenger(),
2424
-            'mt'     => $this->_message_template_group->message_type(),
2425
-            'GRP_ID' => $this->_message_template_group->GRP_ID(),
2426
-        ];
2427
-        // first we need to see if there are any fields
2428
-        $fields = $this->_message_template_group->messenger_obj()->get_test_settings_fields();
2429
-
2430
-        if (! empty($fields)) {
2431
-            // yup there be fields
2432
-            foreach ($fields as $field => $config) {
2433
-                $field_id = $this->_message_template_group->messenger() . '_' . $field;
2434
-                $existing = $this->_message_template_group->messenger_obj()->get_existing_test_settings();
2435
-                $default  = isset($config['default']) ? $config['default'] : '';
2436
-                $default  = isset($config['value']) ? $config['value'] : $default;
2437
-
2438
-                // if type is hidden and the value is empty
2439
-                // something may have gone wrong so let's correct with the defaults
2440
-                $fix                = $config['input'] === 'hidden'
2441
-                                      && isset($existing[ $field ])
2442
-                                      && empty($existing[ $field ])
2443
-                    ? $default
2444
-                    : '';
2445
-                $existing[ $field ] = isset($existing[ $field ]) && empty($fix)
2446
-                    ? $existing[ $field ]
2447
-                    : $fix;
2448
-
2449
-                $template_form_fields[ $field_id ] = [
2450
-                    'name'       => 'test_settings_fld[' . $field . ']',
2451
-                    'label'      => $config['label'],
2452
-                    'input'      => $config['input'],
2453
-                    'type'       => $config['type'],
2454
-                    'required'   => $config['required'],
2455
-                    'validation' => $config['validation'],
2456
-                    'value'      => isset($existing[ $field ]) ? $existing[ $field ] : $default,
2457
-                    'css_class'  => $config['css_class'],
2458
-                    'options'    => isset($config['options']) ? $config['options'] : [],
2459
-                    'default'    => $default,
2460
-                    'format'     => $config['format'],
2461
-                ];
2462
-            }
2463
-        }
2464
-
2465
-        $test_settings_html = ! empty($template_form_fields)
2466
-            ? $this->_generate_admin_form_fields($template_form_fields, 'string', 'ee_tst_settings_flds')
2467
-            : '';
2468
-
2469
-        // print out $test_settings_fields
2470
-        if (! empty($test_settings_html)) {
2471
-            $test_settings_html .= '<input type="submit" class="button--primary mtp-test-button alignright" ';
2472
-            $test_settings_html .= 'name="test_button" value="';
2473
-            $test_settings_html .= esc_html__('Test Send', 'event_espresso');
2474
-            $test_settings_html .= '" /><div style="clear:both"></div>';
2475
-        }
2476
-
2477
-        // and button
2478
-        $test_settings_html .= '<div class="publishing-action alignright resetbutton">';
2479
-        $test_settings_html .= '<p>';
2480
-        $test_settings_html .= esc_html__('Need to reset this message type and start over?', 'event_espresso');
2481
-        $test_settings_html .= '</p>';
2482
-        $test_settings_html .= $this->get_action_link_or_button(
2483
-            'reset_to_default',
2484
-            'reset',
2485
-            $extra_args,
2486
-            'button--primary reset-default-button'
2487
-        );
2488
-        $test_settings_html .= '</div><div style="clear:both"></div>';
2489
-        echo wp_kses($test_settings_html, AllowedTags::getWithFormTags());
2490
-    }
2491
-
2492
-
2493
-    /**
2494
-     * This returns the shortcode selector skeleton for a given context and field.
2495
-     *
2496
-     * @param string $field           The name of the field retrieving shortcodes for.
2497
-     * @param string $linked_input_id The css id of the input that the shortcodes get added to.
2498
-     * @return string
2499
-     * @throws DomainException
2500
-     * @throws EE_Error
2501
-     * @throws InvalidArgumentException
2502
-     * @throws ReflectionException
2503
-     * @throws InvalidDataTypeException
2504
-     * @throws InvalidInterfaceException
2505
-     * @since 4.9.rc.000
2506
-     */
2507
-    protected function _get_shortcode_selector($field, $linked_input_id)
2508
-    {
2509
-        $template_args = [
2510
-            'shortcodes'      => $this->_get_shortcodes([$field]),
2511
-            'fieldname'       => $field,
2512
-            'linked_input_id' => $linked_input_id,
2513
-        ];
2514
-
2515
-        return EEH_Template::display_template(
2516
-            EE_MSG_TEMPLATE_PATH . 'shortcode_selector_skeleton.template.php',
2517
-            $template_args,
2518
-            true
2519
-        );
2520
-    }
2521
-
2522
-
2523
-    /**
2524
-     * This just takes care of returning the meta box content for shortcodes (only used on the edit message template
2525
-     * page)
2526
-     *
2527
-     * @access public
2528
-     * @return void
2529
-     * @throws EE_Error
2530
-     * @throws InvalidArgumentException
2531
-     * @throws ReflectionException
2532
-     * @throws InvalidDataTypeException
2533
-     * @throws InvalidInterfaceException
2534
-     */
2535
-    public function shortcode_meta_box()
2536
-    {
2537
-        $shortcodes = $this->_get_shortcodes([], false);
2538
-        // just make sure the shortcodes property is set
2539
-        // $messenger = $this->_message_template_group->messenger_obj();
2540
-        // now let's set the content depending on the status of the shortcodes array
2541
-        if (empty($shortcodes)) {
2542
-            echo '<p>' . esc_html__('There are no valid shortcodes available', 'event_espresso') . '</p>';
2543
-            return;
2544
-        }
2545
-        ?>
20
+	/**
21
+	 * @var EEM_Message
22
+	 */
23
+	private $MSG_MODEL;
24
+
25
+	/**
26
+	 * @var EEM_Message_Template
27
+	 */
28
+	private $MTP_MODEL;
29
+
30
+	/**
31
+	 * @var EEM_Message_Template_Group
32
+	 */
33
+	private $MTG_MODEL;
34
+
35
+	/**
36
+	 * @var EE_Message_Resource_Manager $_message_resource_manager
37
+	 */
38
+	protected $_message_resource_manager;
39
+
40
+	/**
41
+	 * @var string
42
+	 */
43
+	protected $_active_message_type_name = '';
44
+
45
+	/**
46
+	 * @var string
47
+	 */
48
+	protected $_active_messenger_name = '';
49
+
50
+	/**
51
+	 * @var EE_messenger $_active_messenger
52
+	 */
53
+	protected $_active_messenger;
54
+
55
+	protected $_activate_meta_box_type;
56
+
57
+	protected $_current_message_meta_box;
58
+
59
+	protected $_current_message_meta_box_object;
60
+
61
+	protected $_context_switcher;
62
+
63
+	protected $_shortcodes           = [];
64
+
65
+	protected $_active_messengers    = [];
66
+
67
+	protected $_active_message_types = [];
68
+
69
+	/**
70
+	 * @var EE_Message_Template_Group $_message_template_group
71
+	 */
72
+	protected $_message_template_group;
73
+
74
+	protected $_m_mt_settings = [];
75
+
76
+
77
+	/**
78
+	 * This is set via the _set_message_template_group method and holds whatever the template pack for the group is.
79
+	 * IF there is no group then it gets automatically set to the Default template pack.
80
+	 *
81
+	 * @since 4.5.0
82
+	 *
83
+	 * @var EE_Messages_Template_Pack
84
+	 */
85
+	protected $_template_pack;
86
+
87
+
88
+	/**
89
+	 * This is set via the _set_message_template_group method and holds whatever the template pack variation for the
90
+	 * group is.  If there is no group then it automatically gets set to default.
91
+	 *
92
+	 * @since 4.5.0
93
+	 *
94
+	 * @var string
95
+	 */
96
+	protected $_variation;
97
+
98
+
99
+	/**
100
+	 * @param bool $routing
101
+	 * @throws EE_Error
102
+	 * @throws ReflectionException
103
+	 */
104
+	public function __construct($routing = true)
105
+	{
106
+		// make sure messages autoloader is running
107
+		EED_Messages::set_autoloaders();
108
+		parent::__construct($routing);
109
+	}
110
+
111
+
112
+	/**
113
+	 * @return EEM_Message
114
+	 * @throws EE_Error
115
+	 */
116
+	public function getMsgModel()
117
+	{
118
+		if (! $this->MSG_MODEL instanceof EEM_Message) {
119
+			$this->MSG_MODEL = EEM_Message::instance();
120
+		}
121
+		return $this->MSG_MODEL;
122
+	}
123
+
124
+
125
+	/**
126
+	 * @return EEM_Message_Template
127
+	 * @throws EE_Error
128
+	 */
129
+	public function getMtpModel()
130
+	{
131
+		if (! $this->MTP_MODEL instanceof EEM_Message_Template) {
132
+			$this->MTP_MODEL = EEM_Message_Template::instance();
133
+		}
134
+		return $this->MTP_MODEL;
135
+	}
136
+
137
+
138
+	/**
139
+	 * @return EEM_Message_Template_Group
140
+	 * @throws EE_Error
141
+	 */
142
+	public function getMtgModel()
143
+	{
144
+		if (! $this->MTG_MODEL instanceof EEM_Message_Template_Group) {
145
+			$this->MTG_MODEL = EEM_Message_Template_Group::instance();
146
+		}
147
+		return $this->MTG_MODEL;
148
+	}
149
+
150
+
151
+	/**
152
+	 * @throws EE_Error
153
+	 * @throws ReflectionException
154
+	 */
155
+	protected function _init_page_props()
156
+	{
157
+		$this->page_slug        = EE_MSG_PG_SLUG;
158
+		$this->page_label       = esc_html__('Messages Settings', 'event_espresso');
159
+		$this->_admin_base_url  = EE_MSG_ADMIN_URL;
160
+		$this->_admin_base_path = EE_MSG_ADMIN;
161
+
162
+		$messenger    = $this->request->getRequestParam('messenger', '');
163
+		$message_type = $this->request->getRequestParam('message_type', '');
164
+		$this->_active_messenger_name    = $this->request->getRequestParam('MTP_messenger', $messenger);
165
+		$this->_active_message_type_name = $this->request->getRequestParam('MTP_message_type', $message_type);
166
+
167
+		$this->_load_message_resource_manager();
168
+	}
169
+
170
+
171
+	/**
172
+	 * loads messenger objects into the $_active_messengers property (so we can access the needed methods)
173
+	 *
174
+	 * @throws EE_Error
175
+	 * @throws InvalidDataTypeException
176
+	 * @throws InvalidInterfaceException
177
+	 * @throws InvalidArgumentException
178
+	 * @throws ReflectionException
179
+	 */
180
+	protected function _load_message_resource_manager()
181
+	{
182
+		$this->_message_resource_manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
183
+	}
184
+
185
+
186
+	/**
187
+	 * @return array
188
+	 * @throws EE_Error
189
+	 * @throws InvalidArgumentException
190
+	 * @throws InvalidDataTypeException
191
+	 * @throws InvalidInterfaceException
192
+	 * @deprecated 4.9.9.rc.014
193
+	 */
194
+	public function get_messengers_for_list_table()
195
+	{
196
+		EE_Error::doing_it_wrong(
197
+			__METHOD__,
198
+			sprintf(
199
+				esc_html__(
200
+					'This method is no longer in use.  There is no replacement for it. The method was used to generate a set of values for use in creating a messenger filter dropdown which is now generated differently via %s',
201
+					'event_espresso'
202
+				),
203
+				'Messages_Admin_Page::get_messengers_select_input()'
204
+			),
205
+			'4.9.9.rc.014'
206
+		);
207
+
208
+		$m_values          = [];
209
+		$active_messengers = $this->getMsgModel()->get_all(['group_by' => 'MSG_messenger']);
210
+		// setup messengers for selects
211
+		$i = 1;
212
+		foreach ($active_messengers as $active_messenger) {
213
+			if ($active_messenger instanceof EE_Message) {
214
+				$m_values[ $i ]['id']   = $active_messenger->messenger();
215
+				$m_values[ $i ]['text'] = ucwords($active_messenger->messenger_label());
216
+				$i++;
217
+			}
218
+		}
219
+
220
+		return $m_values;
221
+	}
222
+
223
+
224
+	/**
225
+	 * @return array
226
+	 * @throws EE_Error
227
+	 * @throws InvalidArgumentException
228
+	 * @throws InvalidDataTypeException
229
+	 * @throws InvalidInterfaceException
230
+	 * @deprecated 4.9.9.rc.014
231
+	 */
232
+	public function get_message_types_for_list_table()
233
+	{
234
+		EE_Error::doing_it_wrong(
235
+			__METHOD__,
236
+			sprintf(
237
+				esc_html__(
238
+					'This method is no longer in use.  There is no replacement for it. The method was used to generate a set of values for use in creating a message type filter dropdown which is now generated differently via %s',
239
+					'event_espresso'
240
+				),
241
+				'Messages_Admin_Page::get_message_types_select_input()'
242
+			),
243
+			'4.9.9.rc.014'
244
+		);
245
+
246
+		$mt_values       = [];
247
+		$active_messages = $this->getMsgModel()->get_all(['group_by' => 'MSG_message_type']);
248
+		$i               = 1;
249
+		foreach ($active_messages as $active_message) {
250
+			if ($active_message instanceof EE_Message) {
251
+				$mt_values[ $i ]['id']   = $active_message->message_type();
252
+				$mt_values[ $i ]['text'] = ucwords($active_message->message_type_label());
253
+				$i++;
254
+			}
255
+		}
256
+
257
+		return $mt_values;
258
+	}
259
+
260
+
261
+	/**
262
+	 * @return array
263
+	 * @throws EE_Error
264
+	 * @throws InvalidArgumentException
265
+	 * @throws InvalidDataTypeException
266
+	 * @throws InvalidInterfaceException
267
+	 * @deprecated 4.9.9.rc.014
268
+	 */
269
+	public function get_contexts_for_message_types_for_list_table()
270
+	{
271
+		EE_Error::doing_it_wrong(
272
+			__METHOD__,
273
+			sprintf(
274
+				esc_html__(
275
+					'This method is no longer in use.  There is no replacement for it. The method was used to generate a set of values for use in creating a message type context filter dropdown which is now generated differently via %s',
276
+					'event_espresso'
277
+				),
278
+				'Messages_Admin_Page::get_contexts_for_message_types_select_input()'
279
+			),
280
+			'4.9.9.rc.014'
281
+		);
282
+
283
+		$contexts                = [];
284
+		$active_message_contexts = $this->getMsgModel()->get_all(['group_by' => 'MSG_context']);
285
+		foreach ($active_message_contexts as $active_message) {
286
+			if ($active_message instanceof EE_Message) {
287
+				$message_type = $active_message->message_type_object();
288
+				if ($message_type instanceof EE_message_type) {
289
+					$message_type_contexts = $message_type->get_contexts();
290
+					foreach ($message_type_contexts as $context => $context_details) {
291
+						$contexts[ $context ] = $context_details['label'];
292
+					}
293
+				}
294
+			}
295
+		}
296
+
297
+		return $contexts;
298
+	}
299
+
300
+
301
+	/**
302
+	 * Generate select input with provided messenger options array.
303
+	 *
304
+	 * @param array $messenger_options Array of messengers indexed by messenger slug and values are the messenger
305
+	 *                                 labels.
306
+	 * @return string
307
+	 * @throws EE_Error
308
+	 */
309
+	public function get_messengers_select_input($messenger_options)
310
+	{
311
+		// if empty or just one value then just return an empty string
312
+		if (
313
+			empty($messenger_options)
314
+			|| ! is_array($messenger_options)
315
+			|| count($messenger_options) === 1
316
+		) {
317
+			return '';
318
+		}
319
+		// merge in default
320
+		$messenger_options = array_merge(
321
+			['none_selected' => esc_html__('Show All Messengers', 'event_espresso')],
322
+			$messenger_options
323
+		);
324
+		$input             = new EE_Select_Input(
325
+			$messenger_options,
326
+			[
327
+				'html_name'  => 'ee_messenger_filter_by',
328
+				'html_id'    => 'ee_messenger_filter_by',
329
+				'html_class' => 'wide',
330
+				'default'    => $this->request->getRequestParam('ee_messenger_filter_by', 'none_selected', 'title'),
331
+			]
332
+		);
333
+
334
+		return $input->get_html_for_input();
335
+	}
336
+
337
+
338
+	/**
339
+	 * Generate select input with provided message type options array.
340
+	 *
341
+	 * @param array $message_type_options Array of message types indexed by message type slug, and values are the
342
+	 *                                    message type labels
343
+	 * @return string
344
+	 * @throws EE_Error
345
+	 */
346
+	public function get_message_types_select_input($message_type_options)
347
+	{
348
+		// if empty or count of options is 1 then just return an empty string
349
+		if (
350
+			empty($message_type_options)
351
+			|| ! is_array($message_type_options)
352
+			|| count($message_type_options) === 1
353
+		) {
354
+			return '';
355
+		}
356
+		// merge in default
357
+		$message_type_options = array_merge(
358
+			['none_selected' => esc_html__('Show All Message Types', 'event_espresso')],
359
+			$message_type_options
360
+		);
361
+		$input                = new EE_Select_Input(
362
+			$message_type_options,
363
+			[
364
+				'html_name'  => 'ee_message_type_filter_by',
365
+				'html_id'    => 'ee_message_type_filter_by',
366
+				'html_class' => 'wide',
367
+				'default'    => $this->request->getRequestParam('ee_message_type_filter_by', 'none_selected', 'title'),
368
+			]
369
+		);
370
+
371
+		return $input->get_html_for_input();
372
+	}
373
+
374
+
375
+	/**
376
+	 * Generate select input with provide message type contexts array.
377
+	 *
378
+	 * @param array $context_options Array of message type contexts indexed by context slug, and values are the
379
+	 *                               context label.
380
+	 * @return string
381
+	 * @throws EE_Error
382
+	 */
383
+	public function get_contexts_for_message_types_select_input($context_options)
384
+	{
385
+		// if empty or count of options is one then just return empty string
386
+		if (
387
+			empty($context_options)
388
+			|| ! is_array($context_options)
389
+			|| count($context_options) === 1
390
+		) {
391
+			return '';
392
+		}
393
+		// merge in default
394
+		$context_options = array_merge(
395
+			['none_selected' => esc_html__('Show all Contexts', 'event_espresso')],
396
+			$context_options
397
+		);
398
+		$input           = new EE_Select_Input(
399
+			$context_options,
400
+			[
401
+				'html_name'  => 'ee_context_filter_by',
402
+				'html_id'    => 'ee_context_filter_by',
403
+				'html_class' => 'wide',
404
+				'default'    => $this->request->getRequestParam('ee_context_filter_by', 'none_selected', 'title'),
405
+			]
406
+		);
407
+
408
+		return $input->get_html_for_input();
409
+	}
410
+
411
+
412
+	protected function _ajax_hooks()
413
+	{
414
+		add_action('wp_ajax_activate_messenger', [$this, 'activate_messenger_toggle']);
415
+		add_action('wp_ajax_activate_mt', [$this, 'activate_mt_toggle']);
416
+		add_action('wp_ajax_ee_msgs_save_settings', [$this, 'save_settings']);
417
+		add_action('wp_ajax_ee_msgs_update_mt_form', [$this, 'update_mt_form']);
418
+		add_action('wp_ajax_switch_template_pack', [$this, 'switch_template_pack']);
419
+		add_action('wp_ajax_toggle_context_template', [$this, 'toggle_context_template']);
420
+	}
421
+
422
+
423
+	protected function _define_page_props()
424
+	{
425
+		$this->_admin_page_title = $this->page_label;
426
+		$this->_labels           = [
427
+			'buttons'    => [
428
+				'add'    => esc_html__('Add New Message Template', 'event_espresso'),
429
+				'edit'   => esc_html__('Edit Message Template', 'event_espresso'),
430
+				'delete' => esc_html__('Delete Message Template', 'event_espresso'),
431
+			],
432
+			'publishbox' => esc_html__('Update Actions', 'event_espresso'),
433
+		];
434
+	}
435
+
436
+
437
+	/**
438
+	 *        an array for storing key => value pairs of request actions and their corresponding methods
439
+	 *
440
+	 * @access protected
441
+	 * @return void
442
+	 */
443
+	protected function _set_page_routes()
444
+	{
445
+		$GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
446
+		$GRP_ID = $this->request->getRequestParam('id', $GRP_ID, 'int');
447
+		$MSG_ID = $this->request->getRequestParam('MSG_ID', 0, 'int');
448
+
449
+		$this->_page_routes = [
450
+			'default'                          => [
451
+				'func'       => '_message_queue_list_table',
452
+				'capability' => 'ee_read_global_messages',
453
+			],
454
+			'global_mtps'                      => [
455
+				'func'       => '_ee_default_messages_overview_list_table',
456
+				'capability' => 'ee_read_global_messages',
457
+			],
458
+			'custom_mtps'                      => [
459
+				'func'       => '_custom_mtps_preview',
460
+				'capability' => 'ee_read_messages',
461
+			],
462
+			'add_new_message_template'         => [
463
+				'func'       => 'add_message_template',
464
+				'capability' => 'ee_edit_messages',
465
+				'noheader'   => true,
466
+			],
467
+			'edit_message_template'            => [
468
+				'func'       => '_edit_message_template',
469
+				'capability' => 'ee_edit_message',
470
+				'obj_id'     => $GRP_ID,
471
+			],
472
+			'preview_message'                  => [
473
+				'func'               => '_preview_message',
474
+				'capability'         => 'ee_read_message',
475
+				'obj_id'             => $GRP_ID,
476
+				'noheader'           => true,
477
+				'headers_sent_route' => 'display_preview_message',
478
+			],
479
+			'display_preview_message'          => [
480
+				'func'       => '_display_preview_message',
481
+				'capability' => 'ee_read_message',
482
+				'obj_id'     => $GRP_ID,
483
+			],
484
+			'insert_message_template'          => [
485
+				'func'       => '_insert_or_update_message_template',
486
+				'capability' => 'ee_edit_messages',
487
+				'args'       => ['new' => true],
488
+				'noheader'   => true,
489
+			],
490
+			'update_message_template'          => [
491
+				'func'       => '_insert_or_update_message_template',
492
+				'capability' => 'ee_edit_message',
493
+				'obj_id'     => $GRP_ID,
494
+				'args'       => ['new' => false],
495
+				'noheader'   => true,
496
+			],
497
+			'trash_message_template'           => [
498
+				'func'       => '_trash_or_restore_message_template',
499
+				'capability' => 'ee_delete_message',
500
+				'obj_id'     => $GRP_ID,
501
+				'args'       => ['trash' => true, 'all' => true],
502
+				'noheader'   => true,
503
+			],
504
+			'trash_message_template_context'   => [
505
+				'func'       => '_trash_or_restore_message_template',
506
+				'capability' => 'ee_delete_message',
507
+				'obj_id'     => $GRP_ID,
508
+				'args'       => ['trash' => true],
509
+				'noheader'   => true,
510
+			],
511
+			'restore_message_template'         => [
512
+				'func'       => '_trash_or_restore_message_template',
513
+				'capability' => 'ee_delete_message',
514
+				'obj_id'     => $GRP_ID,
515
+				'args'       => ['trash' => false, 'all' => true],
516
+				'noheader'   => true,
517
+			],
518
+			'restore_message_template_context' => [
519
+				'func'       => '_trash_or_restore_message_template',
520
+				'capability' => 'ee_delete_message',
521
+				'obj_id'     => $GRP_ID,
522
+				'args'       => ['trash' => false],
523
+				'noheader'   => true,
524
+			],
525
+			'delete_message_template'          => [
526
+				'func'       => '_delete_message_template',
527
+				'capability' => 'ee_delete_message',
528
+				'obj_id'     => $GRP_ID,
529
+				'noheader'   => true,
530
+			],
531
+			'reset_to_default'                 => [
532
+				'func'       => '_reset_to_default_template',
533
+				'capability' => 'ee_edit_message',
534
+				'obj_id'     => $GRP_ID,
535
+				'noheader'   => true,
536
+			],
537
+			'settings'                         => [
538
+				'func'       => '_settings',
539
+				'capability' => 'manage_options',
540
+			],
541
+			'update_global_settings'           => [
542
+				'func'       => '_update_global_settings',
543
+				'capability' => 'manage_options',
544
+				'noheader'   => true,
545
+			],
546
+			'generate_now'                     => [
547
+				'func'       => '_generate_now',
548
+				'capability' => 'ee_send_message',
549
+				'noheader'   => true,
550
+			],
551
+			'generate_and_send_now'            => [
552
+				'func'       => '_generate_and_send_now',
553
+				'capability' => 'ee_send_message',
554
+				'noheader'   => true,
555
+			],
556
+			'queue_for_resending'              => [
557
+				'func'       => '_queue_for_resending',
558
+				'capability' => 'ee_send_message',
559
+				'noheader'   => true,
560
+			],
561
+			'send_now'                         => [
562
+				'func'       => '_send_now',
563
+				'capability' => 'ee_send_message',
564
+				'noheader'   => true,
565
+			],
566
+			'delete_ee_message'                => [
567
+				'func'       => '_delete_ee_messages',
568
+				'capability' => 'ee_delete_messages',
569
+				'noheader'   => true,
570
+			],
571
+			'delete_ee_messages'               => [
572
+				'func'       => '_delete_ee_messages',
573
+				'capability' => 'ee_delete_messages',
574
+				'noheader'   => true,
575
+				'obj_id'     => $MSG_ID,
576
+			],
577
+		];
578
+	}
579
+
580
+
581
+	protected function _set_page_config()
582
+	{
583
+		$this->_page_config = [
584
+			'default'                  => [
585
+				'nav'           => [
586
+					'label' => esc_html__('Message Activity', 'event_espresso'),
587
+					'icon' => 'dashicons-email',
588
+					'order' => 10,
589
+				],
590
+				'list_table'    => 'EE_Message_List_Table',
591
+				// 'qtips' => array( 'EE_Message_List_Table_Tips' ),
592
+				'require_nonce' => false,
593
+			],
594
+			'global_mtps'              => [
595
+				'nav'           => [
596
+					'label' => esc_html__('Default Message Templates', 'event_espresso'),
597
+					'icon' => 'dashicons-layout',
598
+					'order' => 20,
599
+				],
600
+				'list_table'    => 'Messages_Template_List_Table',
601
+				'help_tabs'     => [
602
+					'messages_overview_help_tab'                                => [
603
+						'title'    => esc_html__('Messages Overview', 'event_espresso'),
604
+						'filename' => 'messages_overview',
605
+					],
606
+					'messages_overview_messages_table_column_headings_help_tab' => [
607
+						'title'    => esc_html__('Messages Table Column Headings', 'event_espresso'),
608
+						'filename' => 'messages_overview_table_column_headings',
609
+					],
610
+					'messages_overview_messages_filters_help_tab'               => [
611
+						'title'    => esc_html__('Message Filters', 'event_espresso'),
612
+						'filename' => 'messages_overview_filters',
613
+					],
614
+					'messages_overview_messages_views_help_tab'                 => [
615
+						'title'    => esc_html__('Message Views', 'event_espresso'),
616
+						'filename' => 'messages_overview_views',
617
+					],
618
+					'message_overview_message_types_help_tab'                   => [
619
+						'title'    => esc_html__('Message Types', 'event_espresso'),
620
+						'filename' => 'messages_overview_types',
621
+					],
622
+					'messages_overview_messengers_help_tab'                     => [
623
+						'title'    => esc_html__('Messengers', 'event_espresso'),
624
+						'filename' => 'messages_overview_messengers',
625
+					],
626
+				],
627
+				'require_nonce' => false,
628
+			],
629
+			'custom_mtps'              => [
630
+				'nav'           => [
631
+					'label' => esc_html__('Custom Message Templates', 'event_espresso'),
632
+					'icon' => 'dashicons-admin-customizer',
633
+					'order' => 30,
634
+				],
635
+				'help_tabs'     => [],
636
+				'require_nonce' => false,
637
+			],
638
+			'add_new_message_template' => [
639
+				'nav'           => [
640
+					'label'      => esc_html__('Add New Message Templates', 'event_espresso'),
641
+					'icon' => 'dashicons-plus-alt',
642
+					'order'      => 5,
643
+					'persistent' => false,
644
+				],
645
+				'require_nonce' => false,
646
+			],
647
+			'edit_message_template'    => [
648
+				'labels'        => [
649
+					'buttons'    => [
650
+						'reset' => esc_html__('Reset Templates', 'event_espresso'),
651
+					],
652
+					'publishbox' => esc_html__('Update Actions', 'event_espresso'),
653
+				],
654
+				'nav'           => [
655
+					'label'      => esc_html__('Edit Message Templates', 'event_espresso'),
656
+					'icon' => 'dashicons-edit-large',
657
+					'order'      => 5,
658
+					'persistent' => false,
659
+					'url'        => '',
660
+				],
661
+				'metaboxes'     => ['_publish_post_box', '_register_edit_meta_boxes'],
662
+				'has_metaboxes' => true,
663
+				'help_tabs'     => [
664
+					'edit_message_template'            => [
665
+						'title'    => esc_html__('Message Template Editor', 'event_espresso'),
666
+						'callback' => 'edit_message_template_help_tab',
667
+					],
668
+					'message_templates_help_tab'       => [
669
+						'title'    => esc_html__('Message Templates', 'event_espresso'),
670
+						'filename' => 'messages_templates',
671
+					],
672
+					'message_template_shortcodes'      => [
673
+						'title'    => esc_html__('Message Shortcodes', 'event_espresso'),
674
+						'callback' => 'message_template_shortcodes_help_tab',
675
+					],
676
+					'message_preview_help_tab'         => [
677
+						'title'    => esc_html__('Message Preview', 'event_espresso'),
678
+						'filename' => 'messages_preview',
679
+					],
680
+					'messages_overview_other_help_tab' => [
681
+						'title'    => esc_html__('Messages Other', 'event_espresso'),
682
+						'filename' => 'messages_overview_other',
683
+					],
684
+				],
685
+				'require_nonce' => false,
686
+			],
687
+			'display_preview_message'  => [
688
+				'nav'           => [
689
+					'label'      => esc_html__('Message Preview', 'event_espresso'),
690
+					'icon' => 'dashicons-visibility-bar',
691
+					'order'      => 5,
692
+					'url'        => '',
693
+					'persistent' => false,
694
+				],
695
+				'help_tabs'     => [
696
+					'preview_message' => [
697
+						'title'    => esc_html__('About Previews', 'event_espresso'),
698
+						'callback' => 'preview_message_help_tab',
699
+					],
700
+				],
701
+				'require_nonce' => false,
702
+			],
703
+			'settings'                 => [
704
+				'nav'           => [
705
+					'label' => esc_html__('Settings', 'event_espresso'),
706
+					'icon' => 'dashicons-admin-generic',
707
+					'order' => 40,
708
+				],
709
+				'metaboxes'     => ['_messages_settings_metaboxes'],
710
+				'help_tabs'     => [
711
+					'messages_settings_help_tab'               => [
712
+						'title'    => esc_html__('Messages Settings', 'event_espresso'),
713
+						'filename' => 'messages_settings',
714
+					],
715
+					'messages_settings_message_types_help_tab' => [
716
+						'title'    => esc_html__('Activating / Deactivating Message Types', 'event_espresso'),
717
+						'filename' => 'messages_settings_message_types',
718
+					],
719
+					'messages_settings_messengers_help_tab'    => [
720
+						'title'    => esc_html__('Activating / Deactivating Messengers', 'event_espresso'),
721
+						'filename' => 'messages_settings_messengers',
722
+					],
723
+				],
724
+				'require_nonce' => false,
725
+			],
726
+		];
727
+	}
728
+
729
+
730
+	protected function _add_screen_options()
731
+	{
732
+		// todo
733
+	}
734
+
735
+
736
+	protected function _add_screen_options_global_mtps()
737
+	{
738
+		/**
739
+		 * Note: the reason for the value swap here on $this->_admin_page_title is because $this->_per_page_screen_options
740
+		 * uses the $_admin_page_title property and we want different outputs in the different spots.
741
+		 */
742
+		$page_title              = $this->_admin_page_title;
743
+		$this->_admin_page_title = esc_html__('Global Message Templates', 'event_espresso');
744
+		$this->_per_page_screen_option();
745
+		$this->_admin_page_title = $page_title;
746
+	}
747
+
748
+
749
+	protected function _add_screen_options_default()
750
+	{
751
+		$this->_admin_page_title = esc_html__('Message Activity', 'event_espresso');
752
+		$this->_per_page_screen_option();
753
+	}
754
+
755
+
756
+	// none of the below group are currently used for Messages
757
+	protected function _add_feature_pointers()
758
+	{
759
+	}
760
+
761
+
762
+	public function admin_init()
763
+	{
764
+	}
765
+
766
+
767
+	public function admin_notices()
768
+	{
769
+	}
770
+
771
+
772
+	public function admin_footer_scripts()
773
+	{
774
+	}
775
+
776
+
777
+	public function messages_help_tab()
778
+	{
779
+		EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_help_tab.template.php');
780
+	}
781
+
782
+
783
+	public function messengers_help_tab()
784
+	{
785
+		EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messenger_help_tab.template.php');
786
+	}
787
+
788
+
789
+	public function message_types_help_tab()
790
+	{
791
+		EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_message_type_help_tab.template.php');
792
+	}
793
+
794
+
795
+	public function messages_overview_help_tab()
796
+	{
797
+		EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_overview_help_tab.template.php');
798
+	}
799
+
800
+
801
+	public function message_templates_help_tab()
802
+	{
803
+		EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_message_templates_help_tab.template.php');
804
+	}
805
+
806
+
807
+	public function edit_message_template_help_tab()
808
+	{
809
+		$args['img1'] = '<img src="' . EE_MSG_ASSETS_URL . 'images/editor.png' . '" alt="'
810
+						. esc_attr__('Editor Title', 'event_espresso')
811
+						. '" />';
812
+		$args['img2'] = '<img src="' . EE_MSG_ASSETS_URL . 'images/switch-context.png' . '" alt="'
813
+						. esc_attr__('Context Switcher and Preview', 'event_espresso')
814
+						. '" />';
815
+		$args['img3'] = '<img class="left" src="' . EE_MSG_ASSETS_URL . 'images/form-fields.png' . '" alt="'
816
+						. esc_attr__('Message Template Form Fields', 'event_espresso')
817
+						. '" />';
818
+		$args['img4'] = '<img class="right" src="' . EE_MSG_ASSETS_URL . 'images/shortcodes-metabox.png' . '" alt="'
819
+						. esc_attr__('Shortcodes Metabox', 'event_espresso')
820
+						. '" />';
821
+		$args['img5'] = '<img class="right" src="' . EE_MSG_ASSETS_URL . 'images/publish-meta-box.png' . '" alt="'
822
+						. esc_attr__('Publish Metabox', 'event_espresso')
823
+						. '" />';
824
+		EEH_Template::display_template(
825
+			EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_templates_editor_help_tab.template.php',
826
+			$args
827
+		);
828
+	}
829
+
830
+
831
+	/**
832
+	 * @throws ReflectionException
833
+	 * @throws EE_Error
834
+	 */
835
+	public function message_template_shortcodes_help_tab()
836
+	{
837
+		$this->_set_shortcodes();
838
+		$args['shortcodes'] = $this->_shortcodes;
839
+		EEH_Template::display_template(
840
+			EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_shortcodes_help_tab.template.php',
841
+			$args
842
+		);
843
+	}
844
+
845
+
846
+	public function preview_message_help_tab()
847
+	{
848
+		EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_preview_help_tab.template.php');
849
+	}
850
+
851
+
852
+	public function settings_help_tab()
853
+	{
854
+		$args['img1'] = '<img class="inline-text" src="' . EE_MSG_ASSETS_URL . 'images/email-tab-active.png'
855
+						. '" alt="' . esc_attr__('Active Email Tab', 'event_espresso') . '" />';
856
+		$args['img2'] = '<img class="inline-text" src="' . EE_MSG_ASSETS_URL . 'images/email-tab-inactive.png'
857
+						. '" alt="' . esc_attr__('Inactive Email Tab', 'event_espresso') . '" />';
858
+		$args['img3'] = '<div class="ee-switch">'
859
+						. '<input class="ee-switch__input" id="ee-on-off-toggle-on" type="checkbox" checked>'
860
+						. '<label class="ee-switch__toggle" for="ee-on-off-toggle-on"></label>'
861
+						. '</div>';
862
+		$args['img4'] = '<div class="switch">'
863
+						. '<input class="ee-switch__input" id="ee-on-off-toggle-off" type="checkbox">'
864
+						. '<label class="ee-switch__toggle" for="ee-on-off-toggle-off"></label>'
865
+						. '</div>';
866
+		EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_settings_help_tab.template.php', $args);
867
+	}
868
+
869
+
870
+	public function load_scripts_styles()
871
+	{
872
+		wp_register_style('espresso_ee_msg', EE_MSG_ASSETS_URL . 'ee_message_admin.css', EVENT_ESPRESSO_VERSION);
873
+		wp_enqueue_style('espresso_ee_msg');
874
+
875
+		wp_register_script(
876
+			'ee-messages-settings',
877
+			EE_MSG_ASSETS_URL . 'ee-messages-settings.js',
878
+			['jquery-ui-droppable', 'ee-serialize-full-array'],
879
+			EVENT_ESPRESSO_VERSION,
880
+			true
881
+		);
882
+		wp_register_script(
883
+			'ee-msg-list-table-js',
884
+			EE_MSG_ASSETS_URL . 'ee_message_admin_list_table.js',
885
+			['ee-dialog'],
886
+			EVENT_ESPRESSO_VERSION
887
+		);
888
+	}
889
+
890
+
891
+	public function load_scripts_styles_default()
892
+	{
893
+		wp_enqueue_script('ee-msg-list-table-js');
894
+	}
895
+
896
+
897
+	public function wp_editor_css($mce_css)
898
+	{
899
+		// if we're on the edit_message_template route
900
+		if ($this->_req_action === 'edit_message_template' && $this->_active_messenger instanceof EE_messenger) {
901
+			$message_type_name = $this->_active_message_type_name;
902
+
903
+			// we're going to REPLACE the existing mce css
904
+			// we need to get the css file location from the active messenger
905
+			$mce_css = $this->_active_messenger->get_variation(
906
+				$this->_template_pack,
907
+				$message_type_name,
908
+				true,
909
+				'wpeditor',
910
+				$this->_variation
911
+			);
912
+		}
913
+
914
+		return $mce_css;
915
+	}
916
+
917
+
918
+	/**
919
+	 * @throws EE_Error
920
+	 * @throws ReflectionException
921
+	 */
922
+	public function load_scripts_styles_edit_message_template()
923
+	{
924
+
925
+		$this->_set_shortcodes();
926
+
927
+		EE_Registry::$i18n_js_strings['confirm_default_reset']        = sprintf(
928
+			esc_html__(
929
+				'Are you sure you want to reset the %s %s message templates?  Remember continuing will reset the templates for all contexts in this messenger and message type group.',
930
+				'event_espresso'
931
+			),
932
+			$this->_message_template_group->messenger_obj()->label['singular'],
933
+			$this->_message_template_group->message_type_obj()->label['singular']
934
+		);
935
+		EE_Registry::$i18n_js_strings['confirm_switch_template_pack'] = esc_html__(
936
+			'Switching the template pack for a messages template will reset the content for the template so the new layout is loaded.  Any custom content in the existing template will be lost. Are you sure you wish to do this?',
937
+			'event_espresso'
938
+		);
939
+		EE_Registry::$i18n_js_strings['server_error']                 = esc_html__(
940
+			'An unknown error occurred on the server while attempting to process your request. Please refresh the page and try again or contact support.',
941
+			'event_espresso'
942
+		);
943
+
944
+		wp_register_script(
945
+			'ee_msgs_edit_js',
946
+			EE_MSG_ASSETS_URL . 'ee_message_editor.js',
947
+			['jquery'],
948
+			EVENT_ESPRESSO_VERSION
949
+		);
950
+
951
+		wp_enqueue_script('ee_admin_js');
952
+		wp_enqueue_script('ee_msgs_edit_js');
953
+
954
+		// add in special css for tiny_mce
955
+		add_filter('mce_css', [$this, 'wp_editor_css']);
956
+	}
957
+
958
+
959
+	/**
960
+	 * @throws EE_Error
961
+	 * @throws ReflectionException
962
+	 */
963
+	public function load_scripts_styles_display_preview_message()
964
+	{
965
+		$this->_set_message_template_group();
966
+		if ($this->_active_messenger_name) {
967
+			$this->_active_messenger = $this->_message_resource_manager->get_active_messenger(
968
+				$this->_active_messenger_name
969
+			);
970
+		}
971
+
972
+		wp_enqueue_style(
973
+			'espresso_preview_css',
974
+			$this->_active_messenger->get_variation(
975
+				$this->_template_pack,
976
+				$this->_active_message_type_name,
977
+				true,
978
+				'preview',
979
+				$this->_variation
980
+			)
981
+		);
982
+	}
983
+
984
+
985
+	public function load_scripts_styles_settings()
986
+	{
987
+		wp_register_style(
988
+			'ee-message-settings',
989
+			EE_MSG_ASSETS_URL . 'ee_message_settings.css',
990
+			[],
991
+			EVENT_ESPRESSO_VERSION
992
+		);
993
+		wp_enqueue_style('ee-text-links');
994
+		wp_enqueue_style('ee-message-settings');
995
+		wp_enqueue_script('ee-messages-settings');
996
+	}
997
+
998
+
999
+	/**
1000
+	 * set views array for List Table
1001
+	 */
1002
+	public function _set_list_table_views_global_mtps()
1003
+	{
1004
+		$this->_views = [
1005
+			'in_use' => [
1006
+				'slug'  => 'in_use',
1007
+				'label' => esc_html__('In Use', 'event_espresso'),
1008
+				'count' => 0,
1009
+			],
1010
+		];
1011
+	}
1012
+
1013
+
1014
+	/**
1015
+	 * Set views array for the Custom Template List Table
1016
+	 */
1017
+	public function _set_list_table_views_custom_mtps()
1018
+	{
1019
+		$this->_set_list_table_views_global_mtps();
1020
+		$this->_views['in_use']['bulk_action'] = [
1021
+			'trash_message_template' => esc_html__('Move to Trash', 'event_espresso'),
1022
+		];
1023
+	}
1024
+
1025
+
1026
+	/**
1027
+	 * set views array for message queue list table
1028
+	 *
1029
+	 * @throws InvalidDataTypeException
1030
+	 * @throws InvalidInterfaceException
1031
+	 * @throws InvalidArgumentException
1032
+	 * @throws EE_Error
1033
+	 * @throws ReflectionException
1034
+	 */
1035
+	public function _set_list_table_views_default()
1036
+	{
1037
+		EE_Registry::instance()->load_helper('Template');
1038
+
1039
+		$common_bulk_actions = EE_Registry::instance()->CAP->current_user_can(
1040
+			'ee_send_message',
1041
+			'message_list_table_bulk_actions'
1042
+		)
1043
+			? [
1044
+				'generate_now'          => esc_html__('Generate Now', 'event_espresso'),
1045
+				'generate_and_send_now' => esc_html__('Generate and Send Now', 'event_espresso'),
1046
+				'queue_for_resending'   => esc_html__('Queue for Resending', 'event_espresso'),
1047
+				'send_now'              => esc_html__('Send Now', 'event_espresso'),
1048
+			]
1049
+			: [];
1050
+
1051
+		$delete_bulk_action = EE_Registry::instance()->CAP->current_user_can(
1052
+			'ee_delete_messages',
1053
+			'message_list_table_bulk_actions'
1054
+		)
1055
+			? ['delete_ee_messages' => esc_html__('Delete Messages', 'event_espresso')]
1056
+			: [];
1057
+
1058
+
1059
+		$this->_views = [
1060
+			'all' => [
1061
+				'slug'        => 'all',
1062
+				'label'       => esc_html__('All', 'event_espresso'),
1063
+				'count'       => 0,
1064
+				'bulk_action' => array_merge($common_bulk_actions, $delete_bulk_action),
1065
+			],
1066
+		];
1067
+
1068
+
1069
+		foreach ($this->getMsgModel()->all_statuses() as $status) {
1070
+			if ($status === EEM_Message::status_debug_only && ! EEM_Message::debug()) {
1071
+				continue;
1072
+			}
1073
+			$status_bulk_actions = $common_bulk_actions;
1074
+			// unset bulk actions not applying to status
1075
+			if (! empty($status_bulk_actions)) {
1076
+				switch ($status) {
1077
+					case EEM_Message::status_idle:
1078
+					case EEM_Message::status_resend:
1079
+						$status_bulk_actions['send_now'] = $common_bulk_actions['send_now'];
1080
+						break;
1081
+
1082
+					case EEM_Message::status_failed:
1083
+					case EEM_Message::status_debug_only:
1084
+					case EEM_Message::status_messenger_executing:
1085
+						$status_bulk_actions = [];
1086
+						break;
1087
+
1088
+					case EEM_Message::status_incomplete:
1089
+						unset($status_bulk_actions['queue_for_resending'], $status_bulk_actions['send_now']);
1090
+						break;
1091
+
1092
+					case EEM_Message::status_retry:
1093
+					case EEM_Message::status_sent:
1094
+						unset($status_bulk_actions['generate_now'], $status_bulk_actions['generate_and_send_now']);
1095
+						break;
1096
+				}
1097
+			}
1098
+
1099
+			// skip adding messenger executing status to views because it will be included with the Failed view.
1100
+			if ($status === EEM_Message::status_messenger_executing) {
1101
+				continue;
1102
+			}
1103
+
1104
+			$this->_views[ strtolower($status) ] = [
1105
+				'slug'        => strtolower($status),
1106
+				'label'       => EEH_Template::pretty_status($status, false, 'sentence'),
1107
+				'count'       => 0,
1108
+				'bulk_action' => array_merge($status_bulk_actions, $delete_bulk_action),
1109
+			];
1110
+		}
1111
+	}
1112
+
1113
+
1114
+	/**
1115
+	 * @throws EE_Error
1116
+	 */
1117
+	protected function _ee_default_messages_overview_list_table()
1118
+	{
1119
+		$this->_admin_page_title = esc_html__('Default Message Templates', 'event_espresso');
1120
+		$this->display_admin_list_table_page_with_no_sidebar();
1121
+	}
1122
+
1123
+
1124
+	/**
1125
+	 * @throws EE_Error
1126
+	 * @throws ReflectionException
1127
+	 */
1128
+	protected function _message_queue_list_table()
1129
+	{
1130
+		$this->_search_btn_label                   = esc_html__('Message Activity', 'event_espresso');
1131
+		$this->_template_args['per_column']        = 6;
1132
+		$this->_template_args['after_list_table']  = $this->_display_legend($this->_message_legend_items());
1133
+		$message_results = trim(EEM_Message::instance()->get_pretty_label_for_results());
1134
+		$this->_template_args['before_list_table'] = ! empty($message_results) ? "<h3>{$message_results}</h3>" : '';
1135
+		$this->display_admin_list_table_page_with_no_sidebar();
1136
+	}
1137
+
1138
+
1139
+	/**
1140
+	 * @throws EE_Error
1141
+	 */
1142
+	protected function _message_legend_items()
1143
+	{
1144
+
1145
+		$action_css_classes = EEH_MSG_Template::get_message_action_icons();
1146
+		$action_items       = [];
1147
+
1148
+		foreach ($action_css_classes as $action_item => $action_details) {
1149
+			if ($action_item === 'see_notifications_for') {
1150
+				continue;
1151
+			}
1152
+			$action_items[ $action_item ] = [
1153
+				'class' => $action_details['css_class'],
1154
+				'desc'  => $action_details['label'],
1155
+			];
1156
+		}
1157
+
1158
+		/** @var array $status_items status legend setup */
1159
+		$status_items = [
1160
+			'sent_status'                => [
1161
+				'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_sent,
1162
+				'desc'  => EEH_Template::pretty_status(EEM_Message::status_sent, false, 'sentence'),
1163
+			],
1164
+			'idle_status'                => [
1165
+				'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_idle,
1166
+				'desc'  => EEH_Template::pretty_status(EEM_Message::status_idle, false, 'sentence'),
1167
+			],
1168
+			'failed_status'              => [
1169
+				'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_failed,
1170
+				'desc'  => EEH_Template::pretty_status(EEM_Message::status_failed, false, 'sentence'),
1171
+			],
1172
+			'messenger_executing_status' => [
1173
+				'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_messenger_executing,
1174
+				'desc'  => EEH_Template::pretty_status(EEM_Message::status_messenger_executing, false, 'sentence'),
1175
+			],
1176
+			'resend_status'              => [
1177
+				'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_resend,
1178
+				'desc'  => EEH_Template::pretty_status(EEM_Message::status_resend, false, 'sentence'),
1179
+			],
1180
+			'incomplete_status'          => [
1181
+				'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_incomplete,
1182
+				'desc'  => EEH_Template::pretty_status(EEM_Message::status_incomplete, false, 'sentence'),
1183
+			],
1184
+			'retry_status'               => [
1185
+				'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_retry,
1186
+				'desc'  => EEH_Template::pretty_status(EEM_Message::status_retry, false, 'sentence'),
1187
+			],
1188
+		];
1189
+		if (EEM_Message::debug()) {
1190
+			$status_items['debug_only_status'] = [
1191
+				'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_debug_only,
1192
+				'desc'  => EEH_Template::pretty_status(EEM_Message::status_debug_only, false, 'sentence'),
1193
+			];
1194
+		}
1195
+
1196
+		return array_merge($action_items, $status_items);
1197
+	}
1198
+
1199
+
1200
+	/**
1201
+	 * @throws EE_Error
1202
+	 */
1203
+	protected function _custom_mtps_preview()
1204
+	{
1205
+		$this->_admin_page_title              = esc_html__('Custom Message Templates (Preview)', 'event_espresso');
1206
+		$this->_template_args['preview_img']  = '<img src="' . EE_MSG_ASSETS_URL . 'images/custom_mtps_preview.png"'
1207
+												. ' alt="' . esc_attr__(
1208
+													'Preview Custom Message Templates screenshot',
1209
+													'event_espresso'
1210
+												) . '" />';
1211
+		$this->_template_args['preview_text'] = '<strong>'
1212
+												. esc_html__(
1213
+													'Custom Message Templates is a feature that is only available in the premium version of Event Espresso 4 which is available with a support license purchase on EventEspresso.com. With the Custom Message Templates feature, you are able to create custom message templates and assign them on a per-event basis.',
1214
+													'event_espresso'
1215
+												)
1216
+												. '</strong>';
1217
+
1218
+		$this->display_admin_caf_preview_page('custom_message_types', false);
1219
+	}
1220
+
1221
+
1222
+	/**
1223
+	 * get_message_templates
1224
+	 * This gets all the message templates for listing on the overview list.
1225
+	 *
1226
+	 * @access public
1227
+	 * @param int    $per_page the amount of templates groups to show per page
1228
+	 * @param string $type     the current _view we're getting templates for
1229
+	 * @param bool   $count    return count?
1230
+	 * @param bool   $all      disregard any paging info (get all data);
1231
+	 * @param bool   $global   whether to return just global (true) or custom templates (false)
1232
+	 * @return array
1233
+	 * @throws EE_Error
1234
+	 * @throws InvalidArgumentException
1235
+	 * @throws InvalidDataTypeException
1236
+	 * @throws InvalidInterfaceException
1237
+	 */
1238
+	public function get_message_templates(
1239
+		$per_page = 10,
1240
+		$type = 'in_use',
1241
+		$count = false,
1242
+		$all = false,
1243
+		$global = true
1244
+	) {
1245
+		$orderby = $this->request->getRequestParam('orderby', 'GRP_ID');
1246
+		$this->request->setRequestParam('orderby', $orderby);
1247
+
1248
+		$order        = $this->request->getRequestParam('order', 'ASC');
1249
+		$current_page = $this->request->getRequestParam('paged', 1, 'int');
1250
+		$per_page     = $this->request->getRequestParam('perpage', $per_page, 'int');
1251
+
1252
+		$offset = ($current_page - 1) * $per_page;
1253
+		$limit  = $all ? null : [$offset, $per_page];
1254
+
1255
+		// options will match what is in the _views array property
1256
+		return $type === 'in_use'
1257
+			? $this->getMtgModel()->get_all_active_message_templates(
1258
+				$orderby,
1259
+				$order,
1260
+				$limit,
1261
+				$count,
1262
+				$global,
1263
+				true
1264
+			)
1265
+			: $this->getMtgModel()->get_all_trashed_grouped_message_templates(
1266
+				$orderby,
1267
+				$order,
1268
+				$limit,
1269
+				$count,
1270
+				$global
1271
+			);
1272
+	}
1273
+
1274
+
1275
+	/**
1276
+	 * filters etc might need a list of installed message_types
1277
+	 *
1278
+	 * @return array an array of message type objects
1279
+	 */
1280
+	public function get_installed_message_types()
1281
+	{
1282
+		$installed_message_types = $this->_message_resource_manager->installed_message_types();
1283
+		$installed               = [];
1284
+
1285
+		foreach ($installed_message_types as $message_type) {
1286
+			$installed[ $message_type->name ] = $message_type;
1287
+		}
1288
+
1289
+		return $installed;
1290
+	}
1291
+
1292
+
1293
+	/**
1294
+	 * This is used when creating a custom template. All Custom Templates start based off another template.
1295
+	 *
1296
+	 * @param string $message_type
1297
+	 * @param string $messenger
1298
+	 * @param string $GRP_ID
1299
+	 *
1300
+	 * @throws EE_error
1301
+	 * @throws ReflectionException
1302
+	 */
1303
+	public function add_message_template($message_type = '', $messenger = '', $GRP_ID = '')
1304
+	{
1305
+		// set values override any request data
1306
+		$message_type = ! empty($message_type) ? $message_type : $this->_active_message_type_name;
1307
+		$messenger    = ! empty($messenger) ? $messenger : $this->_active_messenger_name;
1308
+		$GRP_ID       = ! empty($GRP_ID) ? $GRP_ID : $this->request->getRequestParam('GRP_ID', 0, 'int');
1309
+
1310
+		// we need messenger and message type.  They should be coming from the event editor. If not here then return error
1311
+		if (empty($message_type) || empty($messenger)) {
1312
+			throw new EE_Error(
1313
+				esc_html__(
1314
+					'Sorry, but we can\'t create new templates because we\'re missing the messenger or message type',
1315
+					'event_espresso'
1316
+				)
1317
+			);
1318
+		}
1319
+
1320
+		// we need the GRP_ID for the template being used as the base for the new template
1321
+		if (empty($GRP_ID)) {
1322
+			throw new EE_Error(
1323
+				esc_html__(
1324
+					'In order to create a custom message template the GRP_ID of the template being used as a base is needed',
1325
+					'event_espresso'
1326
+				)
1327
+			);
1328
+		}
1329
+
1330
+		// let's just make sure the template gets generated!
1331
+
1332
+		// we need to reassign some variables for what the insert is expecting
1333
+		$this->request->setRequestParam('MTP_messenger', $messenger);
1334
+		$this->request->setRequestParam('MTP_message_type', $message_type);
1335
+		$this->request->setRequestParam('GRP_ID', $GRP_ID);
1336
+
1337
+		$this->_insert_or_update_message_template(true);
1338
+	}
1339
+
1340
+
1341
+	/**
1342
+	 * @param string $message_type     message type slug
1343
+	 * @param string $messenger        messenger slug
1344
+	 * @param int    $GRP_ID           GRP_ID for the related message template group this new template will be based
1345
+	 *                                 off of.
1346
+	 * @throws EE_error
1347
+	 * @throws ReflectionException
1348
+	 * @deprecated 4.10.29.p
1349
+	 */
1350
+	protected function _add_message_template($message_type, $messenger, $GRP_ID)
1351
+	{
1352
+		$this->add_message_template($message_type, $messenger, $GRP_ID);
1353
+	}
1354
+
1355
+
1356
+	/**
1357
+	 * _edit_message_template
1358
+	 *
1359
+	 * @access protected
1360
+	 * @return void
1361
+	 * @throws InvalidIdentifierException
1362
+	 * @throws DomainException
1363
+	 * @throws EE_Error
1364
+	 * @throws InvalidArgumentException
1365
+	 * @throws ReflectionException
1366
+	 * @throws InvalidDataTypeException
1367
+	 * @throws InvalidInterfaceException
1368
+	 */
1369
+	protected function _edit_message_template()
1370
+	{
1371
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1372
+		$template_fields = '';
1373
+		$sidebar_fields  = '';
1374
+		// we filter the tinyMCE settings to remove the validation since message templates by their nature will not have
1375
+		// valid html in the templates.
1376
+		add_filter('tiny_mce_before_init', [$this, 'filter_tinymce_init'], 10, 2);
1377
+
1378
+		$GRP_ID = $this->request->getRequestParam('id', 0, 'int');
1379
+		$EVT_ID = $this->request->getRequestParam('evt_id', 0, 'int');
1380
+
1381
+		$this->_set_shortcodes(); // this also sets the _message_template property.
1382
+		$message_template_group = $this->_message_template_group;
1383
+		$c_label                = $message_template_group->context_label();
1384
+		$c_config               = $message_template_group->contexts_config();
1385
+
1386
+		reset($c_config);
1387
+		$context = $this->request->getRequestParam('context', key($c_config));
1388
+		$context = strtolower($context);
1389
+
1390
+		$action = empty($GRP_ID) ? 'insert_message_template' : 'update_message_template';
1391
+
1392
+		$edit_message_template_form_url = add_query_arg(
1393
+			['action' => $action, 'noheader' => true],
1394
+			EE_MSG_ADMIN_URL
1395
+		);
1396
+
1397
+		// set active messenger for this view
1398
+		$this->_active_messenger         = $this->_message_resource_manager->get_active_messenger(
1399
+			$message_template_group->messenger()
1400
+		);
1401
+		$this->_active_message_type_name = $message_template_group->message_type();
1402
+
1403
+
1404
+		// Do we have any validation errors?
1405
+		$validators = $this->_get_transient();
1406
+		$v_fields   = ! empty($validators) ? array_keys($validators) : [];
1407
+
1408
+
1409
+		// we need to assemble the title from Various details
1410
+		$context_label = sprintf(
1411
+			esc_html__('(%s %s)', 'event_espresso'),
1412
+			$c_config[ $context ]['label'],
1413
+			ucwords($c_label['label'])
1414
+		);
1415
+
1416
+		$title = sprintf(
1417
+			esc_html__(' %s %s Template %s', 'event_espresso'),
1418
+			ucwords($message_template_group->messenger_obj()->label['singular']),
1419
+			ucwords($message_template_group->message_type_obj()->label['singular']),
1420
+			$context_label
1421
+		);
1422
+
1423
+		$this->_template_args['GRP_ID']           = $GRP_ID;
1424
+		$this->_template_args['message_template'] = $message_template_group;
1425
+		$this->_template_args['is_extra_fields']  = false;
1426
+
1427
+
1428
+		// let's get EEH_MSG_Template so we can get template form fields
1429
+		$template_field_structure = EEH_MSG_Template::get_fields(
1430
+			$message_template_group->messenger(),
1431
+			$message_template_group->message_type()
1432
+		);
1433
+
1434
+		if (! $template_field_structure) {
1435
+			$template_field_structure = false;
1436
+			$template_fields          = esc_html__(
1437
+				'There was an error in assembling the fields for this display (you should see an error message)',
1438
+				'event_espresso'
1439
+			);
1440
+		}
1441
+
1442
+
1443
+		$message_templates = $message_template_group->context_templates();
1444
+
1445
+
1446
+		// if we have the extra key.. then we need to remove the content index from the template_field_structure as it
1447
+		// will get handled in the "extra" array.
1448
+		if (is_array($template_field_structure[ $context ]) && isset($template_field_structure[ $context ]['extra'])) {
1449
+			foreach ($template_field_structure[ $context ]['extra'] as $reference_field => $new_fields) {
1450
+				unset($template_field_structure[ $context ][ $reference_field ]);
1451
+			}
1452
+		}
1453
+
1454
+		// let's loop through the template_field_structure and actually assemble the input fields!
1455
+		if (! empty($template_field_structure)) {
1456
+			foreach ($template_field_structure[ $context ] as $template_field => $field_setup_array) {
1457
+				// if this is an 'extra' template field then we need to remove any existing fields that are keyed up in
1458
+				// the extra array and reset them.
1459
+				if ($template_field === 'extra') {
1460
+					$this->_template_args['is_extra_fields'] = true;
1461
+					foreach ($field_setup_array as $reference_field => $new_fields_array) {
1462
+						$message_template = $message_templates[ $context ][ $reference_field ];
1463
+						$content          = $message_template instanceof EE_Message_Template
1464
+							? $message_template->get('MTP_content')
1465
+							: '';
1466
+						foreach ($new_fields_array as $extra_field => $extra_array) {
1467
+							// let's verify if we need this extra field via the shortcodes parameter.
1468
+							$continue = false;
1469
+							if (isset($extra_array['shortcodes_required'])) {
1470
+								foreach ((array) $extra_array['shortcodes_required'] as $shortcode) {
1471
+									if (! array_key_exists($shortcode, $this->_shortcodes)) {
1472
+										$continue = true;
1473
+									}
1474
+								}
1475
+								if ($continue) {
1476
+									continue;
1477
+								}
1478
+							}
1479
+
1480
+							$field_id = $reference_field . '-' . $extra_field . '-content';
1481
+
1482
+							$template_form_fields[ $field_id ]         = $extra_array;
1483
+							$template_form_fields[ $field_id ]['name'] = 'MTP_template_fields['
1484
+																		 . $reference_field
1485
+																		 . '][content]['
1486
+																		 . $extra_field . ']';
1487
+							$css_class                                 = isset($extra_array['css_class'])
1488
+								? $extra_array['css_class']
1489
+								: '';
1490
+
1491
+							$template_form_fields[ $field_id ]['css_class'] = ! empty($v_fields)
1492
+																			  && in_array($extra_field, $v_fields, true)
1493
+																			  && (
1494
+																				  is_array($validators[ $extra_field ])
1495
+																				  && isset($validators[ $extra_field ]['msg'])
1496
+																			  )
1497
+								? 'validate-error ' . $css_class
1498
+								: $css_class;
1499
+
1500
+							$template_form_fields[ $field_id ]['value'] = ! empty($message_templates)
1501
+																		  && isset($content[ $extra_field ])
1502
+								? $content[ $extra_field ]
1503
+								: '';
1504
+
1505
+							// do we have a validation error?  if we do then let's use that value instead
1506
+							$template_form_fields[ $field_id ]['value'] = isset($validators[ $extra_field ])
1507
+								? $validators[ $extra_field ]['value']
1508
+								: $template_form_fields[ $field_id ]['value'];
1509
+
1510
+
1511
+							$template_form_fields[ $field_id ]['db-col'] = 'MTP_content';
1512
+
1513
+							// shortcode selector
1514
+							$field_name_to_use                                   = $extra_field === 'main'
1515
+								? 'content'
1516
+								: $extra_field;
1517
+							$template_form_fields[ $field_id ]['append_content'] = $this->_get_shortcode_selector(
1518
+								$field_name_to_use,
1519
+								$field_id
1520
+							);
1521
+						}
1522
+						$template_field_MTP_id           = $reference_field . '-MTP_ID';
1523
+						$template_field_template_name_id = $reference_field . '-name';
1524
+
1525
+						$template_form_fields[ $template_field_MTP_id ] = [
1526
+							'name'       => 'MTP_template_fields[' . $reference_field . '][MTP_ID]',
1527
+							'label'      => null,
1528
+							'input'      => 'hidden',
1529
+							'type'       => 'int',
1530
+							'required'   => false,
1531
+							'validation' => false,
1532
+							'value'      => ! empty($message_templates) ? $message_template->ID() : '',
1533
+							'css_class'  => '',
1534
+							'format'     => '%d',
1535
+							'db-col'     => 'MTP_ID',
1536
+						];
1537
+
1538
+						$template_form_fields[ $template_field_template_name_id ] = [
1539
+							'name'       => 'MTP_template_fields[' . $reference_field . '][name]',
1540
+							'label'      => null,
1541
+							'input'      => 'hidden',
1542
+							'type'       => 'string',
1543
+							'required'   => false,
1544
+							'validation' => true,
1545
+							'value'      => $reference_field,
1546
+							'css_class'  => '',
1547
+							'format'     => '%s',
1548
+							'db-col'     => 'MTP_template_field',
1549
+						];
1550
+					}
1551
+					continue; // skip the next stuff, we got the necessary fields here for this dataset.
1552
+				} else {
1553
+					$field_id                                   = $template_field . '-content';
1554
+					$template_form_fields[ $field_id ]          = $field_setup_array;
1555
+					$template_form_fields[ $field_id ]['name']  =
1556
+						'MTP_template_fields[' . $template_field . '][content]';
1557
+					$message_template                           =
1558
+						isset($message_templates[ $context ][ $template_field ])
1559
+							? $message_templates[ $context ][ $template_field ]
1560
+							: null;
1561
+					$template_form_fields[ $field_id ]['value'] = ! empty($message_templates)
1562
+																  && is_array($message_templates[ $context ])
1563
+																  && $message_template instanceof EE_Message_Template
1564
+						? $message_template->get('MTP_content')
1565
+						: '';
1566
+
1567
+					// do we have a validator error for this field?  if we do then we'll use that value instead
1568
+					$template_form_fields[ $field_id ]['value'] = isset($validators[ $template_field ])
1569
+						? $validators[ $template_field ]['value']
1570
+						: $template_form_fields[ $field_id ]['value'];
1571
+
1572
+
1573
+					$template_form_fields[ $field_id ]['db-col']    = 'MTP_content';
1574
+					$css_class                                      = isset($field_setup_array['css_class'])
1575
+						? $field_setup_array['css_class']
1576
+						: '';
1577
+					$template_form_fields[ $field_id ]['css_class'] = ! empty($v_fields)
1578
+																	  && in_array($template_field, $v_fields, true)
1579
+																	  && isset($validators[ $template_field ]['msg'])
1580
+						? 'validate-error ' . $css_class
1581
+						: $css_class;
1582
+
1583
+					// shortcode selector
1584
+					$template_form_fields[ $field_id ]['append_content'] = $this->_get_shortcode_selector(
1585
+						$template_field,
1586
+						$field_id
1587
+					);
1588
+				}
1589
+
1590
+				// k took care of content field(s) now let's take care of others.
1591
+
1592
+				$template_field_MTP_id                 = $template_field . '-MTP_ID';
1593
+				$template_field_field_template_name_id = $template_field . '-name';
1594
+
1595
+				// foreach template field there are actually two form fields created
1596
+				$template_form_fields[ $template_field_MTP_id ] = [
1597
+					'name'       => 'MTP_template_fields[' . $template_field . '][MTP_ID]',
1598
+					'label'      => null,
1599
+					'input'      => 'hidden',
1600
+					'type'       => 'int',
1601
+					'required'   => false,
1602
+					'validation' => true,
1603
+					'value'      => $message_template instanceof EE_Message_Template ? $message_template->ID() : '',
1604
+					'css_class'  => '',
1605
+					'format'     => '%d',
1606
+					'db-col'     => 'MTP_ID',
1607
+				];
1608
+
1609
+				$template_form_fields[ $template_field_field_template_name_id ] = [
1610
+					'name'       => 'MTP_template_fields[' . $template_field . '][name]',
1611
+					'label'      => null,
1612
+					'input'      => 'hidden',
1613
+					'type'       => 'string',
1614
+					'required'   => false,
1615
+					'validation' => true,
1616
+					'value'      => $template_field,
1617
+					'css_class'  => '',
1618
+					'format'     => '%s',
1619
+					'db-col'     => 'MTP_template_field',
1620
+				];
1621
+			}
1622
+
1623
+			// add other fields
1624
+			$template_form_fields['ee-msg-current-context'] = [
1625
+				'name'       => 'MTP_context',
1626
+				'label'      => null,
1627
+				'input'      => 'hidden',
1628
+				'type'       => 'string',
1629
+				'required'   => false,
1630
+				'validation' => true,
1631
+				'value'      => $context,
1632
+				'css_class'  => '',
1633
+				'format'     => '%s',
1634
+				'db-col'     => 'MTP_context',
1635
+			];
1636
+
1637
+			$template_form_fields['ee-msg-grp-id'] = [
1638
+				'name'       => 'GRP_ID',
1639
+				'label'      => null,
1640
+				'input'      => 'hidden',
1641
+				'type'       => 'int',
1642
+				'required'   => false,
1643
+				'validation' => true,
1644
+				'value'      => $GRP_ID,
1645
+				'css_class'  => '',
1646
+				'format'     => '%d',
1647
+				'db-col'     => 'GRP_ID',
1648
+			];
1649
+
1650
+			$template_form_fields['ee-msg-messenger'] = [
1651
+				'name'       => 'MTP_messenger',
1652
+				'label'      => null,
1653
+				'input'      => 'hidden',
1654
+				'type'       => 'string',
1655
+				'required'   => false,
1656
+				'validation' => true,
1657
+				'value'      => $message_template_group->messenger(),
1658
+				'css_class'  => '',
1659
+				'format'     => '%s',
1660
+				'db-col'     => 'MTP_messenger',
1661
+			];
1662
+
1663
+			$template_form_fields['ee-msg-message-type'] = [
1664
+				'name'       => 'MTP_message_type',
1665
+				'label'      => null,
1666
+				'input'      => 'hidden',
1667
+				'type'       => 'string',
1668
+				'required'   => false,
1669
+				'validation' => true,
1670
+				'value'      => $message_template_group->message_type(),
1671
+				'css_class'  => '',
1672
+				'format'     => '%s',
1673
+				'db-col'     => 'MTP_message_type',
1674
+			];
1675
+
1676
+			$sidebar_form_fields['ee-msg-is-global'] = [
1677
+				'name'       => 'MTP_is_global',
1678
+				'label'      => esc_html__('Global Template', 'event_espresso'),
1679
+				'input'      => 'hidden',
1680
+				'type'       => 'int',
1681
+				'required'   => false,
1682
+				'validation' => true,
1683
+				'value'      => $message_template_group->get('MTP_is_global'),
1684
+				'css_class'  => '',
1685
+				'format'     => '%d',
1686
+				'db-col'     => 'MTP_is_global',
1687
+			];
1688
+
1689
+			$sidebar_form_fields['ee-msg-is-override'] = [
1690
+				'name'       => 'MTP_is_override',
1691
+				'label'      => esc_html__('Override all custom', 'event_espresso'),
1692
+				'input'      => $message_template_group->is_global() ? 'checkbox' : 'hidden',
1693
+				'type'       => 'int',
1694
+				'required'   => false,
1695
+				'validation' => true,
1696
+				'value'      => $message_template_group->get('MTP_is_override'),
1697
+				'css_class'  => '',
1698
+				'format'     => '%d',
1699
+				'db-col'     => 'MTP_is_override',
1700
+			];
1701
+
1702
+			$sidebar_form_fields['ee-msg-is-active'] = [
1703
+				'name'       => 'MTP_is_active',
1704
+				'label'      => esc_html__('Active Template', 'event_espresso'),
1705
+				'input'      => 'hidden',
1706
+				'type'       => 'int',
1707
+				'required'   => false,
1708
+				'validation' => true,
1709
+				'value'      => $message_template_group->is_active(),
1710
+				'css_class'  => '',
1711
+				'format'     => '%d',
1712
+				'db-col'     => 'MTP_is_active',
1713
+			];
1714
+
1715
+			$sidebar_form_fields['ee-msg-deleted'] = [
1716
+				'name'       => 'MTP_deleted',
1717
+				'label'      => null,
1718
+				'input'      => 'hidden',
1719
+				'type'       => 'int',
1720
+				'required'   => false,
1721
+				'validation' => true,
1722
+				'value'      => $message_template_group->get('MTP_deleted'),
1723
+				'css_class'  => '',
1724
+				'format'     => '%d',
1725
+				'db-col'     => 'MTP_deleted',
1726
+			];
1727
+			$sidebar_form_fields['ee-msg-author']  = [
1728
+				'name'       => 'MTP_user_id',
1729
+				'label'      => esc_html__('Author', 'event_espresso'),
1730
+				'input'      => 'hidden',
1731
+				'type'       => 'int',
1732
+				'required'   => false,
1733
+				'validation' => false,
1734
+				'value'      => $message_template_group->user(),
1735
+				'format'     => '%d',
1736
+				'db-col'     => 'MTP_user_id',
1737
+			];
1738
+
1739
+			$sidebar_form_fields['ee-msg-route'] = [
1740
+				'name'  => 'action',
1741
+				'input' => 'hidden',
1742
+				'type'  => 'string',
1743
+				'value' => $action,
1744
+			];
1745
+
1746
+			$sidebar_form_fields['ee-msg-id']        = [
1747
+				'name'  => 'id',
1748
+				'input' => 'hidden',
1749
+				'type'  => 'int',
1750
+				'value' => $GRP_ID,
1751
+			];
1752
+			$sidebar_form_fields['ee-msg-evt-nonce'] = [
1753
+				'name'  => $action . '_nonce',
1754
+				'input' => 'hidden',
1755
+				'type'  => 'string',
1756
+				'value' => wp_create_nonce($action . '_nonce'),
1757
+			];
1758
+
1759
+			$template_switch = $this->request->getRequestParam('template_switch');
1760
+			if ($template_switch) {
1761
+				$sidebar_form_fields['ee-msg-template-switch'] = [
1762
+					'name'  => 'template_switch',
1763
+					'input' => 'hidden',
1764
+					'type'  => 'int',
1765
+					'value' => 1,
1766
+				];
1767
+			}
1768
+
1769
+
1770
+			$template_fields = $this->_generate_admin_form_fields($template_form_fields);
1771
+			$sidebar_fields  = $this->_generate_admin_form_fields($sidebar_form_fields);
1772
+		} //end if ( !empty($template_field_structure) )
1773
+
1774
+		// set extra content for publish box
1775
+		$this->_template_args['publish_box_extra_content'] = $sidebar_fields;
1776
+		$this->_set_publish_post_box_vars(
1777
+			'id',
1778
+			$GRP_ID,
1779
+			false,
1780
+			add_query_arg(
1781
+				['action' => 'global_mtps'],
1782
+				$this->_admin_base_url
1783
+			)
1784
+		);
1785
+
1786
+		// add preview button
1787
+		$preview_url    = parent::add_query_args_and_nonce(
1788
+			[
1789
+				'message_type' => $message_template_group->message_type(),
1790
+				'messenger'    => $message_template_group->messenger(),
1791
+				'context'      => $context,
1792
+				'GRP_ID'       => $GRP_ID,
1793
+				'evt_id'       => $EVT_ID ?: false,
1794
+				'action'       => 'preview_message',
1795
+			],
1796
+			$this->_admin_base_url
1797
+		);
1798
+		$preview_button = '<a href="' . $preview_url . '" class="button--secondary messages-preview-button">'
1799
+						  . esc_html__('Preview', 'event_espresso')
1800
+						  . '</a>';
1801
+
1802
+
1803
+		// setup context switcher
1804
+		$this->_set_context_switcher(
1805
+			$message_template_group,
1806
+			[
1807
+				'page'    => 'espresso_messages',
1808
+				'action'  => 'edit_message_template',
1809
+				'id'      => $GRP_ID,
1810
+				'evt_id'  => $EVT_ID,
1811
+				'context' => $context,
1812
+				'extra'   => $preview_button,
1813
+			]
1814
+		);
1815
+
1816
+
1817
+		// main box
1818
+		$this->_template_args['template_fields']                         = $template_fields;
1819
+		$this->_template_args['sidebar_box_id']                          = 'details';
1820
+		$this->_template_args['action']                                  = $action;
1821
+		$this->_template_args['context']                                 = $context;
1822
+		$this->_template_args['edit_message_template_form_url']          = $edit_message_template_form_url;
1823
+		$this->_template_args['learn_more_about_message_templates_link'] =
1824
+			$this->_learn_more_about_message_templates_link();
1825
+
1826
+
1827
+		$this->_template_args['before_admin_page_content'] = '<div class="ee-msg-admin-header">';
1828
+		$this->_template_args['before_admin_page_content'] .= $this->add_active_context_element(
1829
+			$message_template_group,
1830
+			$context,
1831
+			$context_label
1832
+		);
1833
+		$this->_template_args['before_admin_page_content'] .= $this->add_context_switcher();
1834
+		$this->_template_args['before_admin_page_content'] .= '</div>';
1835
+		$this->_template_args['before_admin_page_content'] .= $this->_add_form_element_before();
1836
+		$this->_template_args['after_admin_page_content']  = $this->_add_form_element_after();
1837
+
1838
+		$this->_template_path = $this->_template_args['GRP_ID']
1839
+			? EE_MSG_TEMPLATE_PATH . 'ee_msg_details_main_edit_meta_box.template.php'
1840
+			: EE_MSG_TEMPLATE_PATH . 'ee_msg_details_main_add_meta_box.template.php';
1841
+
1842
+		// send along EE_Message_Template_Group object for further template use.
1843
+		$this->_template_args['MTP'] = $message_template_group;
1844
+
1845
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
1846
+			$this->_template_path,
1847
+			$this->_template_args,
1848
+			true
1849
+		);
1850
+
1851
+
1852
+		// finally, let's set the admin_page title
1853
+		$this->_admin_page_title = sprintf(esc_html__('Editing %s', 'event_espresso'), $title);
1854
+
1855
+
1856
+		// we need to take care of setting the shortcodes property for use elsewhere.
1857
+		$this->_set_shortcodes();
1858
+
1859
+
1860
+		// final template wrapper
1861
+		$this->display_admin_page_with_sidebar();
1862
+	}
1863
+
1864
+
1865
+	public function filter_tinymce_init($mceInit, $editor_id)
1866
+	{
1867
+		return $mceInit;
1868
+	}
1869
+
1870
+
1871
+	public function add_context_switcher()
1872
+	{
1873
+		return $this->_context_switcher;
1874
+	}
1875
+
1876
+
1877
+	/**
1878
+	 * Adds the activation/deactivation toggle for the message template context.
1879
+	 *
1880
+	 * @param EE_Message_Template_Group $message_template_group
1881
+	 * @param string                    $context
1882
+	 * @param string                    $context_label
1883
+	 * @return string
1884
+	 * @throws DomainException
1885
+	 * @throws EE_Error
1886
+	 * @throws InvalidIdentifierException
1887
+	 * @throws ReflectionException
1888
+	 */
1889
+	protected function add_active_context_element(
1890
+		EE_Message_Template_Group $message_template_group,
1891
+		$context,
1892
+		$context_label
1893
+	) {
1894
+		$template_args = [
1895
+			'context'                   => $context,
1896
+			'nonce'                     => wp_create_nonce('activate_' . $context . '_toggle_nonce'),
1897
+			'is_active'                 => $message_template_group->is_context_active($context),
1898
+			'on_off_action'             => $message_template_group->is_context_active($context)
1899
+				? 'context-off'
1900
+				: 'context-on',
1901
+			'context_label'             => str_replace(['(', ')'], '', $context_label),
1902
+			'message_template_group_id' => $message_template_group->ID(),
1903
+		];
1904
+		return EEH_Template::display_template(
1905
+			EE_MSG_TEMPLATE_PATH . 'ee_msg_editor_active_context_element.template.php',
1906
+			$template_args,
1907
+			true
1908
+		);
1909
+	}
1910
+
1911
+
1912
+	/**
1913
+	 * Ajax callback for `toggle_context_template` ajax action.
1914
+	 * Handles toggling the message context on or off.
1915
+	 *
1916
+	 * @throws EE_Error
1917
+	 * @throws InvalidArgumentException
1918
+	 * @throws InvalidDataTypeException
1919
+	 * @throws InvalidIdentifierException
1920
+	 * @throws InvalidInterfaceException
1921
+	 */
1922
+	public function toggle_context_template()
1923
+	{
1924
+		$success = true;
1925
+		// check for required data
1926
+		if (
1927
+			! (
1928
+				$this->request->requestParamIsSet('message_template_group_id')
1929
+				&& $this->request->requestParamIsSet('context')
1930
+				&& $this->request->requestParamIsSet('status')
1931
+			)
1932
+		) {
1933
+			EE_Error::add_error(
1934
+				esc_html__('Required data for doing this action is not available.', 'event_espresso'),
1935
+				__FILE__,
1936
+				__FUNCTION__,
1937
+				__LINE__
1938
+			);
1939
+			$success = false;
1940
+		}
1941
+
1942
+		$nonce   = $this->request->getRequestParam('toggle_context_nonce', '');
1943
+		$context = $this->request->getRequestParam('context', '');
1944
+		$status  = $this->request->getRequestParam('status', '');
1945
+
1946
+		$this->_verify_nonce($nonce, "activate_{$context}_toggle_nonce");
1947
+
1948
+		if ($status !== 'off' && $status !== 'on') {
1949
+			EE_Error::add_error(
1950
+				sprintf(
1951
+					esc_html__('The given status (%s) is not valid. Must be "off" or "on"', 'event_espresso'),
1952
+					$status
1953
+				),
1954
+				__FILE__,
1955
+				__FUNCTION__,
1956
+				__LINE__
1957
+			);
1958
+			$success = false;
1959
+		}
1960
+		$message_template_group_id = $this->request->getRequestParam('message_template_group_id', 0, 'int');
1961
+		$message_template_group    = $this->getMtgModel()->get_one_by_ID($message_template_group_id);
1962
+		if (! $message_template_group instanceof EE_Message_Template_Group) {
1963
+			EE_Error::add_error(
1964
+				sprintf(
1965
+					esc_html__(
1966
+						'Unable to change the active state because the given id "%1$d" does not match a valid "%2$s"',
1967
+						'event_espresso'
1968
+					),
1969
+					$message_template_group_id,
1970
+					'EE_Message_Template_Group'
1971
+				),
1972
+				__FILE__,
1973
+				__FUNCTION__,
1974
+				__LINE__
1975
+			);
1976
+			$success = false;
1977
+		}
1978
+		if ($success) {
1979
+			$success = $status === 'off'
1980
+				? $message_template_group->deactivate_context($context)
1981
+				: $message_template_group->activate_context($context);
1982
+		}
1983
+		$this->_template_args['success'] = $success;
1984
+		$this->_return_json();
1985
+	}
1986
+
1987
+
1988
+	public function _add_form_element_before()
1989
+	{
1990
+		return '<form method="post" action="'
1991
+			   . $this->_template_args['edit_message_template_form_url']
1992
+			   . '" id="ee-msg-edit-frm">';
1993
+	}
1994
+
1995
+
1996
+	public function _add_form_element_after()
1997
+	{
1998
+		return '</form>';
1999
+	}
2000
+
2001
+
2002
+	/**
2003
+	 * This executes switching the template pack for a message template.
2004
+	 *
2005
+	 * @throws EE_Error
2006
+	 * @throws InvalidDataTypeException
2007
+	 * @throws InvalidInterfaceException
2008
+	 * @throws InvalidArgumentException
2009
+	 * @throws ReflectionException
2010
+	 * @since 4.5.0
2011
+	 */
2012
+	public function switch_template_pack()
2013
+	{
2014
+
2015
+		$GRP_ID        = $this->request->getRequestParam('GRP_ID', 0, 'int');
2016
+		$template_pack = $this->request->getRequestParam('template_pack', '');
2017
+
2018
+		// verify we have needed values.
2019
+		if (empty($GRP_ID) || empty($template_pack)) {
2020
+			$this->_template_args['error'] = true;
2021
+			EE_Error::add_error(
2022
+				esc_html__('The required date for switching templates is not available.', 'event_espresso'),
2023
+				__FILE__,
2024
+				__FUNCTION__,
2025
+				__LINE__
2026
+			);
2027
+		} else {
2028
+			// get template, set the new template_pack and then reset to default
2029
+			/** @var EE_Message_Template_Group $message_template_group */
2030
+			$message_template_group = $this->getMtgModel()->get_one_by_ID($GRP_ID);
2031
+
2032
+			$message_template_group->set_template_pack_name($template_pack);
2033
+			$this->request->setRequestParam('msgr', $message_template_group->messenger());
2034
+			$this->request->setRequestParam('mt', $message_template_group->message_type());
2035
+
2036
+			$query_args = $this->_reset_to_default_template();
2037
+
2038
+			if (empty($query_args['id'])) {
2039
+				EE_Error::add_error(
2040
+					esc_html__(
2041
+						'Something went wrong with switching the template pack. Please try again or contact EE support',
2042
+						'event_espresso'
2043
+					),
2044
+					__FILE__,
2045
+					__FUNCTION__,
2046
+					__LINE__
2047
+				);
2048
+				$this->_template_args['error'] = true;
2049
+			} else {
2050
+				$template_label       = $message_template_group->get_template_pack()->label;
2051
+				$template_pack_labels = $message_template_group->messenger_obj()->get_supports_labels();
2052
+				EE_Error::add_success(
2053
+					sprintf(
2054
+						esc_html__(
2055
+							'This message template has been successfully switched to use the %1$s %2$s.  Please wait while the page reloads with your new template.',
2056
+							'event_espresso'
2057
+						),
2058
+						$template_label,
2059
+						$template_pack_labels->template_pack
2060
+					)
2061
+				);
2062
+				// generate the redirect url for js.
2063
+				$url = self::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2064
+
2065
+				$this->_template_args['data']['redirect_url'] = $url;
2066
+				$this->_template_args['success']              = true;
2067
+			}
2068
+
2069
+			$this->_return_json();
2070
+		}
2071
+	}
2072
+
2073
+
2074
+	/**
2075
+	 * This handles resetting the template for the given messenger/message_type so that users can start from scratch if
2076
+	 * they want.
2077
+	 *
2078
+	 * @access protected
2079
+	 * @return array|void
2080
+	 * @throws EE_Error
2081
+	 * @throws InvalidArgumentException
2082
+	 * @throws InvalidDataTypeException
2083
+	 * @throws InvalidInterfaceException
2084
+	 * @throws ReflectionException
2085
+	 */
2086
+	protected function _reset_to_default_template()
2087
+	{
2088
+		$templates    = [];
2089
+		$GRP_ID       = $this->request->getRequestParam('GRP_ID', 0, 'int');
2090
+		$messenger    = $this->request->getRequestParam('msgr');
2091
+		$message_type = $this->request->getRequestParam('mt');
2092
+		// we need to make sure we've got the info we need.
2093
+		if (! ($GRP_ID && $messenger && $message_type)) {
2094
+			EE_Error::add_error(
2095
+				esc_html__(
2096
+					'In order to reset the template to its default we require the messenger, message type, and message template GRP_ID to know what is being reset.  At least one of these is missing.',
2097
+					'event_espresso'
2098
+				),
2099
+				__FILE__,
2100
+				__FUNCTION__,
2101
+				__LINE__
2102
+			);
2103
+		}
2104
+
2105
+		// all templates will be reset to whatever the defaults are
2106
+		// for the global template matching the messenger and message type.
2107
+		$success = ! empty($GRP_ID);
2108
+
2109
+		if ($success) {
2110
+			// let's first determine if the incoming template is a global template,
2111
+			// if it isn't then we need to get the global template matching messenger and message type.
2112
+			// $MTPG = $this->getMtgModel()->get_one_by_ID( $GRP_ID );
2113
+
2114
+
2115
+			// note this is ONLY deleting the template fields (Message Template rows) NOT the message template group.
2116
+			$success = $this->_delete_mtp_permanently($GRP_ID, false);
2117
+
2118
+			if ($success) {
2119
+				// if successfully deleted, lets generate the new ones.
2120
+				// Note. We set GLOBAL to true, because resets on ANY template
2121
+				// will use the related global template defaults for regeneration.
2122
+				// This means that if a custom template is reset it resets to whatever the related global template is.
2123
+				// HOWEVER, we DO keep the template pack and template variation set
2124
+				// for the current custom template when resetting.
2125
+				$templates = $this->_generate_new_templates($messenger, $message_type, $GRP_ID, true);
2126
+			}
2127
+		}
2128
+
2129
+		// any error messages?
2130
+		if (! $success) {
2131
+			EE_Error::add_error(
2132
+				esc_html__(
2133
+					'Something went wrong with deleting existing templates. Unable to reset to default',
2134
+					'event_espresso'
2135
+				),
2136
+				__FILE__,
2137
+				__FUNCTION__,
2138
+				__LINE__
2139
+			);
2140
+		}
2141
+
2142
+		// all good, let's add a success message!
2143
+		if ($success && ! empty($templates)) {
2144
+			// the info for the template we generated is the first element in the returned array
2145
+			EE_Error::overwrite_success();
2146
+			EE_Error::add_success(esc_html__('Templates have been reset to defaults.', 'event_espresso'));
2147
+		}
2148
+
2149
+
2150
+		$query_args = [
2151
+			'id'      => isset($templates['GRP_ID']) ? $templates['GRP_ID'] : null,
2152
+			'context' => isset($templates['MTP_context']) ? $templates['MTP_context'] : null,
2153
+			'action'  => isset($templates['GRP_ID']) ? 'edit_message_template' : 'global_mtps',
2154
+		];
2155
+
2156
+		// if called via ajax then we return query args otherwise redirect
2157
+		if ($this->request->isAjax()) {
2158
+			return $query_args;
2159
+		}
2160
+		$this->_redirect_after_action(false, '', '', $query_args, true);
2161
+	}
2162
+
2163
+
2164
+	/**
2165
+	 * Retrieve and set the message preview for display.
2166
+	 *
2167
+	 * @param bool $send if TRUE then we are doing an actual TEST send with the results of the preview.
2168
+	 * @return string
2169
+	 * @throws ReflectionException
2170
+	 * @throws EE_Error
2171
+	 * @throws InvalidArgumentException
2172
+	 * @throws InvalidDataTypeException
2173
+	 * @throws InvalidInterfaceException
2174
+	 */
2175
+	public function _preview_message($send = false)
2176
+	{
2177
+		// first make sure we've got the necessary parameters
2178
+		$GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
2179
+		if (! ($GRP_ID && $this->_active_messenger_name && $this->_active_message_type_name)) {
2180
+			EE_Error::add_error(
2181
+				esc_html__('Missing necessary parameters for displaying preview', 'event_espresso'),
2182
+				__FILE__,
2183
+				__FUNCTION__,
2184
+				__LINE__
2185
+			);
2186
+		}
2187
+
2188
+		$context = $this->request->getRequestParam('context');
2189
+		// get the preview!
2190
+		$preview = EED_Messages::preview_message(
2191
+			$this->_active_message_type_name,
2192
+			$context,
2193
+			$this->_active_messenger_name,
2194
+			$send
2195
+		);
2196
+
2197
+		if ($send) {
2198
+			return $preview;
2199
+		}
2200
+
2201
+		// if we have an evt_id set on the request, use it.
2202
+		$EVT_ID = $this->request->getRequestParam('evt_id', 0, 'int');
2203
+
2204
+		// let's add a button to go back to the edit view
2205
+		$query_args             = [
2206
+			'id'      => $GRP_ID,
2207
+			'evt_id'  => $EVT_ID,
2208
+			'context' => $context,
2209
+			'action'  => 'edit_message_template',
2210
+		];
2211
+		$go_back_url            = parent::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2212
+		$preview_button         = '<a href="'
2213
+								  . $go_back_url
2214
+								  . '" class="button--secondary messages-preview-go-back-button">'
2215
+								  . esc_html__('Go Back to Edit', 'event_espresso')
2216
+								  . '</a>';
2217
+		$message_types          = $this->get_installed_message_types();
2218
+		$active_messenger       = $this->_message_resource_manager->get_active_messenger($this->_active_messenger_name);
2219
+		$active_messenger_label = $active_messenger instanceof EE_messenger
2220
+			? ucwords($active_messenger->label['singular'])
2221
+			: esc_html__('Unknown Messenger', 'event_espresso');
2222
+		// let's provide a helpful title for context
2223
+		$preview_title = sprintf(
2224
+			esc_html__('Viewing Preview for %s %s Message Template', 'event_espresso'),
2225
+			$active_messenger_label,
2226
+			ucwords($message_types[ $this->_active_message_type_name ]->label['singular'])
2227
+		);
2228
+		if (empty($preview)) {
2229
+			$this->noEventsErrorMessage();
2230
+		}
2231
+		// setup display of preview.
2232
+		$this->_admin_page_title                    = $preview_title;
2233
+		$this->_template_args['admin_page_title']   = $preview_title;
2234
+		$this->_template_args['admin_page_content'] = $preview_button . '<br />' . $preview;
2235
+		$this->_template_args['data']['force_json'] = true;
2236
+
2237
+		return '';
2238
+	}
2239
+
2240
+
2241
+	/**
2242
+	 * Used to set an error if there are no events available for generating a preview/test send.
2243
+	 *
2244
+	 * @param bool $test_send Whether the error should be generated for the context of a test send.
2245
+	 */
2246
+	protected function noEventsErrorMessage($test_send = false)
2247
+	{
2248
+		$events_url = parent::add_query_args_and_nonce(
2249
+			[
2250
+				'action' => 'default',
2251
+				'page'   => 'espresso_events',
2252
+			],
2253
+			admin_url('admin.php')
2254
+		);
2255
+		$message    = $test_send
2256
+			? esc_html__(
2257
+				'A test message could not be sent for this message template because there are no events created yet. The preview system uses actual events for generating the test message. %1$sGo see your events%2$s!',
2258
+				'event_espresso'
2259
+			)
2260
+			: esc_html__(
2261
+				'There is no preview for this message template available because there are no events created yet. The preview system uses actual events for generating the preview. %1$sGo see your events%2$s!',
2262
+				'event_espresso'
2263
+			);
2264
+
2265
+		EE_Error::add_attention(
2266
+			sprintf(
2267
+				$message,
2268
+				"<a href='{$events_url}'>",
2269
+				'</a>'
2270
+			)
2271
+		);
2272
+	}
2273
+
2274
+
2275
+	/**
2276
+	 * The initial _preview_message is on a no headers route.  It will optionally call this if necessary otherwise it
2277
+	 * gets called automatically.
2278
+	 *
2279
+	 * @return void
2280
+	 * @throws EE_Error
2281
+	 * @since 4.5.0
2282
+	 *
2283
+	 */
2284
+	protected function _display_preview_message()
2285
+	{
2286
+		$this->display_admin_page_with_no_sidebar();
2287
+	}
2288
+
2289
+
2290
+	/**
2291
+	 * registers metaboxes that should show up on the "edit_message_template" page
2292
+	 *
2293
+	 * @access protected
2294
+	 * @return void
2295
+	 */
2296
+	protected function _register_edit_meta_boxes()
2297
+	{
2298
+		$this->addMetaBox(
2299
+			'mtp_valid_shortcodes',
2300
+			esc_html__('Valid Shortcodes', 'event_espresso'),
2301
+			[$this, 'shortcode_meta_box'],
2302
+			$this->_current_screen->id,
2303
+			'side'
2304
+		);
2305
+		$this->addMetaBox(
2306
+			'mtp_extra_actions',
2307
+			esc_html__('Extra Actions', 'event_espresso'),
2308
+			[$this, 'extra_actions_meta_box'],
2309
+			$this->_current_screen->id,
2310
+			'side',
2311
+			'high'
2312
+		);
2313
+		$this->addMetaBox(
2314
+			'mtp_templates',
2315
+			esc_html__('Template Styles', 'event_espresso'),
2316
+			[$this, 'template_pack_meta_box'],
2317
+			$this->_current_screen->id,
2318
+			'side',
2319
+			'high'
2320
+		);
2321
+	}
2322
+
2323
+
2324
+	/**
2325
+	 * metabox content for all template pack and variation selection.
2326
+	 *
2327
+	 * @return void
2328
+	 * @throws DomainException
2329
+	 * @throws EE_Error
2330
+	 * @throws InvalidArgumentException
2331
+	 * @throws ReflectionException
2332
+	 * @throws InvalidDataTypeException
2333
+	 * @throws InvalidInterfaceException
2334
+	 * @since 4.5.0
2335
+	 */
2336
+	public function template_pack_meta_box()
2337
+	{
2338
+		$this->_set_message_template_group();
2339
+
2340
+		$tp_collection = EEH_MSG_Template::get_template_pack_collection();
2341
+
2342
+		$tp_select_values = [];
2343
+
2344
+		foreach ($tp_collection as $tp) {
2345
+			// only include template packs that support this messenger and message type!
2346
+			$supports = $tp->get_supports();
2347
+			if (
2348
+				! isset($supports[ $this->_message_template_group->messenger() ])
2349
+				|| ! in_array(
2350
+					$this->_message_template_group->message_type(),
2351
+					$supports[ $this->_message_template_group->messenger() ],
2352
+					true
2353
+				)
2354
+			) {
2355
+				// not supported
2356
+				continue;
2357
+			}
2358
+
2359
+			$tp_select_values[] = [
2360
+				'text' => $tp->label,
2361
+				'id'   => $tp->dbref,
2362
+			];
2363
+		}
2364
+
2365
+		// if empty $tp_select_values then we make sure default is set because EVERY message type should be supported by
2366
+		// the default template pack.  This still allows for the odd template pack to override.
2367
+		if (empty($tp_select_values)) {
2368
+			$tp_select_values[] = [
2369
+				'text' => esc_html__('Default', 'event_espresso'),
2370
+				'id'   => 'default',
2371
+			];
2372
+		}
2373
+
2374
+		// setup variation select values for the currently selected template.
2375
+		$variations               = $this->_message_template_group->get_template_pack()->get_variations(
2376
+			$this->_message_template_group->messenger(),
2377
+			$this->_message_template_group->message_type()
2378
+		);
2379
+		$variations_select_values = [];
2380
+		foreach ($variations as $variation => $label) {
2381
+			$variations_select_values[] = [
2382
+				'text' => $label,
2383
+				'id'   => $variation,
2384
+			];
2385
+		}
2386
+
2387
+		$template_pack_labels = $this->_message_template_group->messenger_obj()->get_supports_labels();
2388
+
2389
+		$template_args['template_packs_selector']        = EEH_Form_Fields::select_input(
2390
+			'MTP_template_pack',
2391
+			$tp_select_values,
2392
+			$this->_message_template_group->get_template_pack_name()
2393
+		);
2394
+		$template_args['variations_selector']            = EEH_Form_Fields::select_input(
2395
+			'MTP_template_variation',
2396
+			$variations_select_values,
2397
+			$this->_message_template_group->get_template_pack_variation()
2398
+		);
2399
+		$template_args['template_pack_label']            = $template_pack_labels->template_pack;
2400
+		$template_args['template_variation_label']       = $template_pack_labels->template_variation;
2401
+		$template_args['template_pack_description']      = $template_pack_labels->template_pack_description;
2402
+		$template_args['template_variation_description'] = $template_pack_labels->template_variation_description;
2403
+
2404
+		$template = EE_MSG_TEMPLATE_PATH . 'template_pack_and_variations_metabox.template.php';
2405
+
2406
+		EEH_Template::display_template($template, $template_args);
2407
+	}
2408
+
2409
+
2410
+	/**
2411
+	 * This meta box holds any extra actions related to Message Templates
2412
+	 * For now, this includes Resetting templates to defaults and sending a test email.
2413
+	 *
2414
+	 * @access  public
2415
+	 * @return void
2416
+	 * @throws EE_Error
2417
+	 */
2418
+	public function extra_actions_meta_box()
2419
+	{
2420
+		$template_form_fields = [];
2421
+
2422
+		$extra_args = [
2423
+			'msgr'   => $this->_message_template_group->messenger(),
2424
+			'mt'     => $this->_message_template_group->message_type(),
2425
+			'GRP_ID' => $this->_message_template_group->GRP_ID(),
2426
+		];
2427
+		// first we need to see if there are any fields
2428
+		$fields = $this->_message_template_group->messenger_obj()->get_test_settings_fields();
2429
+
2430
+		if (! empty($fields)) {
2431
+			// yup there be fields
2432
+			foreach ($fields as $field => $config) {
2433
+				$field_id = $this->_message_template_group->messenger() . '_' . $field;
2434
+				$existing = $this->_message_template_group->messenger_obj()->get_existing_test_settings();
2435
+				$default  = isset($config['default']) ? $config['default'] : '';
2436
+				$default  = isset($config['value']) ? $config['value'] : $default;
2437
+
2438
+				// if type is hidden and the value is empty
2439
+				// something may have gone wrong so let's correct with the defaults
2440
+				$fix                = $config['input'] === 'hidden'
2441
+									  && isset($existing[ $field ])
2442
+									  && empty($existing[ $field ])
2443
+					? $default
2444
+					: '';
2445
+				$existing[ $field ] = isset($existing[ $field ]) && empty($fix)
2446
+					? $existing[ $field ]
2447
+					: $fix;
2448
+
2449
+				$template_form_fields[ $field_id ] = [
2450
+					'name'       => 'test_settings_fld[' . $field . ']',
2451
+					'label'      => $config['label'],
2452
+					'input'      => $config['input'],
2453
+					'type'       => $config['type'],
2454
+					'required'   => $config['required'],
2455
+					'validation' => $config['validation'],
2456
+					'value'      => isset($existing[ $field ]) ? $existing[ $field ] : $default,
2457
+					'css_class'  => $config['css_class'],
2458
+					'options'    => isset($config['options']) ? $config['options'] : [],
2459
+					'default'    => $default,
2460
+					'format'     => $config['format'],
2461
+				];
2462
+			}
2463
+		}
2464
+
2465
+		$test_settings_html = ! empty($template_form_fields)
2466
+			? $this->_generate_admin_form_fields($template_form_fields, 'string', 'ee_tst_settings_flds')
2467
+			: '';
2468
+
2469
+		// print out $test_settings_fields
2470
+		if (! empty($test_settings_html)) {
2471
+			$test_settings_html .= '<input type="submit" class="button--primary mtp-test-button alignright" ';
2472
+			$test_settings_html .= 'name="test_button" value="';
2473
+			$test_settings_html .= esc_html__('Test Send', 'event_espresso');
2474
+			$test_settings_html .= '" /><div style="clear:both"></div>';
2475
+		}
2476
+
2477
+		// and button
2478
+		$test_settings_html .= '<div class="publishing-action alignright resetbutton">';
2479
+		$test_settings_html .= '<p>';
2480
+		$test_settings_html .= esc_html__('Need to reset this message type and start over?', 'event_espresso');
2481
+		$test_settings_html .= '</p>';
2482
+		$test_settings_html .= $this->get_action_link_or_button(
2483
+			'reset_to_default',
2484
+			'reset',
2485
+			$extra_args,
2486
+			'button--primary reset-default-button'
2487
+		);
2488
+		$test_settings_html .= '</div><div style="clear:both"></div>';
2489
+		echo wp_kses($test_settings_html, AllowedTags::getWithFormTags());
2490
+	}
2491
+
2492
+
2493
+	/**
2494
+	 * This returns the shortcode selector skeleton for a given context and field.
2495
+	 *
2496
+	 * @param string $field           The name of the field retrieving shortcodes for.
2497
+	 * @param string $linked_input_id The css id of the input that the shortcodes get added to.
2498
+	 * @return string
2499
+	 * @throws DomainException
2500
+	 * @throws EE_Error
2501
+	 * @throws InvalidArgumentException
2502
+	 * @throws ReflectionException
2503
+	 * @throws InvalidDataTypeException
2504
+	 * @throws InvalidInterfaceException
2505
+	 * @since 4.9.rc.000
2506
+	 */
2507
+	protected function _get_shortcode_selector($field, $linked_input_id)
2508
+	{
2509
+		$template_args = [
2510
+			'shortcodes'      => $this->_get_shortcodes([$field]),
2511
+			'fieldname'       => $field,
2512
+			'linked_input_id' => $linked_input_id,
2513
+		];
2514
+
2515
+		return EEH_Template::display_template(
2516
+			EE_MSG_TEMPLATE_PATH . 'shortcode_selector_skeleton.template.php',
2517
+			$template_args,
2518
+			true
2519
+		);
2520
+	}
2521
+
2522
+
2523
+	/**
2524
+	 * This just takes care of returning the meta box content for shortcodes (only used on the edit message template
2525
+	 * page)
2526
+	 *
2527
+	 * @access public
2528
+	 * @return void
2529
+	 * @throws EE_Error
2530
+	 * @throws InvalidArgumentException
2531
+	 * @throws ReflectionException
2532
+	 * @throws InvalidDataTypeException
2533
+	 * @throws InvalidInterfaceException
2534
+	 */
2535
+	public function shortcode_meta_box()
2536
+	{
2537
+		$shortcodes = $this->_get_shortcodes([], false);
2538
+		// just make sure the shortcodes property is set
2539
+		// $messenger = $this->_message_template_group->messenger_obj();
2540
+		// now let's set the content depending on the status of the shortcodes array
2541
+		if (empty($shortcodes)) {
2542
+			echo '<p>' . esc_html__('There are no valid shortcodes available', 'event_espresso') . '</p>';
2543
+			return;
2544
+		}
2545
+		?>
2546 2546
         <div style="float:right; margin-top:10px">
2547 2547
             <?php echo wp_kses($this->_get_help_tab_link('message_template_shortcodes'), AllowedTags::getAllowedTags());
2548
-            ?>
2548
+			?>
2549 2549
         </div>
2550 2550
         <p class="small-text">
2551 2551
             <?php printf(
2552
-                esc_html__(
2553
-                    'You can view the shortcodes usable in your template by clicking the %s icon next to each field.',
2554
-                    'event_espresso'
2555
-                ),
2556
-                '<span class="dashicons dashicons-shortcode"></span>'
2557
-            ); ?>
2552
+				esc_html__(
2553
+					'You can view the shortcodes usable in your template by clicking the %s icon next to each field.',
2554
+					'event_espresso'
2555
+				),
2556
+				'<span class="dashicons dashicons-shortcode"></span>'
2557
+			); ?>
2558 2558
         </p>
2559 2559
         <?php
2560
-    }
2561
-
2562
-
2563
-    /**
2564
-     * used to set the $_shortcodes property for when its needed elsewhere.
2565
-     *
2566
-     * @access protected
2567
-     * @return void
2568
-     * @throws EE_Error
2569
-     * @throws InvalidArgumentException
2570
-     * @throws ReflectionException
2571
-     * @throws InvalidDataTypeException
2572
-     * @throws InvalidInterfaceException
2573
-     */
2574
-    protected function _set_shortcodes()
2575
-    {
2576
-
2577
-        // no need to run this if the property is already set
2578
-        if (! empty($this->_shortcodes)) {
2579
-            return;
2580
-        }
2581
-
2582
-        $this->_shortcodes = $this->_get_shortcodes();
2583
-    }
2584
-
2585
-
2586
-    /**
2587
-     * gets all shortcodes for a given template group. (typically used by _set_shortcodes to set the $_shortcodes
2588
-     * property)
2589
-     *
2590
-     * @access  protected
2591
-     * @param array   $fields  include an array of specific field names that you want to be used to get the shortcodes
2592
-     *                         for. Defaults to all (for the given context)
2593
-     * @param boolean $merged  Whether to merge all the shortcodes into one list of unique shortcodes
2594
-     * @return array Shortcodes indexed by fieldname and the an array of shortcode/label pairs OR if merged is
2595
-     *                         true just an array of shortcode/label pairs.
2596
-     * @throws EE_Error
2597
-     * @throws InvalidArgumentException
2598
-     * @throws ReflectionException
2599
-     * @throws InvalidDataTypeException
2600
-     * @throws InvalidInterfaceException
2601
-     */
2602
-    protected function _get_shortcodes($fields = [], $merged = true)
2603
-    {
2604
-        $this->_set_message_template_group();
2605
-
2606
-        // we need the messenger and message template to retrieve the valid shortcodes array.
2607
-        $GRP_ID = $this->request->getRequestParam('id', 0, 'int');
2608
-        if (empty($GRP_ID)) {
2609
-            return [];
2610
-        }
2611
-        $context = $this->request->getRequestParam(
2612
-            'messenger',
2613
-            key($this->_message_template_group->contexts_config())
2614
-        );
2615
-        return $this->_message_template_group->get_shortcodes($context, $fields, $merged);
2616
-    }
2617
-
2618
-
2619
-    /**
2620
-     * This sets the _message_template property (containing the called message_template object)
2621
-     *
2622
-     * @access protected
2623
-     * @return void
2624
-     * @throws EE_Error
2625
-     * @throws InvalidArgumentException
2626
-     * @throws ReflectionException
2627
-     * @throws InvalidDataTypeException
2628
-     * @throws InvalidInterfaceException
2629
-     */
2630
-    protected function _set_message_template_group()
2631
-    {
2632
-        // get out if this is already set.
2633
-        if (! empty($this->_message_template_group)) {
2634
-            return;
2635
-        }
2636
-
2637
-        $GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
2638
-        $GRP_ID = $this->request->getRequestParam('id', $GRP_ID, 'int');
2639
-
2640
-        // let's get the message templates
2641
-        $this->_message_template_group = ! empty($GRP_ID)
2642
-            ? $this->getMtgModel()->get_one_by_ID($GRP_ID)
2643
-            : $this->getMtgModel()->create_default_object();
2644
-
2645
-        $this->_template_pack = $this->_message_template_group->get_template_pack();
2646
-        $this->_variation     = $this->_message_template_group->get_template_pack_variation();
2647
-    }
2648
-
2649
-
2650
-    /**
2651
-     * sets up a context switcher for edit forms
2652
-     *
2653
-     * @access  protected
2654
-     * @param EE_Message_Template_Group $template_group_object the template group object being displayed on the form
2655
-     * @param array                     $args                  various things the context switcher needs.
2656
-     * @throws EE_Error
2657
-     */
2658
-    protected function _set_context_switcher(EE_Message_Template_Group $template_group_object, $args)
2659
-    {
2660
-        $context_details = $template_group_object->contexts_config();
2661
-        $context_label   = $template_group_object->context_label();
2662
-        ob_start();
2663
-        ?>
2560
+	}
2561
+
2562
+
2563
+	/**
2564
+	 * used to set the $_shortcodes property for when its needed elsewhere.
2565
+	 *
2566
+	 * @access protected
2567
+	 * @return void
2568
+	 * @throws EE_Error
2569
+	 * @throws InvalidArgumentException
2570
+	 * @throws ReflectionException
2571
+	 * @throws InvalidDataTypeException
2572
+	 * @throws InvalidInterfaceException
2573
+	 */
2574
+	protected function _set_shortcodes()
2575
+	{
2576
+
2577
+		// no need to run this if the property is already set
2578
+		if (! empty($this->_shortcodes)) {
2579
+			return;
2580
+		}
2581
+
2582
+		$this->_shortcodes = $this->_get_shortcodes();
2583
+	}
2584
+
2585
+
2586
+	/**
2587
+	 * gets all shortcodes for a given template group. (typically used by _set_shortcodes to set the $_shortcodes
2588
+	 * property)
2589
+	 *
2590
+	 * @access  protected
2591
+	 * @param array   $fields  include an array of specific field names that you want to be used to get the shortcodes
2592
+	 *                         for. Defaults to all (for the given context)
2593
+	 * @param boolean $merged  Whether to merge all the shortcodes into one list of unique shortcodes
2594
+	 * @return array Shortcodes indexed by fieldname and the an array of shortcode/label pairs OR if merged is
2595
+	 *                         true just an array of shortcode/label pairs.
2596
+	 * @throws EE_Error
2597
+	 * @throws InvalidArgumentException
2598
+	 * @throws ReflectionException
2599
+	 * @throws InvalidDataTypeException
2600
+	 * @throws InvalidInterfaceException
2601
+	 */
2602
+	protected function _get_shortcodes($fields = [], $merged = true)
2603
+	{
2604
+		$this->_set_message_template_group();
2605
+
2606
+		// we need the messenger and message template to retrieve the valid shortcodes array.
2607
+		$GRP_ID = $this->request->getRequestParam('id', 0, 'int');
2608
+		if (empty($GRP_ID)) {
2609
+			return [];
2610
+		}
2611
+		$context = $this->request->getRequestParam(
2612
+			'messenger',
2613
+			key($this->_message_template_group->contexts_config())
2614
+		);
2615
+		return $this->_message_template_group->get_shortcodes($context, $fields, $merged);
2616
+	}
2617
+
2618
+
2619
+	/**
2620
+	 * This sets the _message_template property (containing the called message_template object)
2621
+	 *
2622
+	 * @access protected
2623
+	 * @return void
2624
+	 * @throws EE_Error
2625
+	 * @throws InvalidArgumentException
2626
+	 * @throws ReflectionException
2627
+	 * @throws InvalidDataTypeException
2628
+	 * @throws InvalidInterfaceException
2629
+	 */
2630
+	protected function _set_message_template_group()
2631
+	{
2632
+		// get out if this is already set.
2633
+		if (! empty($this->_message_template_group)) {
2634
+			return;
2635
+		}
2636
+
2637
+		$GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
2638
+		$GRP_ID = $this->request->getRequestParam('id', $GRP_ID, 'int');
2639
+
2640
+		// let's get the message templates
2641
+		$this->_message_template_group = ! empty($GRP_ID)
2642
+			? $this->getMtgModel()->get_one_by_ID($GRP_ID)
2643
+			: $this->getMtgModel()->create_default_object();
2644
+
2645
+		$this->_template_pack = $this->_message_template_group->get_template_pack();
2646
+		$this->_variation     = $this->_message_template_group->get_template_pack_variation();
2647
+	}
2648
+
2649
+
2650
+	/**
2651
+	 * sets up a context switcher for edit forms
2652
+	 *
2653
+	 * @access  protected
2654
+	 * @param EE_Message_Template_Group $template_group_object the template group object being displayed on the form
2655
+	 * @param array                     $args                  various things the context switcher needs.
2656
+	 * @throws EE_Error
2657
+	 */
2658
+	protected function _set_context_switcher(EE_Message_Template_Group $template_group_object, $args)
2659
+	{
2660
+		$context_details = $template_group_object->contexts_config();
2661
+		$context_label   = $template_group_object->context_label();
2662
+		ob_start();
2663
+		?>
2664 2664
         <div class="ee-msg-switcher-container">
2665 2665
             <form method="get" action="<?php echo esc_url_raw(EE_MSG_ADMIN_URL); ?>" id="ee-msg-context-switcher-frm">
2666 2666
                 <?php
2667
-                foreach ($args as $name => $value) {
2668
-                    if ($name === 'context' || empty($value) || $name === 'extra') {
2669
-                        continue;
2670
-                    }
2671
-                    ?>
2667
+				foreach ($args as $name => $value) {
2668
+					if ($name === 'context' || empty($value) || $name === 'extra') {
2669
+						continue;
2670
+					}
2671
+					?>
2672 2672
                     <input type="hidden"
2673 2673
                            name="<?php echo esc_attr($name); ?>"
2674 2674
                            value="<?php echo esc_attr($value); ?>"
2675 2675
                     />
2676 2676
                     <?php
2677
-                }
2678
-                // setup nonce_url
2679
-                wp_nonce_field($args['action'] . '_nonce', $args['action'] . '_nonce', false);
2680
-                $id = 'ee-' . sanitize_key($context_label['label']) . '-select';
2681
-                ?>
2677
+				}
2678
+				// setup nonce_url
2679
+				wp_nonce_field($args['action'] . '_nonce', $args['action'] . '_nonce', false);
2680
+				$id = 'ee-' . sanitize_key($context_label['label']) . '-select';
2681
+				?>
2682 2682
                 <label for='<?php echo esc_attr($id); ?>' class='screen-reader-text'>
2683 2683
                     <?php esc_html_e('message context options', 'event_espresso'); ?>
2684 2684
                 </label>
2685 2685
                 <select id="<?php echo esc_attr($id); ?>" name="context">
2686 2686
                     <?php
2687
-                    $context_templates = $template_group_object->context_templates();
2688
-                    if (is_array($context_templates)) :
2689
-                        foreach ($context_templates as $context => $template_fields) :
2690
-                            $checked = ($context === $args['context']) ? 'selected' : '';
2691
-                            ?>
2687
+					$context_templates = $template_group_object->context_templates();
2688
+					if (is_array($context_templates)) :
2689
+						foreach ($context_templates as $context => $template_fields) :
2690
+							$checked = ($context === $args['context']) ? 'selected' : '';
2691
+							?>
2692 2692
                             <option value="<?php echo esc_attr($context); ?>" <?php echo esc_attr($checked); ?>>
2693 2693
                                 <?php echo esc_html($context_details[ $context ]['label']); ?>
2694 2694
                             </option>
2695 2695
                         <?php endforeach;
2696
-                    endif; ?>
2696
+					endif; ?>
2697 2697
                 </select>
2698 2698
                 <?php $button_text = sprintf(
2699
-                    esc_html__('Switch %s', 'event_espresso'),
2700
-                    ucwords($context_label['label'])
2701
-                ); ?>
2699
+					esc_html__('Switch %s', 'event_espresso'),
2700
+					ucwords($context_label['label'])
2701
+				); ?>
2702 2702
                 <input class='button--secondary'
2703 2703
                        id="submit-msg-context-switcher-sbmt"
2704 2704
                        type="submit"
@@ -2708,1993 +2708,1993 @@  discard block
 block discarded – undo
2708 2708
             <?php echo wp_kses($args['extra'], AllowedTags::getWithFormTags()); ?>
2709 2709
         </div> <!-- end .ee-msg-switcher-container -->
2710 2710
         <?php $this->_context_switcher = ob_get_clean();
2711
-    }
2712
-
2713
-
2714
-    /**
2715
-     * @param bool $new
2716
-     * @throws EE_Error
2717
-     * @throws ReflectionException
2718
-     */
2719
-    protected function _insert_or_update_message_template($new = false)
2720
-    {
2721
-        $form_data    = $this->getMessageTemplateFormData();
2722
-        $GRP_ID       = $form_data['GRP_ID'];
2723
-        $messenger    = $form_data['MTP_messenger'];
2724
-        $message_type = $form_data['MTP_message_type'];
2725
-        $context      = $form_data['MTP_context'];
2726
-
2727
-        // if this is "new" then we need to generate the default contexts
2728
-        // for the selected messenger/message_type for user to edit.
2729
-        [$success, $query_args] = $new
2730
-            ? $this->generateNewTemplates($GRP_ID, $messenger, $message_type)
2731
-            : $this->updateExistingTemplates($GRP_ID, $messenger, $message_type, $context, $form_data);
2732
-
2733
-        $success     = $success ? 1 : 0;
2734
-        $action_desc = $new ? 'created' : 'updated';
2735
-        $item_desc   = $this->generateUpdateDescription($messenger, $message_type, $context);
2736
-        $override    = $this->performTestSendAfterUpdate($messenger, $message_type, $context);
2737
-
2738
-        $this->_redirect_after_action($success, $item_desc, $action_desc, $query_args, $override);
2739
-    }
2740
-
2741
-
2742
-    /**
2743
-     * retrieve and sanitize form data
2744
-     *
2745
-     * @return array
2746
-     * @since 4.10.29.p
2747
-     */
2748
-    protected function getMessageTemplateFormData()
2749
-    {
2750
-        return [
2751
-            'GRP_ID'           => $this->request->getRequestParam('GRP_ID', 0, 'int'),
2752
-            'MTP_context'      => strtolower($this->request->getRequestParam('MTP_context', '')),
2753
-            'MTP_messenger'    => strtolower($this->request->getRequestParam('MTP_messenger', '')),
2754
-            'MTP_message_type' => strtolower($this->request->getRequestParam('MTP_message_type', '')),
2755
-            'MTP_user_id'      => $this->request->getRequestParam('MTP_user_id', 0, 'int'),
2756
-            'MTP_is_global'    => $this->request->getRequestParam('MTP_is_global', 0, 'int'),
2757
-            'MTP_is_override'  => $this->request->getRequestParam('MTP_is_override', 0, 'int'),
2758
-            'MTP_deleted'      => $this->request->getRequestParam('MTP_deleted', 0, 'int'),
2759
-            'MTP_is_active'    => $this->request->getRequestParam('MTP_is_active', 0, 'int'),
2760
-        ];
2761
-    }
2762
-
2763
-
2764
-    /**
2765
-     * @param int    $GRP_ID
2766
-     * @param string $messenger
2767
-     * @param string $message_type
2768
-     * @return array no return on AJAX requests
2769
-     * @throws EE_Error
2770
-     * @throws ReflectionException
2771
-     * @since 4.10.29.p
2772
-     */
2773
-    private function generateNewTemplates($GRP_ID, $messenger, $message_type)
2774
-    {
2775
-        $new_templates = $this->_generate_new_templates($messenger, [$message_type], $GRP_ID);
2776
-        $success       = ! empty($new_templates);
2777
-
2778
-        // we return things differently if doing ajax
2779
-        if ($this->request->isAjax()) {
2780
-            $this->_template_args['success'] = $success;
2781
-            $this->_template_args['error']   = ! $success;
2782
-            $this->_template_args['content'] = '';
2783
-            $this->_template_args['data']    = [
2784
-                'grpID'        => $new_templates['GRP_ID'],
2785
-                'templateName' => $new_templates['template_name'],
2786
-            ];
2787
-            if ($success) {
2788
-                EE_Error::overwrite_success();
2789
-                EE_Error::add_success(
2790
-                    esc_html__(
2791
-                        'The new template has been created and automatically selected for this event.  You can edit the new template by clicking the edit button.  Note before this template is assigned to this event, the event must be saved.',
2792
-                        'event_espresso'
2793
-                    )
2794
-                );
2795
-            }
2796
-            $this->_return_json();
2797
-        }
2798
-        return [
2799
-            $success,
2800
-            // 'query_args'
2801
-            [
2802
-                'id'      => $new_templates['GRP_ID'],
2803
-                'context' => $new_templates['MTP_context'],
2804
-                'action'  => 'edit_message_template',
2805
-            ],
2806
-        ];
2807
-    }
2808
-
2809
-
2810
-    /**
2811
-     * @param int    $GRP_ID
2812
-     * @param string $messenger
2813
-     * @param string $message_type
2814
-     * @param string $context
2815
-     * @param array  $form_data
2816
-     * @return array
2817
-     * @throws EE_Error
2818
-     * @since 4.10.29.p
2819
-     */
2820
-    private function updateExistingTemplates(
2821
-        $GRP_ID,
2822
-        $messenger,
2823
-        $message_type,
2824
-        $context,
2825
-        array $form_data
2826
-    ) {
2827
-        $success         = false;
2828
-        $template_fields = $this->getTemplateFields();
2829
-        if ($template_fields) {
2830
-            // if field data is valid, then success will be true
2831
-            $success = $this->validateTemplateFields(
2832
-                $messenger,
2833
-                $message_type,
2834
-                $context,
2835
-                $template_fields
2836
-            );
2837
-            if ($success) {
2838
-                $field_data = [];
2839
-                foreach ($template_fields as $template_field => $content) {
2840
-                    // combine top-level form data with content for this field
2841
-                    $field_data = $this->getTemplateFieldFormData($content, $form_data);
2842
-                    $success    = $this->updateMessageTemplates($template_field, $field_data) ? $success : false;
2843
-                }
2844
-                // we can use the last set_column_values for the MTPG update
2845
-                // (because its the same for all of these specific MTPs)
2846
-                $success = $this->updateMessageTemplateGroup($field_data) ? $success : false;
2847
-            }
2848
-        }
2849
-
2850
-        return [
2851
-            $success,
2852
-            // 'query_args'
2853
-            [
2854
-                'id'      => $GRP_ID,
2855
-                'context' => $context,
2856
-                'action'  => 'edit_message_template',
2857
-            ],
2858
-        ];
2859
-    }
2860
-
2861
-
2862
-    /**
2863
-     * @return array
2864
-     * @since 4.10.29.p
2865
-     */
2866
-    private function getTemplateFields()
2867
-    {
2868
-        $template_fields = $this->request->getRequestParam('MTP_template_fields', null, 'html', true);
2869
-        if (empty($template_fields)) {
2870
-            EE_Error::add_error(
2871
-                esc_html__(
2872
-                    'There was a problem saving the template fields from the form because I didn\'t receive any actual template field data.',
2873
-                    'event_espresso'
2874
-                ),
2875
-                __FILE__,
2876
-                __FUNCTION__,
2877
-                __LINE__
2878
-            );
2879
-            return null;
2880
-        }
2881
-        // messages content is expected to be escaped
2882
-        return EEH_Array::addSlashesRecursively($template_fields);
2883
-    }
2884
-
2885
-
2886
-    /**
2887
-     * @param string $messenger
2888
-     * @param string $message_type
2889
-     * @param string $context
2890
-     * @param array  $template_fields
2891
-     * @return bool
2892
-     * @throws EE_Error
2893
-     * @since   4.10.29.p
2894
-     */
2895
-    private function validateTemplateFields(
2896
-        $messenger,
2897
-        $message_type,
2898
-        $context,
2899
-        array $template_fields
2900
-    ) {
2901
-        // first validate all fields!
2902
-        // this filter allows client code to add its own validation to the template fields as well.
2903
-        // returning an empty array means everything passed validation.
2904
-        // errors in validation should be represented in an array with the following shape:
2905
-        // array(
2906
-        //   'fieldname' => array(
2907
-        //          'msg' => 'error message'
2908
-        //          'value' => 'value for field producing error'
2909
-        // )
2910
-        $custom_validation = (array) apply_filters(
2911
-            'FHEE__Messages_Admin_Page___insert_or_update_message_template__validates',
2912
-            [],
2913
-            $template_fields,
2914
-            $context,
2915
-            $messenger,
2916
-            $message_type
2917
-        );
2918
-
2919
-        $system_validation = $this->getMtgModel()->validate(
2920
-            $template_fields,
2921
-            $context,
2922
-            $messenger,
2923
-            $message_type
2924
-        );
2925
-
2926
-        $system_validation = ! is_array($system_validation) && $system_validation ? [] : $system_validation;
2927
-        $validates         = array_merge($custom_validation, $system_validation);
2928
-
2929
-        // if $validate returned error messages (i.e. is_array()) then we need to process them and setup an
2930
-        // appropriate response. HMM, dang this isn't correct, $validates will ALWAYS be an array.
2931
-        //  WE need to make sure there is no actual error messages in validates.
2932
-        if (empty($validates)) {
2933
-            return true;
2934
-        }
2935
-
2936
-        // add the transient so when the form loads we know which fields to highlight
2937
-        $this->_add_transient('edit_message_template', $validates);
2938
-        // setup notices
2939
-        foreach ($validates as $error) {
2940
-            if (isset($error['msg'])) {
2941
-                EE_Error::add_error($error['msg'], __FILE__, __FUNCTION__, __LINE__);
2942
-            }
2943
-        }
2944
-        return false;
2945
-    }
2946
-
2947
-
2948
-    /**
2949
-     * @param array $field_data
2950
-     * @param array $form_data
2951
-     * @return array
2952
-     * @since   4.10.29.p
2953
-     */
2954
-    private function getTemplateFieldFormData(array $field_data, array $form_data)
2955
-    {
2956
-        return $form_data + [
2957
-                'MTP_ID'             => $field_data['MTP_ID'],
2958
-                'MTP_template_field' => $field_data['name'],
2959
-                // if they aren't allowed to use all JS, restrict them to standard allowed post tags
2960
-                'MTP_content'        => ! current_user_can('unfiltered_html')
2961
-                    ? $this->sanitizeMessageTemplateContent($field_data['content'])
2962
-                    : $field_data['content'],
2963
-            ];
2964
-    }
2965
-
2966
-
2967
-    /**
2968
-     * @param string $template_field
2969
-     * @param array  $form_data
2970
-     * @return bool
2971
-     * @throws EE_Error
2972
-     * @since 4.10.29.p
2973
-     */
2974
-    private function updateMessageTemplates($template_field, array $form_data)
2975
-    {
2976
-        $MTP_ID                  = $form_data['MTP_ID'];
2977
-        $message_template_fields = [
2978
-            'GRP_ID'             => $form_data['GRP_ID'],
2979
-            'MTP_template_field' => $form_data['MTP_template_field'],
2980
-            'MTP_context'        => $form_data['MTP_context'],
2981
-            'MTP_content'        => $form_data['MTP_content'],
2982
-        ];
2983
-
2984
-        $hasMtpID = ! empty($MTP_ID);
2985
-        // if we have a MTP_ID for this field then update it, otherwise insert.
2986
-        // this has already been through the template field validator and sanitized, so it will be
2987
-        // safe to insert this field.  Why insert?  This typically happens when we introduce a new
2988
-        // message template field in a messenger/message type and existing users don't have the
2989
-        // default setup for it.
2990
-        // @link https://events.codebasehq.com/projects/event-espresso/tickets/9465
2991
-        $updated = $hasMtpID
2992
-            ? $this->getMtpModel()->update($message_template_fields, [['MTP_ID' => $MTP_ID]])
2993
-            : $this->getMtpModel()->insert($message_template_fields);
2994
-
2995
-        $insert_failed = ! $hasMtpID && ! $updated;
2996
-        // updates will return 0 if the field was not changed (ie: no changes = nothing actually updated)
2997
-        // but we won't consider that a problem, but if it returns false, then something went BOOM!
2998
-        $update_failed = $hasMtpID && $updated === false;
2999
-
3000
-        if ($insert_failed || $update_failed) {
3001
-            EE_Error::add_error(
3002
-                sprintf(
3003
-                    esc_html__('%s field was NOT updated for some reason', 'event_espresso'),
3004
-                    $template_field
3005
-                ),
3006
-                __FILE__,
3007
-                __FUNCTION__,
3008
-                __LINE__
3009
-            );
3010
-            return false;
3011
-        }
3012
-        return true;
3013
-    }
3014
-
3015
-
3016
-    /**
3017
-     * @param array $form_data
3018
-     * @return bool
3019
-     * @throws EE_Error
3020
-     * @since 4.10.29.p
3021
-     */
3022
-    private function updateMessageTemplateGroup(array $form_data)
3023
-    {
3024
-        $GRP_ID  = $form_data['GRP_ID'];
3025
-        $updated = $this->getMtgModel()->update(
3026
-        // fields and values
3027
-            [
3028
-                'MTP_user_id'      => $form_data['MTP_user_id'],
3029
-                'MTP_messenger'    => $form_data['MTP_messenger'],
3030
-                'MTP_message_type' => $form_data['MTP_message_type'],
3031
-                'MTP_is_global'    => $form_data['MTP_is_global'],
3032
-                'MTP_is_override'  => $form_data['MTP_is_override'],
3033
-                'MTP_deleted'      => $form_data['MTP_deleted'],
3034
-                'MTP_is_active'    => $form_data['MTP_is_active'],
3035
-                'MTP_name'         => $this->request->getRequestParam('ee_msg_non_global_fields[MTP_name]', ''),
3036
-                'MTP_description'  => $this->request->getRequestParam(
3037
-                    'ee_msg_non_global_fields[MTP_description]',
3038
-                    ''
3039
-                ),
3040
-            ],
3041
-            // where
3042
-            [['GRP_ID' => $GRP_ID]]
3043
-        );
3044
-
3045
-        if ($updated === false) {
3046
-            EE_Error::add_error(
3047
-                sprintf(
3048
-                    esc_html__(
3049
-                        'The Message Template Group (%d) was NOT updated for some reason',
3050
-                        'event_espresso'
3051
-                    ),
3052
-                    $form_data['GRP_ID']
3053
-                ),
3054
-                __FILE__,
3055
-                __FUNCTION__,
3056
-                __LINE__
3057
-            );
3058
-            return false;
3059
-        }
3060
-        // k now we need to ensure the template_pack and template_variation fields are set.
3061
-        $template_pack      = $this->request->getRequestParam('MTP_template_pack', 'default');
3062
-        $template_variation = $this->request->getRequestParam('MTP_template_variation', 'default');
3063
-
3064
-        $message_template_group = $this->getMtgModel()->get_one_by_ID($GRP_ID);
3065
-        if ($message_template_group instanceof EE_Message_Template_Group) {
3066
-            $message_template_group->set_template_pack_name($template_pack);
3067
-            $message_template_group->set_template_pack_variation($template_variation);
3068
-        }
3069
-        return true;
3070
-    }
3071
-
3072
-
3073
-    /**
3074
-     * recursively runs wp_kses() on message template content in a model safe manner
3075
-     *
3076
-     * @param array|string $content
3077
-     * @return array|string
3078
-     * @since   4.10.29.p
3079
-     */
3080
-    private function sanitizeMessageTemplateContent($content)
3081
-    {
3082
-        if (is_array($content)) {
3083
-            foreach ($content as $key => $value) {
3084
-                $content[ $key ] = $this->sanitizeMessageTemplateContent($value);
3085
-            }
3086
-            return $content;
3087
-        }
3088
-        // remove slashes so wp_kses() works properly
3089
-        // wp_kses_stripslashes() only removes slashes from double-quotes,
3090
-        // so attributes using single quotes always appear invalid.
3091
-        $content = stripslashes($content);
3092
-        $content = wp_kses($content, wp_kses_allowed_html('post'));
3093
-        // But currently the models expect slashed data, so after wp_kses()
3094
-        // runs we need to re-slash the data. Sheesh.
3095
-        // See https://events.codebasehq.com/projects/event-espresso/tickets/11211#update-47321587
3096
-        return addslashes($content);
3097
-    }
3098
-
3099
-
3100
-    /**
3101
-     * @param string $messenger
3102
-     * @param string $message_type
3103
-     * @param string $context
3104
-     * @return string
3105
-     * @since 4.10.29.p
3106
-     */
3107
-    private function generateUpdateDescription($messenger, $message_type, $context)
3108
-    {
3109
-        // need the message type and messenger objects to be able to use the labels for the notices
3110
-        $messenger_object = $this->_message_resource_manager->get_messenger($messenger);
3111
-        $messenger_label  = $messenger_object instanceof EE_messenger
3112
-            ? ucwords($messenger_object->label['singular'])
3113
-            : '';
3114
-
3115
-        $message_type_object = $this->_message_resource_manager->get_message_type($message_type);
3116
-        $message_type_label  = $message_type_object instanceof EE_message_type
3117
-            ? ucwords($message_type_object->label['singular'])
3118
-            : '';
3119
-
3120
-        $context   = ucwords(str_replace('_', ' ', $context));
3121
-        $item_desc = $messenger_label && $message_type_label
3122
-            ? $messenger_label . ' ' . $message_type_label . ' ' . $context . ' '
3123
-            : '';
3124
-        $item_desc .= 'Message Template';
3125
-        return $item_desc;
3126
-    }
3127
-
3128
-
3129
-    /**
3130
-     * @param string $messenger
3131
-     * @param string $message_type
3132
-     * @param string $context
3133
-     * @return bool
3134
-     * @throws EE_Error
3135
-     * @throws ReflectionException
3136
-     * @since 4.10.29.p
3137
-     */
3138
-    private function performTestSendAfterUpdate($messenger, $message_type, $context)
3139
-    {
3140
-        // was a test send triggered?
3141
-        if ($this->request->requestParamIsSet('test_button')) {
3142
-            EE_Error::overwrite_success();
3143
-            $this->_do_test_send($context, $messenger, $message_type);
3144
-            return true;
3145
-        }
3146
-        return false;
3147
-    }
3148
-
3149
-
3150
-    /**
3151
-     * processes a test send request to do an actual messenger delivery test for the given message template being tested
3152
-     *
3153
-     * @param string $context      what context being tested
3154
-     * @param string $messenger    messenger being tested
3155
-     * @param string $message_type message type being tested
3156
-     * @throws EE_Error
3157
-     * @throws InvalidArgumentException
3158
-     * @throws InvalidDataTypeException
3159
-     * @throws InvalidInterfaceException
3160
-     * @throws ReflectionException
3161
-     */
3162
-    protected function _do_test_send($context, $messenger, $message_type)
3163
-    {
3164
-        // set things up for preview
3165
-        $this->request->setRequestParam('messenger', $messenger);
3166
-        $this->request->setRequestParam('message_type', $message_type);
3167
-        $this->request->setRequestParam('context', $context);
3168
-        $GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
3169
-        $this->request->setRequestParam('GRP_ID', $GRP_ID);
3170
-
3171
-        $active_messenger  = $this->_message_resource_manager->get_active_messenger($messenger);
3172
-        $test_settings_fld = $this->request->getRequestParam('test_settings_fld', [], 'string', true);
3173
-
3174
-        // let's save any existing fields that might be required by the messenger
3175
-        if (
3176
-            ! empty($test_settings_fld)
3177
-            && $active_messenger instanceof EE_messenger
3178
-            && apply_filters(
3179
-                'FHEE__Messages_Admin_Page__do_test_send__set_existing_test_settings',
3180
-                true,
3181
-                $test_settings_fld,
3182
-                $active_messenger
3183
-            )
3184
-        ) {
3185
-            $active_messenger->set_existing_test_settings($test_settings_fld);
3186
-        }
3187
-
3188
-        /**
3189
-         * Use filter to add additional controls on whether message can send or not
3190
-         */
3191
-        if (
3192
-            apply_filters(
3193
-                'FHEE__Messages_Admin_Page__do_test_send__can_send',
3194
-                true,
3195
-                $context,
3196
-                $this->request->requestParams(),
3197
-                $messenger,
3198
-                $message_type
3199
-            )
3200
-        ) {
3201
-            if (EEM_Event::instance()->count() > 0) {
3202
-                $success = $this->_preview_message(true);
3203
-                if ($success) {
3204
-                    EE_Error::add_success(esc_html__('Test message sent', 'event_espresso'));
3205
-                } else {
3206
-                    EE_Error::add_error(
3207
-                        esc_html__('The test message was not sent', 'event_espresso'),
3208
-                        __FILE__,
3209
-                        __FUNCTION__,
3210
-                        __LINE__
3211
-                    );
3212
-                }
3213
-            } else {
3214
-                $this->noEventsErrorMessage(true);
3215
-            }
3216
-        }
3217
-    }
3218
-
3219
-
3220
-    /**
3221
-     * _generate_new_templates
3222
-     * This will handle the messenger, message_type selection when "adding a new custom template" for an event and will
3223
-     * automatically create the defaults for the event.  The user would then be redirected to edit the default context
3224
-     * for the event.
3225
-     *
3226
-     *
3227
-     * @param string $messenger      the messenger we are generating templates for
3228
-     * @param array  $message_types  array of message types that the templates are generated for.
3229
-     * @param int    $GRP_ID         If this is a custom template being generated then a GRP_ID needs to be included to
3230
-     *                               indicate the message_template_group being used as the base.
3231
-     *
3232
-     * @param bool   $global
3233
-     *
3234
-     * @return array|bool array of data required for the redirect to the correct edit page or bool if
3235
-     *                               encountering problems.
3236
-     * @throws EE_Error
3237
-     * @throws ReflectionException
3238
-     */
3239
-    protected function _generate_new_templates($messenger, $message_types, $GRP_ID = 0, $global = false)
3240
-    {
3241
-        // if no $message_types are given then that's okay... this may be a messenger that just adds shortcodes, so we
3242
-        // just don't generate any templates.
3243
-        if (empty($message_types)) {
3244
-            return [];
3245
-        }
3246
-
3247
-        $templates = EEH_MSG_Template::generate_new_templates($messenger, $message_types, $GRP_ID, $global);
3248
-        return $templates[0];
3249
-    }
3250
-
3251
-
3252
-    /**
3253
-     * [_trash_or_restore_message_template]
3254
-     *
3255
-     * @param boolean $trash  whether to move an item to trash/restore (TRUE) or restore it (FALSE)
3256
-     * @param boolean $all    whether this is going to trash/restore all contexts within a template group (TRUE) OR just
3257
-     *                        an individual context (FALSE).
3258
-     * @return void
3259
-     * @throws EE_Error
3260
-     * @throws InvalidArgumentException
3261
-     * @throws InvalidDataTypeException
3262
-     * @throws InvalidInterfaceException
3263
-     */
3264
-    protected function _trash_or_restore_message_template($trash = true, $all = false)
3265
-    {
3266
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3267
-
3268
-        $success = 1;
3269
-
3270
-        // incoming GRP_IDs
3271
-        if ($all) {
3272
-            // Checkboxes
3273
-            $checkboxes = $this->request->getRequestParam('checkbox', [], 'int', true);
3274
-            if (! empty($checkboxes)) {
3275
-                // if array has more than one element then success message should be plural.
3276
-                // todo: what about nonce?
3277
-                $success = count($checkboxes) > 1 ? 2 : 1;
3278
-
3279
-                // cycle through checkboxes
3280
-                while (list($GRP_ID, $value) = each($checkboxes)) {
3281
-                    $trashed_or_restored = $trash
3282
-                        ? $this->getMtgModel()->delete_by_ID($GRP_ID)
3283
-                        : $this->getMtgModel()->restore_by_ID($GRP_ID);
3284
-                    if (! $trashed_or_restored) {
3285
-                        $success = 0;
3286
-                    }
3287
-                }
3288
-            } else {
3289
-                // grab single GRP_ID and handle
3290
-                $GRP_ID = $this->request->getRequestParam('id', 0, 'int');
3291
-                if (! empty($GRP_ID)) {
3292
-                    $trashed_or_restored = $trash
3293
-                        ? $this->getMtgModel()->delete_by_ID($GRP_ID)
3294
-                        : $this->getMtgModel()->restore_by_ID($GRP_ID);
3295
-                    if (! $trashed_or_restored) {
3296
-                        $success = 0;
3297
-                    }
3298
-                } else {
3299
-                    $success = 0;
3300
-                }
3301
-            }
3302
-        }
3303
-
3304
-        $action_desc = $trash
3305
-            ? esc_html__('moved to the trash', 'event_espresso')
3306
-            : esc_html__('restored', 'event_espresso');
3307
-
3308
-        $template_switch = $this->request->getRequestParam('template_switch', false, 'bool');
3309
-        $action_desc     = $template_switch ? esc_html__('switched', 'event_espresso') : $action_desc;
3310
-
3311
-        $item_desc = $all ? _n(
3312
-            'Message Template Group',
3313
-            'Message Template Groups',
3314
-            $success,
3315
-            'event_espresso'
3316
-        ) : _n('Message Template Context', 'Message Template Contexts', $success, 'event_espresso');
3317
-
3318
-        $item_desc = $template_switch
3319
-            ? _n('template', 'templates', $success, 'event_espresso')
3320
-            : $item_desc;
3321
-
3322
-        $this->_redirect_after_action($success, $item_desc, $action_desc, []);
3323
-    }
3324
-
3325
-
3326
-    /**
3327
-     * [_delete_message_template]
3328
-     * NOTE: this handles not only the deletion of the groups but also all the templates belonging to that group.
3329
-     *
3330
-     * @return void
3331
-     * @throws EE_Error
3332
-     * @throws InvalidArgumentException
3333
-     * @throws InvalidDataTypeException
3334
-     * @throws InvalidInterfaceException
3335
-     * @throws ReflectionException
3336
-     */
3337
-    protected function _delete_message_template()
3338
-    {
3339
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3340
-
3341
-        // checkboxes
3342
-        $checkboxes = $this->request->getRequestParam('checkbox', [], 'int', true);
3343
-        if (! empty($checkboxes)) {
3344
-            // if array has more than one element then success message should be plural
3345
-            $success = count($checkboxes) > 1 ? 2 : 1;
3346
-
3347
-            // cycle through bulk action checkboxes
3348
-            while (list($GRP_ID, $value) = each($checkboxes)) {
3349
-                $success = $this->_delete_mtp_permanently($GRP_ID) ? $success : false;
3350
-            }
3351
-        } else {
3352
-            // grab single grp_id and delete
3353
-            $GRP_ID  = $this->request->getRequestParam('id', 0, 'int');
3354
-            $success = $this->_delete_mtp_permanently($GRP_ID);
3355
-        }
3356
-
3357
-        $this->_redirect_after_action($success, 'Message Templates', 'deleted', []);
3358
-    }
3359
-
3360
-
3361
-    /**
3362
-     * helper for permanently deleting a mtP group and all related message_templates
3363
-     *
3364
-     * @param int  $GRP_ID        The group being deleted
3365
-     * @param bool $include_group whether to delete the Message Template Group as well.
3366
-     * @return bool boolean to indicate the success of the deletes or not.
3367
-     * @throws EE_Error
3368
-     * @throws InvalidArgumentException
3369
-     * @throws InvalidDataTypeException
3370
-     * @throws InvalidInterfaceException
3371
-     * @throws ReflectionException
3372
-     * @throws ReflectionException
3373
-     */
3374
-    private function _delete_mtp_permanently($GRP_ID, $include_group = true)
3375
-    {
3376
-        $success = true;
3377
-        // first let's GET this group
3378
-        $MTG = $this->getMtgModel()->get_one_by_ID($GRP_ID);
3379
-        // then delete permanently all the related Message Templates
3380
-        $deleted = $MTG->delete_related_permanently('Message_Template');
3381
-
3382
-        if ($deleted === 0) {
3383
-            $success = false;
3384
-        }
3385
-
3386
-        // now delete permanently this particular group
3387
-
3388
-        if ($include_group && ! $MTG->delete_permanently()) {
3389
-            $success = false;
3390
-        }
3391
-
3392
-        return $success;
3393
-    }
3394
-
3395
-
3396
-    /**
3397
-     *    _learn_more_about_message_templates_link
3398
-     *
3399
-     * @access protected
3400
-     * @return string
3401
-     */
3402
-    protected function _learn_more_about_message_templates_link()
3403
-    {
3404
-        return '<a class="hidden" style="margin:0 20px; cursor:pointer; font-size:12px;" >'
3405
-               . esc_html__('learn more about how message templates works', 'event_espresso')
3406
-               . '</a>';
3407
-    }
3408
-
3409
-
3410
-    /**
3411
-     * Used for setting up messenger/message type activation.  This loads up the initial view.  The rest is handled by
3412
-     * ajax and other routes.
3413
-     *
3414
-     * @return void
3415
-     * @throws DomainException
3416
-     * @throws EE_Error
3417
-     */
3418
-    protected function _settings()
3419
-    {
3420
-        $this->_set_m_mt_settings();
3421
-
3422
-        // let's setup the messenger tabs
3423
-        $this->_template_args['admin_page_header'] = EEH_Tabbed_Content::tab_text_links(
3424
-            $this->_m_mt_settings['messenger_tabs'],
3425
-            'messenger_links',
3426
-            '|',
3427
-            $this->request->getRequestParam('selected_messenger', 'email')
3428
-        );
3429
-
3430
-        $this->_template_args['before_admin_page_content'] = '<div class="ui-widget ui-helper-clearfix">';
3431
-        $this->_template_args['after_admin_page_content']  = '</div><!-- end .ui-widget -->';
3432
-
3433
-        $this->display_admin_page_with_sidebar();
3434
-    }
3435
-
3436
-
3437
-    /**
3438
-     * This sets the $_m_mt_settings property for when needed (used on the Messages settings page)
3439
-     *
3440
-     * @access protected
3441
-     * @return void
3442
-     * @throws DomainException
3443
-     */
3444
-    protected function _set_m_mt_settings()
3445
-    {
3446
-        // first if this is already set then lets get out no need to regenerate data.
3447
-        if (! empty($this->_m_mt_settings)) {
3448
-            return;
3449
-        }
3450
-
3451
-        // get all installed messengers and message_types
3452
-        $messengers    = $this->_message_resource_manager->installed_messengers();
3453
-        $message_types = $this->_message_resource_manager->installed_message_types();
3454
-
3455
-
3456
-        // assemble the array for the _tab_text_links helper
3457
-
3458
-        foreach ($messengers as $messenger) {
3459
-            $active = $this->_message_resource_manager->is_messenger_active($messenger->name);
3460
-            $class = 'ee-messenger-' .  sanitize_key($messenger->label['singular']);
3461
-            $this->_m_mt_settings['messenger_tabs'][ $messenger->name ] = [
3462
-                'label' => ucwords($messenger->label['singular']),
3463
-                'class' => $active ? "{$class} messenger-active" : $class,
3464
-                'href'  => $messenger->name,
3465
-                'title' => esc_html__('Modify this Messenger', 'event_espresso'),
3466
-                'slug'  => $messenger->name,
3467
-                'obj'   => $messenger,
3468
-                'icon' => $active
3469
-                    ? '<span class="dashicons dashicons-yes-alt"></span>'
3470
-                    : '<span class="dashicons dashicons-remove"></span>',
3471
-            ];
3472
-
3473
-
3474
-            $message_types_for_messenger = $messenger->get_valid_message_types();
3475
-
3476
-            foreach ($message_types as $message_type) {
3477
-                // first we need to verify that this message type is valid with this messenger. Cause if it isn't then
3478
-                // it shouldn't show in either the inactive OR active metabox.
3479
-                if (! in_array($message_type->name, $message_types_for_messenger, true)) {
3480
-                    continue;
3481
-                }
3482
-
3483
-                $a_or_i = $this->_message_resource_manager->is_message_type_active_for_messenger(
3484
-                    $messenger->name,
3485
-                    $message_type->name
3486
-                )
3487
-                    ? 'active'
3488
-                    : 'inactive';
3489
-
3490
-                $this->_m_mt_settings['message_type_tabs'][ $messenger->name ][ $a_or_i ][ $message_type->name ] = [
3491
-                    'label'    => ucwords($message_type->label['singular']),
3492
-                    'class'    => 'message-type-' . $a_or_i,
3493
-                    'slug_id'  => $message_type->name . '-messagetype-' . $messenger->name,
3494
-                    'mt_nonce' => wp_create_nonce($message_type->name . '_nonce'),
3495
-                    'href'     => 'espresso_' . $message_type->name . '_message_type_settings',
3496
-                    'title'    => $a_or_i === 'active'
3497
-                        ? esc_html__('Drag this message type to the Inactive window to deactivate', 'event_espresso')
3498
-                        : esc_html__('Drag this message type to the messenger to activate', 'event_espresso'),
3499
-                    'content'  => $a_or_i === 'active'
3500
-                        ? $this->_message_type_settings_content($message_type, $messenger, true)
3501
-                        : $this->_message_type_settings_content($message_type, $messenger),
3502
-                    'slug'     => $message_type->name,
3503
-                    'active'   => $a_or_i === 'active',
3504
-                    'obj'      => $message_type,
3505
-                ];
3506
-            }
3507
-        }
3508
-    }
3509
-
3510
-
3511
-    /**
3512
-     * This just prepares the content for the message type settings
3513
-     *
3514
-     * @param EE_message_type $message_type The message type object
3515
-     * @param EE_messenger    $messenger    The messenger object
3516
-     * @param boolean         $active       Whether the message type is active or not
3517
-     * @return string html output for the content
3518
-     * @throws DomainException
3519
-     */
3520
-    protected function _message_type_settings_content($message_type, $messenger, $active = false)
3521
-    {
3522
-        // get message type fields
3523
-        $fields                                         = $message_type->get_admin_settings_fields();
3524
-        $settings_template_args['template_form_fields'] = '';
3525
-
3526
-        if (! empty($fields) && $active) {
3527
-            $existing_settings = $message_type->get_existing_admin_settings($messenger->name);
3528
-            foreach ($fields as $fldname => $fldprops) {
3529
-                $field_id                         = $messenger->name . '-' . $message_type->name . '-' . $fldname;
3530
-                $template_form_field[ $field_id ] = [
3531
-                    'name'       => 'message_type_settings[' . $fldname . ']',
3532
-                    'label'      => $fldprops['label'],
3533
-                    'input'      => $fldprops['field_type'],
3534
-                    'type'       => $fldprops['value_type'],
3535
-                    'required'   => $fldprops['required'],
3536
-                    'validation' => $fldprops['validation'],
3537
-                    'value'      => isset($existing_settings[ $fldname ])
3538
-                        ? $existing_settings[ $fldname ]
3539
-                        : $fldprops['default'],
3540
-                    'options'    => isset($fldprops['options'])
3541
-                        ? $fldprops['options']
3542
-                        : [],
3543
-                    'default'    => isset($existing_settings[ $fldname ])
3544
-                        ? $existing_settings[ $fldname ]
3545
-                        : $fldprops['default'],
3546
-                    'css_class'  => 'no-drag',
3547
-                    'format'     => $fldprops['format'],
3548
-                ];
3549
-            }
3550
-
3551
-
3552
-            $settings_template_args['template_form_fields'] = ! empty($template_form_field)
3553
-                ? $this->_generate_admin_form_fields(
3554
-                    $template_form_field,
3555
-                    'string',
3556
-                    'ee_mt_activate_form'
3557
-                )
3558
-                : '';
3559
-        }
3560
-
3561
-        $settings_template_args['description'] = $message_type->description;
3562
-        // we also need some hidden fields
3563
-        $hidden_fields = [
3564
-            'message_type_settings[messenger]' . $message_type->name    => [
3565
-                'type'  => 'hidden',
3566
-                'value' => $messenger->name,
3567
-            ],
3568
-            'message_type_settings[message_type]' . $message_type->name => [
3569
-                'type'  => 'hidden',
3570
-                'value' => $message_type->name,
3571
-            ],
3572
-            'type' . $message_type->name                                => [
3573
-                'type'  => 'hidden',
3574
-                'value' => 'message_type',
3575
-            ],
3576
-        ];
3577
-
3578
-        $settings_template_args['hidden_fields'] = $this->_generate_admin_form_fields(
3579
-            $hidden_fields,
3580
-            'array'
3581
-        );
3582
-        $settings_template_args['show_form']     = empty($settings_template_args['template_form_fields'])
3583
-            ? ' hidden'
3584
-            : '';
3585
-
3586
-
3587
-        $template = EE_MSG_TEMPLATE_PATH . 'ee_msg_mt_settings_content.template.php';
3588
-        return EEH_Template::display_template($template, $settings_template_args, true);
3589
-    }
3590
-
3591
-
3592
-    /**
3593
-     * Generate all the metaboxes for the message types and register them for the messages settings page.
3594
-     *
3595
-     * @access protected
3596
-     * @return void
3597
-     * @throws DomainException
3598
-     */
3599
-    protected function _messages_settings_metaboxes()
3600
-    {
3601
-        $this->_set_m_mt_settings();
3602
-        $m_boxes         = $mt_boxes = [];
3603
-        $m_template_args = $mt_template_args = [];
3604
-
3605
-        $selected_messenger = $this->request->getRequestParam('selected_messenger', 'email');
3606
-
3607
-        if (isset($this->_m_mt_settings['messenger_tabs'])) {
3608
-            foreach ($this->_m_mt_settings['messenger_tabs'] as $messenger => $tab_array) {
3609
-                $is_messenger_active = $this->_message_resource_manager->is_messenger_active($messenger);
3610
-                $hide_on_message     = $is_messenger_active ? '' : 'hidden';
3611
-                $hide_off_message    = $is_messenger_active ? 'hidden' : '';
3612
-
3613
-                // messenger meta boxes
3614
-                $active         = $selected_messenger === $messenger;
3615
-                $active_mt_tabs = isset($this->_m_mt_settings['message_type_tabs'][ $messenger ]['active'])
3616
-                    ? $this->_m_mt_settings['message_type_tabs'][ $messenger ]['active']
3617
-                    : '';
3618
-
3619
-                $m_boxes[ $messenger . '_a_box' ] = sprintf(
3620
-                    esc_html__('%s Settings', 'event_espresso'),
3621
-                    $tab_array['label']
3622
-                );
3623
-
3624
-                $m_template_args[ $messenger . '_a_box' ] = [
3625
-                    'active_message_types'   => ! empty($active_mt_tabs) ? $this->_get_mt_tabs($active_mt_tabs) : '',
3626
-                    'inactive_message_types' => isset(
3627
-                        $this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive']
3628
-                    )
3629
-                        ? $this->_get_mt_tabs($this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive'])
3630
-                        : '',
3631
-                    'content'                => $this->_get_messenger_box_content($tab_array['obj']),
3632
-                    'hidden'                 => $active ? '' : ' hidden',
3633
-                    'hide_on_message'        => $hide_on_message,
3634
-                    'messenger'              => $messenger,
3635
-                    'active'                 => $active,
3636
-                ];
3637
-
3638
-                // message type meta boxes
3639
-                // (which is really just the inactive container for each messenger
3640
-                // showing inactive message types for that messenger)
3641
-                $mt_boxes[ $messenger . '_i_box' ]         = esc_html__('Inactive Message Types', 'event_espresso');
3642
-                $mt_template_args[ $messenger . '_i_box' ] = [
3643
-                    'active_message_types'   => ! empty($active_mt_tabs) ? $this->_get_mt_tabs($active_mt_tabs) : '',
3644
-                    'inactive_message_types' => isset(
3645
-                        $this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive']
3646
-                    )
3647
-                        ? $this->_get_mt_tabs($this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive'])
3648
-                        : '',
3649
-                    'hidden'                 => $active ? '' : ' hidden',
3650
-                    'hide_on_message'        => $hide_on_message,
3651
-                    'hide_off_message'       => $hide_off_message,
3652
-                    'messenger'              => $messenger,
3653
-                    'active'                 => $active,
3654
-                ];
3655
-            }
3656
-        }
3657
-
3658
-
3659
-        // register messenger metaboxes
3660
-        $m_template_path = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_messenger_mt_meta_box.template.php';
3661
-        foreach ($m_boxes as $box => $label) {
3662
-            $callback_args = ['template_path' => $m_template_path, 'template_args' => $m_template_args[ $box ]];
3663
-            $msgr          = str_replace('_a_box', '', $box);
3664
-            $this->addMetaBox(
3665
-                'espresso_' . $msgr . '_settings',
3666
-                $label,
3667
-                function ($post, $metabox) {
3668
-                    EEH_Template::display_template(
3669
-                        $metabox['args']['template_path'],
3670
-                        $metabox['args']['template_args']
3671
-                    );
3672
-                },
3673
-                $this->_current_screen->id,
3674
-                'normal',
3675
-                'high',
3676
-                $callback_args
3677
-            );
3678
-        }
3679
-
3680
-        // register message type metaboxes
3681
-        $mt_template_path = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_messenger_meta_box.template.php';
3682
-        foreach ($mt_boxes as $box => $label) {
3683
-            $callback_args = [
3684
-                'template_path' => $mt_template_path,
3685
-                'template_args' => $mt_template_args[ $box ],
3686
-            ];
3687
-            $mt            = str_replace('_i_box', '', $box);
3688
-            $this->addMetaBox(
3689
-                'espresso_' . $mt . '_inactive_mts',
3690
-                $label,
3691
-                function ($post, $metabox) {
3692
-                    EEH_Template::display_template(
3693
-                        $metabox['args']['template_path'],
3694
-                        $metabox['args']['template_args']
3695
-                    );
3696
-                },
3697
-                $this->_current_screen->id,
3698
-                'side',
3699
-                'high',
3700
-                $callback_args
3701
-            );
3702
-        }
3703
-
3704
-        // register metabox for global messages settings but only when on the main site.  On single site installs this
3705
-        // will always result in the metabox showing, on multisite installs the metabox will only show on the main site.
3706
-        if (is_main_site()) {
3707
-            $this->addMetaBox(
3708
-                'espresso_global_message_settings',
3709
-                esc_html__('Global Message Settings', 'event_espresso'),
3710
-                [$this, 'global_messages_settings_metabox_content'],
3711
-                $this->_current_screen->id,
3712
-                'normal',
3713
-                'low',
3714
-                []
3715
-            );
3716
-        }
3717
-    }
3718
-
3719
-
3720
-    /**
3721
-     *  This generates the content for the global messages settings metabox.
3722
-     *
3723
-     * @return void
3724
-     * @throws EE_Error
3725
-     * @throws InvalidArgumentException
3726
-     * @throws ReflectionException
3727
-     * @throws InvalidDataTypeException
3728
-     * @throws InvalidInterfaceException
3729
-     */
3730
-    public function global_messages_settings_metabox_content()
3731
-    {
3732
-        $form = $this->_generate_global_settings_form();
3733
-        echo wp_kses(
3734
-            $form->form_open(
3735
-                $this->add_query_args_and_nonce(['action' => 'update_global_settings'], EE_MSG_ADMIN_URL),
3736
-                'POST'
3737
-            ),
3738
-            AllowedTags::getWithFormTags()
3739
-        );
3740
-        echo wp_kses($form->get_html(), AllowedTags::getWithFormTags());
3741
-        echo wp_kses($form->form_close(), AllowedTags::getWithFormTags());
3742
-    }
3743
-
3744
-
3745
-    /**
3746
-     * This generates and returns the form object for the global messages settings.
3747
-     *
3748
-     * @return EE_Form_Section_Proper
3749
-     * @throws EE_Error
3750
-     * @throws InvalidArgumentException
3751
-     * @throws ReflectionException
3752
-     * @throws InvalidDataTypeException
3753
-     * @throws InvalidInterfaceException
3754
-     */
3755
-    protected function _generate_global_settings_form()
3756
-    {
3757
-        /** @var EE_Network_Core_Config $network_config */
3758
-        $network_config = EE_Registry::instance()->NET_CFG->core;
3759
-
3760
-        return new EE_Form_Section_Proper(
3761
-            [
3762
-                'name'            => 'global_messages_settings',
3763
-                'html_id'         => 'global_messages_settings',
3764
-                'html_class'      => 'form-table',
3765
-                'layout_strategy' => new EE_Admin_Two_Column_Layout(),
3766
-                'subsections'     => apply_filters(
3767
-                    'FHEE__Messages_Admin_Page__global_messages_settings_metabox_content__form_subsections',
3768
-                    [
3769
-                        'do_messages_on_same_request' => new EE_Select_Input(
3770
-                            [
3771
-                                true  => esc_html__('On the same request', 'event_espresso'),
3772
-                                false => esc_html__('On a separate request', 'event_espresso'),
3773
-                            ],
3774
-                            [
3775
-                                'default'         => $network_config->do_messages_on_same_request,
3776
-                                'html_label_text' => esc_html__(
3777
-                                    'Generate and send all messages:',
3778
-                                    'event_espresso'
3779
-                                ),
3780
-                                'html_help_text'  => esc_html__(
3781
-                                    'By default the messages system uses a more efficient means of processing messages on separate requests and utilizes the wp-cron scheduling system.  This makes things execute faster for people registering for your events.  However, if the wp-cron system is disabled on your site and there is no alternative in place, then you can change this so messages are always executed on the same request.',
3782
-                                    'event_espresso'
3783
-                                ),
3784
-                            ]
3785
-                        ),
3786
-                        'delete_threshold'            => new EE_Select_Input(
3787
-                            [
3788
-                                0  => esc_html__('Forever', 'event_espresso'),
3789
-                                3  => esc_html__('3 Months', 'event_espresso'),
3790
-                                6  => esc_html__('6 Months', 'event_espresso'),
3791
-                                9  => esc_html__('9 Months', 'event_espresso'),
3792
-                                12 => esc_html__('12 Months', 'event_espresso'),
3793
-                                24 => esc_html__('24 Months', 'event_espresso'),
3794
-                                36 => esc_html__('36 Months', 'event_espresso'),
3795
-                            ],
3796
-                            [
3797
-                                'default'         => EE_Registry::instance()->CFG->messages->delete_threshold,
3798
-                                'html_label_text' => esc_html__('Cleanup of old messages:', 'event_espresso'),
3799
-                                'html_help_text'  => esc_html__(
3800
-                                    'You can control how long a record of processed messages is kept via this option.',
3801
-                                    'event_espresso'
3802
-                                ),
3803
-                            ]
3804
-                        ),
3805
-                        'update_settings'             => new EE_Submit_Input(
3806
-                            [
3807
-                                'default'         => esc_html__('Update', 'event_espresso'),
3808
-                                'html_label_text' => '',
3809
-                            ]
3810
-                        ),
3811
-                    ]
3812
-                ),
3813
-            ]
3814
-        );
3815
-    }
3816
-
3817
-
3818
-    /**
3819
-     * This handles updating the global settings set on the admin page.
3820
-     *
3821
-     * @throws EE_Error
3822
-     * @throws InvalidDataTypeException
3823
-     * @throws InvalidInterfaceException
3824
-     * @throws InvalidArgumentException
3825
-     * @throws ReflectionException
3826
-     */
3827
-    protected function _update_global_settings()
3828
-    {
3829
-        /** @var EE_Network_Core_Config $network_config */
3830
-        $network_config  = EE_Registry::instance()->NET_CFG->core;
3831
-        $messages_config = EE_Registry::instance()->CFG->messages;
3832
-        $form            = $this->_generate_global_settings_form();
3833
-        if ($form->was_submitted()) {
3834
-            $form->receive_form_submission();
3835
-            if ($form->is_valid()) {
3836
-                $valid_data = $form->valid_data();
3837
-                foreach ($valid_data as $property => $value) {
3838
-                    $setter = 'set_' . $property;
3839
-                    if (method_exists($network_config, $setter)) {
3840
-                        $network_config->{$setter}($value);
3841
-                    } elseif (
3842
-                        property_exists($network_config, $property)
3843
-                        && $network_config->{$property} !== $value
3844
-                    ) {
3845
-                        $network_config->{$property} = $value;
3846
-                    } elseif (
3847
-                        property_exists($messages_config, $property)
3848
-                        && $messages_config->{$property} !== $value
3849
-                    ) {
3850
-                        $messages_config->{$property} = $value;
3851
-                    }
3852
-                }
3853
-                // only update if the form submission was valid!
3854
-                EE_Registry::instance()->NET_CFG->update_config(true, false);
3855
-                EE_Registry::instance()->CFG->update_espresso_config();
3856
-                EE_Error::overwrite_success();
3857
-                EE_Error::add_success(esc_html__('Global message settings were updated', 'event_espresso'));
3858
-            }
3859
-        }
3860
-        $this->_redirect_after_action(0, '', '', ['action' => 'settings'], true);
3861
-    }
3862
-
3863
-
3864
-    /**
3865
-     * this prepares the messenger tabs that can be dragged in and out of messenger boxes to activate/deactivate
3866
-     *
3867
-     * @param array $tab_array This is an array of message type tab details used to generate the tabs
3868
-     * @return string html formatted tabs
3869
-     * @throws DomainException
3870
-     */
3871
-    protected function _get_mt_tabs($tab_array)
3872
-    {
3873
-        $tab_array = (array) $tab_array;
3874
-        $template  = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_mt_settings_tab_item.template.php';
3875
-        $tabs      = '';
3876
-
3877
-        foreach ($tab_array as $tab) {
3878
-            $tabs .= EEH_Template::display_template($template, $tab, true);
3879
-        }
3880
-
3881
-        return $tabs;
3882
-    }
3883
-
3884
-
3885
-    /**
3886
-     * This prepares the content of the messenger meta box admin settings
3887
-     *
3888
-     * @param EE_messenger $messenger The messenger we're setting up content for
3889
-     * @return string html formatted content
3890
-     * @throws DomainException
3891
-     */
3892
-    protected function _get_messenger_box_content(EE_messenger $messenger)
3893
-    {
3894
-
3895
-        $fields = $messenger->get_admin_settings_fields();
3896
-
3897
-        $settings_template_args['template_form_fields'] = '';
3898
-        // is $messenger active?
3899
-        $settings_template_args['active'] = $this->_message_resource_manager->is_messenger_active($messenger->name);
3900
-
3901
-
3902
-        if (! empty($fields)) {
3903
-            $existing_settings = $messenger->get_existing_admin_settings();
3904
-
3905
-            foreach ($fields as $field_name => $field_props) {
3906
-                $field_id                         = $messenger->name . '-' . $field_name;
3907
-                $template_form_field[ $field_id ] = [
3908
-                    'name'       => 'messenger_settings[' . $field_id . ']',
3909
-                    'label'      => $field_props['label'],
3910
-                    'input'      => $field_props['field_type'],
3911
-                    'type'       => $field_props['value_type'],
3912
-                    'required'   => $field_props['required'],
3913
-                    'validation' => $field_props['validation'],
3914
-                    'value'      => $existing_settings[ $field_id ] ?? $field_props['default'],
3915
-                    'css_class'  => '',
3916
-                    'format'     => $field_props['format'],
3917
-                ];
3918
-            }
3919
-
3920
-            $settings_template_args['template_form_fields'] = ! empty($template_form_field)
3921
-                ? $this->_generate_admin_form_fields($template_form_field, 'string', 'ee_m_activate_form')
3922
-                : '';
3923
-        }
3924
-
3925
-        // we also need some hidden fields
3926
-        $settings_template_args['hidden_fields'] = [
3927
-            'messenger_settings[messenger]' . $messenger->name => [
3928
-                'type'  => 'hidden',
3929
-                'value' => $messenger->name,
3930
-            ],
3931
-            'type' . $messenger->name                          => [
3932
-                'type'  => 'hidden',
3933
-                'value' => 'messenger',
3934
-            ],
3935
-        ];
3936
-
3937
-        // make sure any active message types that are existing are included in the hidden fields
3938
-        if (isset($this->_m_mt_settings['message_type_tabs'][ $messenger->name ]['active'])) {
3939
-            foreach ($this->_m_mt_settings['message_type_tabs'][ $messenger->name ]['active'] as $mt => $values) {
3940
-                $settings_template_args['hidden_fields'][ 'messenger_settings[message_types][' . $mt . ']' ] = [
3941
-                    'type'  => 'hidden',
3942
-                    'value' => $mt,
3943
-                ];
3944
-            }
3945
-        }
3946
-        $settings_template_args['hidden_fields'] = $this->_generate_admin_form_fields(
3947
-            $settings_template_args['hidden_fields'],
3948
-            'array'
3949
-        );
3950
-        $active                                  =
3951
-            $this->_message_resource_manager->is_messenger_active($messenger->name);
3952
-
3953
-        $settings_template_args['messenger']           = $messenger->name;
3954
-        $settings_template_args['description']         = $messenger->description;
3955
-        $settings_template_args['show_hide_edit_form'] = $active ? '' : ' hidden';
3956
-
3957
-
3958
-        $settings_template_args['show_hide_edit_form'] = $this->_message_resource_manager->is_messenger_active(
3959
-            $messenger->name
3960
-        )
3961
-            ? $settings_template_args['show_hide_edit_form']
3962
-            : ' hidden';
3963
-
3964
-        $settings_template_args['show_hide_edit_form'] = empty($settings_template_args['template_form_fields'])
3965
-            ? ' hidden'
3966
-            : $settings_template_args['show_hide_edit_form'];
3967
-
3968
-
3969
-        $settings_template_args['on_off_action'] = $active ? 'messenger-off' : 'messenger-on';
3970
-        $settings_template_args['nonce']         = wp_create_nonce('activate_' . $messenger->name . '_toggle_nonce');
3971
-        $settings_template_args['on_off_status'] = $active;
3972
-        $template                                = EE_MSG_TEMPLATE_PATH . 'ee_msg_m_settings_content.template.php';
3973
-        return EEH_Template::display_template(
3974
-            $template,
3975
-            $settings_template_args,
3976
-            true
3977
-        );
3978
-    }
3979
-
3980
-
3981
-    /**
3982
-     * used by ajax on the messages settings page to activate|deactivate the messenger
3983
-     *
3984
-     * @throws DomainException
3985
-     * @throws EE_Error
3986
-     * @throws InvalidDataTypeException
3987
-     * @throws InvalidInterfaceException
3988
-     * @throws InvalidArgumentException
3989
-     * @throws ReflectionException
3990
-     */
3991
-    public function activate_messenger_toggle()
3992
-    {
3993
-        $success = true;
3994
-        $this->_prep_default_response_for_messenger_or_message_type_toggle();
3995
-        // let's check that we have required data
3996
-
3997
-        if (! $this->_active_messenger_name) {
3998
-            EE_Error::add_error(
3999
-                esc_html__('Messenger name needed to toggle activation. None given', 'event_espresso'),
4000
-                __FILE__,
4001
-                __FUNCTION__,
4002
-                __LINE__
4003
-            );
4004
-            $success = false;
4005
-        }
4006
-
4007
-        // do a nonce check here since we're not arriving via a normal route
4008
-        $nonce     = $this->request->getRequestParam('activate_nonce', '');
4009
-        $nonce_ref = "activate_{$this->_active_messenger_name}_toggle_nonce";
4010
-
4011
-        $this->_verify_nonce($nonce, $nonce_ref);
4012
-
4013
-
4014
-        $status = $this->request->getRequestParam('status');
4015
-        if (! $status) {
4016
-            EE_Error::add_error(
4017
-                esc_html__(
4018
-                    'Messenger status needed to know whether activation or deactivation is happening. No status is given',
4019
-                    'event_espresso'
4020
-                ),
4021
-                __FILE__,
4022
-                __FUNCTION__,
4023
-                __LINE__
4024
-            );
4025
-            $success = false;
4026
-        }
4027
-
4028
-        // do check to verify we have a valid status.
4029
-        if ($status !== 'off' && $status !== 'on') {
4030
-            EE_Error::add_error(
4031
-                sprintf(
4032
-                    esc_html__('The given status (%s) is not valid. Must be "off" or "on"', 'event_espresso'),
4033
-                    $status
4034
-                ),
4035
-                __FILE__,
4036
-                __FUNCTION__,
4037
-                __LINE__
4038
-            );
4039
-            $success = false;
4040
-        }
4041
-
4042
-        if ($success) {
4043
-            // made it here?  Stop dawdling then!!
4044
-            $success = $status === 'off'
4045
-                ? $this->_deactivate_messenger($this->_active_messenger_name)
4046
-                : $this->_activate_messenger($this->_active_messenger_name);
4047
-        }
4048
-
4049
-        $this->_template_args['success'] = $success;
4050
-
4051
-        // no special instructions so let's just do the json return (which should automatically do all the special stuff).
4052
-        $this->_return_json();
4053
-    }
4054
-
4055
-
4056
-    /**
4057
-     * used by ajax from the messages settings page to activate|deactivate a message type
4058
-     *
4059
-     * @throws DomainException
4060
-     * @throws EE_Error
4061
-     * @throws ReflectionException
4062
-     * @throws InvalidDataTypeException
4063
-     * @throws InvalidInterfaceException
4064
-     * @throws InvalidArgumentException
4065
-     */
4066
-    public function activate_mt_toggle()
4067
-    {
4068
-        $success = true;
4069
-        $this->_prep_default_response_for_messenger_or_message_type_toggle();
4070
-
4071
-        // let's make sure we have the necessary data
4072
-        if (! $this->_active_message_type_name) {
4073
-            EE_Error::add_error(
4074
-                esc_html__('Message Type name needed to toggle activation. None given', 'event_espresso'),
4075
-                __FILE__,
4076
-                __FUNCTION__,
4077
-                __LINE__
4078
-            );
4079
-            $success = false;
4080
-        }
4081
-
4082
-        if (! $this->_active_messenger_name) {
4083
-            EE_Error::add_error(
4084
-                esc_html__('Messenger name needed to toggle activation. None given', 'event_espresso'),
4085
-                __FILE__,
4086
-                __FUNCTION__,
4087
-                __LINE__
4088
-            );
4089
-            $success = false;
4090
-        }
4091
-
4092
-        $status = $this->request->getRequestParam('status');
4093
-        if (! $status) {
4094
-            EE_Error::add_error(
4095
-                esc_html__(
4096
-                    'Messenger status needed to know whether activation or deactivation is happening. No status is given',
4097
-                    'event_espresso'
4098
-                ),
4099
-                __FILE__,
4100
-                __FUNCTION__,
4101
-                __LINE__
4102
-            );
4103
-            $success = false;
4104
-        }
4105
-
4106
-
4107
-        // do check to verify we have a valid status.
4108
-        if ($status !== 'activate' && $status !== 'deactivate') {
4109
-            EE_Error::add_error(
4110
-                sprintf(
4111
-                    esc_html__('The given status (%s) is not valid. Must be "active" or "inactive"', 'event_espresso'),
4112
-                    $status
4113
-                ),
4114
-                __FILE__,
4115
-                __FUNCTION__,
4116
-                __LINE__
4117
-            );
4118
-            $success = false;
4119
-        }
4120
-
4121
-
4122
-        // do a nonce check here since we're not arriving via a normal route
4123
-        $nonce = $this->request->getRequestParam('mt_nonce', '');
4124
-        $this->_verify_nonce($nonce, "{$this->_active_message_type_name}_nonce");
4125
-
4126
-        if ($success) {
4127
-            // made it here? um, what are you waiting for then?
4128
-            $success = $status === 'deactivate'
4129
-                ? $this->_deactivate_message_type_for_messenger(
4130
-                    $this->_active_messenger_name,
4131
-                    $this->_active_message_type_name
4132
-                )
4133
-                : $this->_activate_message_type_for_messenger(
4134
-                    $this->_active_messenger_name,
4135
-                    $this->_active_message_type_name
4136
-                );
4137
-        }
4138
-
4139
-        $this->_template_args['success'] = $success;
4140
-        $this->_return_json();
4141
-    }
4142
-
4143
-
4144
-    /**
4145
-     * Takes care of processing activating a messenger and preparing the appropriate response.
4146
-     *
4147
-     * @param string $messenger_name The name of the messenger being activated
4148
-     * @return bool
4149
-     * @throws DomainException
4150
-     * @throws EE_Error
4151
-     * @throws InvalidArgumentException
4152
-     * @throws ReflectionException
4153
-     * @throws InvalidDataTypeException
4154
-     * @throws InvalidInterfaceException
4155
-     */
4156
-    protected function _activate_messenger($messenger_name)
4157
-    {
4158
-        $active_messenger          = $this->_message_resource_manager->get_messenger($messenger_name);
4159
-        $message_types_to_activate = $active_messenger instanceof EE_Messenger
4160
-            ? $active_messenger->get_default_message_types()
4161
-            : [];
4162
-
4163
-        // ensure is active
4164
-        $this->_message_resource_manager->activate_messenger($active_messenger, $message_types_to_activate);
4165
-
4166
-        // set response_data for reload
4167
-        foreach ($message_types_to_activate as $message_type_name) {
4168
-            $message_type = $this->_message_resource_manager->get_message_type($message_type_name);
4169
-            if (
4170
-                $this->_message_resource_manager->is_message_type_active_for_messenger(
4171
-                    $messenger_name,
4172
-                    $message_type_name
4173
-                )
4174
-                && $message_type instanceof EE_message_type
4175
-            ) {
4176
-                $this->_template_args['data']['active_mts'][] = $message_type_name;
4177
-                if ($message_type->get_admin_settings_fields()) {
4178
-                    $this->_template_args['data']['mt_reload'][] = $message_type_name;
4179
-                }
4180
-            }
4181
-        }
4182
-
4183
-        // add success message for activating messenger
4184
-        return $this->_setup_response_message_for_activating_messenger_with_message_types($active_messenger);
4185
-    }
4186
-
4187
-
4188
-    /**
4189
-     * Takes care of processing deactivating a messenger and preparing the appropriate response.
4190
-     *
4191
-     * @param string $messenger_name The name of the messenger being activated
4192
-     * @return bool
4193
-     * @throws DomainException
4194
-     * @throws EE_Error
4195
-     * @throws InvalidArgumentException
4196
-     * @throws ReflectionException
4197
-     * @throws InvalidDataTypeException
4198
-     * @throws InvalidInterfaceException
4199
-     */
4200
-    protected function _deactivate_messenger($messenger_name)
4201
-    {
4202
-        $active_messenger = $this->_message_resource_manager->get_messenger($messenger_name);
4203
-        $this->_message_resource_manager->deactivate_messenger($messenger_name);
4204
-
4205
-        return $this->_setup_response_message_for_deactivating_messenger_with_message_types($active_messenger);
4206
-    }
4207
-
4208
-
4209
-    /**
4210
-     * Takes care of processing activating a message type for a messenger and preparing the appropriate response.
4211
-     *
4212
-     * @param string $messenger_name    The name of the messenger the message type is being activated for.
4213
-     * @param string $message_type_name The name of the message type being activated for the messenger
4214
-     * @return bool
4215
-     * @throws DomainException
4216
-     * @throws EE_Error
4217
-     * @throws InvalidArgumentException
4218
-     * @throws ReflectionException
4219
-     * @throws InvalidDataTypeException
4220
-     * @throws InvalidInterfaceException
4221
-     */
4222
-    protected function _activate_message_type_for_messenger($messenger_name, $message_type_name)
4223
-    {
4224
-        $active_messenger         = $this->_message_resource_manager->get_messenger($messenger_name);
4225
-        $message_type_to_activate = $this->_message_resource_manager->get_message_type($message_type_name);
4226
-
4227
-        // ensure is active
4228
-        $this->_message_resource_manager->activate_messenger($active_messenger, $message_type_name);
4229
-
4230
-        // set response for load
4231
-        if (
4232
-            $this->_message_resource_manager->is_message_type_active_for_messenger(
4233
-                $messenger_name,
4234
-                $message_type_name
4235
-            )
4236
-        ) {
4237
-            $this->_template_args['data']['active_mts'][] = $message_type_name;
4238
-            if ($message_type_to_activate->get_admin_settings_fields()) {
4239
-                $this->_template_args['data']['mt_reload'][] = $message_type_name;
4240
-            }
4241
-        }
4242
-
4243
-        return $this->_setup_response_message_for_activating_messenger_with_message_types(
4244
-            $active_messenger,
4245
-            $message_type_to_activate
4246
-        );
4247
-    }
4248
-
4249
-
4250
-    /**
4251
-     * Takes care of processing deactivating a message type for a messenger and preparing the appropriate response.
4252
-     *
4253
-     * @param string $messenger_name    The name of the messenger the message type is being deactivated for.
4254
-     * @param string $message_type_name The name of the message type being deactivated for the messenger
4255
-     * @return bool
4256
-     * @throws DomainException
4257
-     * @throws EE_Error
4258
-     * @throws InvalidArgumentException
4259
-     * @throws ReflectionException
4260
-     * @throws InvalidDataTypeException
4261
-     * @throws InvalidInterfaceException
4262
-     */
4263
-    protected function _deactivate_message_type_for_messenger($messenger_name, $message_type_name)
4264
-    {
4265
-        $active_messenger = $this->_message_resource_manager->get_messenger($messenger_name);
4266
-        /** @var EE_message_type $message_type_to_activate This will be present because it can't be toggled if it isn't */
4267
-        $message_type_to_deactivate = $this->_message_resource_manager->get_message_type($message_type_name);
4268
-        $this->_message_resource_manager->deactivate_message_type_for_messenger($message_type_name, $messenger_name);
4269
-
4270
-        return $this->_setup_response_message_for_deactivating_messenger_with_message_types(
4271
-            $active_messenger,
4272
-            $message_type_to_deactivate
4273
-        );
4274
-    }
4275
-
4276
-
4277
-    /**
4278
-     * This just initializes the defaults for activating messenger and message type responses.
4279
-     */
4280
-    protected function _prep_default_response_for_messenger_or_message_type_toggle()
4281
-    {
4282
-        $this->_template_args['data']['active_mts'] = [];
4283
-        $this->_template_args['data']['mt_reload']  = [];
4284
-    }
4285
-
4286
-
4287
-    /**
4288
-     * Setup appropriate response for activating a messenger and/or message types
4289
-     *
4290
-     * @param EE_messenger         $messenger
4291
-     * @param EE_message_type|null $message_type
4292
-     * @return bool
4293
-     * @throws DomainException
4294
-     * @throws EE_Error
4295
-     * @throws InvalidArgumentException
4296
-     * @throws ReflectionException
4297
-     * @throws InvalidDataTypeException
4298
-     * @throws InvalidInterfaceException
4299
-     */
4300
-    protected function _setup_response_message_for_activating_messenger_with_message_types(
4301
-        $messenger,
4302
-        EE_Message_Type $message_type = null
4303
-    ) {
4304
-        // if $messenger isn't a valid messenger object then get out.
4305
-        if (! $messenger instanceof EE_Messenger) {
4306
-            EE_Error::add_error(
4307
-                esc_html__('The messenger being activated is not a valid messenger', 'event_espresso'),
4308
-                __FILE__,
4309
-                __FUNCTION__,
4310
-                __LINE__
4311
-            );
4312
-            return false;
4313
-        }
4314
-        // activated
4315
-        if ($this->_template_args['data']['active_mts']) {
4316
-            EE_Error::overwrite_success();
4317
-            // activated a message type with the messenger
4318
-            if ($message_type instanceof EE_message_type) {
4319
-                EE_Error::add_success(
4320
-                    sprintf(
4321
-                        esc_html__(
4322
-                            '%s message type has been successfully activated with the %s messenger',
4323
-                            'event_espresso'
4324
-                        ),
4325
-                        ucwords($message_type->label['singular']),
4326
-                        ucwords($messenger->label['singular'])
4327
-                    )
4328
-                );
4329
-
4330
-                // if message type was invoice then let's make sure we activate the invoice payment method.
4331
-                if ($message_type->name === 'invoice') {
4332
-                    EE_Registry::instance()->load_lib('Payment_Method_Manager');
4333
-                    $pm = EE_Payment_Method_Manager::instance()->activate_a_payment_method_of_type('Invoice');
4334
-                    if ($pm instanceof EE_Payment_Method) {
4335
-                        EE_Error::add_attention(
4336
-                            esc_html__(
4337
-                                'Activating the invoice message type also automatically activates the invoice payment method.  If you do not wish the invoice payment method to be active, or to change its settings, visit the payment method admin page.',
4338
-                                'event_espresso'
4339
-                            )
4340
-                        );
4341
-                    }
4342
-                }
4343
-                // just toggles the entire messenger
4344
-            } else {
4345
-                EE_Error::add_success(
4346
-                    sprintf(
4347
-                        esc_html__('%s messenger has been successfully activated', 'event_espresso'),
4348
-                        ucwords($messenger->label['singular'])
4349
-                    )
4350
-                );
4351
-            }
4352
-
4353
-            return true;
4354
-
4355
-            // possible error condition. This will happen when our active_mts data is empty because it is validated for actual active
4356
-            // message types after the activation process.  However its possible some messengers don't HAVE any default_message_types
4357
-            // in which case we just give a success message for the messenger being successfully activated.
4358
-        } else {
4359
-            if (! $messenger->get_default_message_types()) {
4360
-                // messenger doesn't have any default message types so still a success.
4361
-                EE_Error::add_success(
4362
-                    sprintf(
4363
-                        esc_html__('%s messenger was successfully activated.', 'event_espresso'),
4364
-                        ucwords($messenger->label['singular'])
4365
-                    )
4366
-                );
4367
-
4368
-                return true;
4369
-            } else {
4370
-                EE_Error::add_error(
4371
-                    $message_type instanceof EE_message_type
4372
-                    ? sprintf(
4373
-                        esc_html__(
4374
-                            '%s message type was not successfully activated with the %s messenger',
4375
-                            'event_espresso'
4376
-                        ),
4377
-                        ucwords($message_type->label['singular']),
4378
-                        ucwords($messenger->label['singular'])
4379
-                    )
4380
-                    : sprintf(
4381
-                        esc_html__('%s messenger was not successfully activated', 'event_espresso'),
4382
-                        ucwords($messenger->label['singular'])
4383
-                    ),
4384
-                    __FILE__,
4385
-                    __FUNCTION__,
4386
-                    __LINE__
4387
-                );
4388
-
4389
-                return false;
4390
-            }
4391
-        }
4392
-    }
4393
-
4394
-
4395
-    /**
4396
-     * This sets up the appropriate response for deactivating a messenger and/or message type.
4397
-     *
4398
-     * @param EE_messenger         $messenger
4399
-     * @param EE_message_type|null $message_type
4400
-     * @return bool
4401
-     * @throws DomainException
4402
-     * @throws EE_Error
4403
-     * @throws InvalidArgumentException
4404
-     * @throws ReflectionException
4405
-     * @throws InvalidDataTypeException
4406
-     * @throws InvalidInterfaceException
4407
-     */
4408
-    protected function _setup_response_message_for_deactivating_messenger_with_message_types(
4409
-        $messenger,
4410
-        EE_message_type $message_type = null
4411
-    ) {
4412
-        EE_Error::overwrite_success();
4413
-
4414
-        // if $messenger isn't a valid messenger object then get out.
4415
-        if (! $messenger instanceof EE_Messenger) {
4416
-            EE_Error::add_error(
4417
-                esc_html__('The messenger being deactivated is not a valid messenger', 'event_espresso'),
4418
-                __FILE__,
4419
-                __FUNCTION__,
4420
-                __LINE__
4421
-            );
4422
-
4423
-            return false;
4424
-        }
4425
-
4426
-        if ($message_type instanceof EE_message_type) {
4427
-            $message_type_name = $message_type->name;
4428
-            EE_Error::add_success(
4429
-                sprintf(
4430
-                    esc_html__(
4431
-                        '%s message type has been successfully deactivated for the %s messenger.',
4432
-                        'event_espresso'
4433
-                    ),
4434
-                    ucwords($message_type->label['singular']),
4435
-                    ucwords($messenger->label['singular'])
4436
-                )
4437
-            );
4438
-        } else {
4439
-            $message_type_name = '';
4440
-            EE_Error::add_success(
4441
-                sprintf(
4442
-                    esc_html__('%s messenger has been successfully deactivated.', 'event_espresso'),
4443
-                    ucwords($messenger->label['singular'])
4444
-                )
4445
-            );
4446
-        }
4447
-
4448
-        // if messenger was html or message type was invoice then let's make sure we deactivate invoice payment method.
4449
-        if (
4450
-            $messenger->name === 'html'
4451
-            && (
4452
-                is_null($message_type)
4453
-                || $message_type_name === 'invoice'
4454
-            )
4455
-        ) {
4456
-            EE_Registry::instance()->load_lib('Payment_Method_Manager');
4457
-            $count_updated = EE_Payment_Method_Manager::instance()->deactivate_payment_method('invoice');
4458
-            if ($count_updated > 0) {
4459
-                $msg = $message_type_name === 'invoice'
4460
-                    ? esc_html__(
4461
-                        'Deactivating the invoice message type also automatically deactivates the invoice payment method. In order for invoices to be generated the invoice message type must be active. If you completed this action by mistake, simply reactivate the invoice message type and then visit the payment methods admin page to reactivate the invoice payment method.',
4462
-                        'event_espresso'
4463
-                    )
4464
-                    : esc_html__(
4465
-                        'Deactivating the html messenger also automatically deactivates the invoice payment method.  In order for invoices to be generated the html messenger must be be active.  If you completed this action by mistake, simply reactivate the html messenger, then visit the payment methods admin page to reactivate the invoice payment method.',
4466
-                        'event_espresso'
4467
-                    );
4468
-                EE_Error::add_attention($msg);
4469
-            }
4470
-        }
4471
-
4472
-        return true;
4473
-    }
4474
-
4475
-
4476
-    /**
4477
-     * handles updating a message type form on messenger activation IF the message type has settings fields. (via ajax)
4478
-     *
4479
-     * @throws DomainException
4480
-     * @throws EE_Error
4481
-     * @throws EE_Error
4482
-     */
4483
-    public function update_mt_form()
4484
-    {
4485
-        if (! $this->_active_messenger_name || ! $this->_active_message_type_name) {
4486
-            EE_Error::add_error(
4487
-                esc_html__('Require message type or messenger to send an updated form', 'event_espresso'),
4488
-                __FILE__,
4489
-                __FUNCTION__,
4490
-                __LINE__
4491
-            );
4492
-            $this->_return_json();
4493
-        }
4494
-
4495
-        $message_types = $this->get_installed_message_types();
4496
-        $message_type  = $message_types[ $this->_active_message_type_name ];
4497
-        $messenger     = $this->_message_resource_manager->get_active_messenger($this->_active_messenger_name);
4498
-        $content       = $this->_message_type_settings_content($message_type, $messenger, true);
4499
-
4500
-        $this->_template_args['success'] = true;
4501
-        $this->_template_args['content'] = $content;
4502
-        $this->_return_json();
4503
-    }
4504
-
4505
-
4506
-    /**
4507
-     * this handles saving the settings for a messenger or message type
4508
-     *
4509
-     * @throws EE_Error
4510
-     * @throws EE_Error
4511
-     */
4512
-    public function save_settings()
4513
-    {
4514
-        $type = $this->request->getRequestParam('type');
4515
-        if (! $type) {
4516
-            EE_Error::add_error(
4517
-                esc_html__(
4518
-                    'Cannot save settings because type is unknown (messenger settings or message type settings?)',
4519
-                    'event_espresso'
4520
-                ),
4521
-                __FILE__,
4522
-                __FUNCTION__,
4523
-                __LINE__
4524
-            );
4525
-            $this->_template_args['error'] = true;
4526
-            $this->_return_json();
4527
-        }
4528
-
4529
-
4530
-        if ($type === 'messenger') {
4531
-            // this should be an array.
4532
-            $settings  = $this->request->getRequestParam('messenger_settings', [], 'string', true);
4533
-            $messenger = $settings['messenger'];
4534
-            // remove messenger and message_types from settings array
4535
-            unset($settings['messenger'], $settings['message_types']);
4536
-            $this->_message_resource_manager->add_settings_for_messenger($messenger, $settings);
4537
-        } elseif ($type === 'message_type') {
4538
-            $settings     = $this->request->getRequestParam('message_type_settings', [], 'string', true);
4539
-            $messenger    = $settings['messenger'];
4540
-            $message_type = $settings['message_type'];
4541
-            // remove messenger and message_types from settings array
4542
-            unset($settings['messenger'], $settings['message_types']);
4543
-            $this->_message_resource_manager->add_settings_for_message_type($messenger, $message_type, $settings);
4544
-        }
4545
-
4546
-        // okay we should have the data all setup.  Now we just update!
4547
-        $success = $this->_message_resource_manager->update_active_messengers_option();
4548
-
4549
-        if ($success) {
4550
-            EE_Error::add_success(esc_html__('Settings updated', 'event_espresso'));
4551
-        } else {
4552
-            EE_Error::add_error(
4553
-                esc_html__('Settings did not get updated', 'event_espresso'),
4554
-                __FILE__,
4555
-                __FUNCTION__,
4556
-                __LINE__
4557
-            );
4558
-        }
4559
-
4560
-        $this->_template_args['success'] = $success;
4561
-        $this->_return_json();
4562
-    }
4563
-
4564
-
4565
-
4566
-
4567
-    /**  EE MESSAGE PROCESSING ACTIONS **/
4568
-
4569
-
4570
-    /**
4571
-     * This immediately generates any EE_Message ID's that are selected that are EEM_Message::status_incomplete
4572
-     * However, this does not send immediately, it just queues for sending.
4573
-     *
4574
-     * @throws EE_Error
4575
-     * @throws InvalidDataTypeException
4576
-     * @throws InvalidInterfaceException
4577
-     * @throws InvalidArgumentException
4578
-     * @throws ReflectionException
4579
-     * @since 4.9.0
4580
-     */
4581
-    protected function _generate_now()
4582
-    {
4583
-        EED_Messages::generate_now($this->_get_msg_ids_from_request());
4584
-        $this->_redirect_after_action(false, '', '', [], true);
4585
-    }
4586
-
4587
-
4588
-    /**
4589
-     * This immediately generates AND sends any EE_Message's selected that are EEM_Message::status_incomplete or that
4590
-     * are EEM_Message::status_resend or EEM_Message::status_idle
4591
-     *
4592
-     * @throws EE_Error
4593
-     * @throws InvalidDataTypeException
4594
-     * @throws InvalidInterfaceException
4595
-     * @throws InvalidArgumentException
4596
-     * @throws ReflectionException
4597
-     * @since 4.9.0
4598
-     */
4599
-    protected function _generate_and_send_now()
4600
-    {
4601
-        EED_Messages::generate_and_send_now($this->_get_msg_ids_from_request());
4602
-        $this->_redirect_after_action(false, '', '', [], true);
4603
-    }
4604
-
4605
-
4606
-    /**
4607
-     * This queues any EEM_Message::status_sent EE_Message ids in the request for resending.
4608
-     *
4609
-     * @throws EE_Error
4610
-     * @throws InvalidDataTypeException
4611
-     * @throws InvalidInterfaceException
4612
-     * @throws InvalidArgumentException
4613
-     * @throws ReflectionException
4614
-     * @since 4.9.0
4615
-     */
4616
-    protected function _queue_for_resending()
4617
-    {
4618
-        EED_Messages::queue_for_resending($this->_get_msg_ids_from_request());
4619
-        $this->_redirect_after_action(false, '', '', [], true);
4620
-    }
4621
-
4622
-
4623
-    /**
4624
-     *  This sends immediately any EEM_Message::status_idle or EEM_Message::status_resend messages in the queue
4625
-     *
4626
-     * @throws EE_Error
4627
-     * @throws InvalidDataTypeException
4628
-     * @throws InvalidInterfaceException
4629
-     * @throws InvalidArgumentException
4630
-     * @throws ReflectionException
4631
-     * @since 4.9.0
4632
-     */
4633
-    protected function _send_now()
4634
-    {
4635
-        EED_Messages::send_now($this->_get_msg_ids_from_request());
4636
-        $this->_redirect_after_action(false, '', '', [], true);
4637
-    }
4638
-
4639
-
4640
-    /**
4641
-     * Deletes EE_messages for IDs in the request.
4642
-     *
4643
-     * @throws EE_Error
4644
-     * @throws InvalidDataTypeException
4645
-     * @throws InvalidInterfaceException
4646
-     * @throws InvalidArgumentException
4647
-     * @since 4.9.0
4648
-     */
4649
-    protected function _delete_ee_messages()
4650
-    {
4651
-        $MSG_IDs       = $this->_get_msg_ids_from_request();
4652
-        $deleted_count = 0;
4653
-        foreach ($MSG_IDs as $MSG_ID) {
4654
-            if ($this->getMsgModel()->delete_by_ID($MSG_ID)) {
4655
-                $deleted_count++;
4656
-            }
4657
-        }
4658
-        if ($deleted_count) {
4659
-            EE_Error::add_success(
4660
-                esc_html(
4661
-                    _n(
4662
-                        'Message successfully deleted',
4663
-                        'Messages successfully deleted',
4664
-                        $deleted_count,
4665
-                        'event_espresso'
4666
-                    )
4667
-                )
4668
-            );
4669
-        } else {
4670
-            EE_Error::add_error(
4671
-                _n('The message was not deleted.', 'The messages were not deleted', count($MSG_IDs), 'event_espresso'),
4672
-                __FILE__,
4673
-                __FUNCTION__,
4674
-                __LINE__
4675
-            );
4676
-        }
4677
-        $this->_redirect_after_action(false, '', '', [], true);
4678
-    }
4679
-
4680
-
4681
-    /**
4682
-     *  This looks for 'MSG_ID' key in the request and returns an array of MSG_ID's if present.
4683
-     *
4684
-     * @return array
4685
-     * @since 4.9.0
4686
-     */
4687
-    protected function _get_msg_ids_from_request()
4688
-    {
4689
-        $MSG_IDs = $this->request->getRequestParam('MSG_ID', [], 'string', true);
4690
-        if (empty($MSG_IDs)) {
4691
-            return [];
4692
-        }
4693
-        // if 'MSG_ID' was just a single ID (not an array)
4694
-        // then $MSG_IDs will be something like [123] so $MSG_IDs[0] should be 123
4695
-        // otherwise, $MSG_IDs was already an array where message IDs were used as the keys
4696
-        return count($MSG_IDs) === 1 && isset($MSG_IDs[0])
4697
-            ? $MSG_IDs
4698
-            : array_keys($MSG_IDs);
4699
-    }
2711
+	}
2712
+
2713
+
2714
+	/**
2715
+	 * @param bool $new
2716
+	 * @throws EE_Error
2717
+	 * @throws ReflectionException
2718
+	 */
2719
+	protected function _insert_or_update_message_template($new = false)
2720
+	{
2721
+		$form_data    = $this->getMessageTemplateFormData();
2722
+		$GRP_ID       = $form_data['GRP_ID'];
2723
+		$messenger    = $form_data['MTP_messenger'];
2724
+		$message_type = $form_data['MTP_message_type'];
2725
+		$context      = $form_data['MTP_context'];
2726
+
2727
+		// if this is "new" then we need to generate the default contexts
2728
+		// for the selected messenger/message_type for user to edit.
2729
+		[$success, $query_args] = $new
2730
+			? $this->generateNewTemplates($GRP_ID, $messenger, $message_type)
2731
+			: $this->updateExistingTemplates($GRP_ID, $messenger, $message_type, $context, $form_data);
2732
+
2733
+		$success     = $success ? 1 : 0;
2734
+		$action_desc = $new ? 'created' : 'updated';
2735
+		$item_desc   = $this->generateUpdateDescription($messenger, $message_type, $context);
2736
+		$override    = $this->performTestSendAfterUpdate($messenger, $message_type, $context);
2737
+
2738
+		$this->_redirect_after_action($success, $item_desc, $action_desc, $query_args, $override);
2739
+	}
2740
+
2741
+
2742
+	/**
2743
+	 * retrieve and sanitize form data
2744
+	 *
2745
+	 * @return array
2746
+	 * @since 4.10.29.p
2747
+	 */
2748
+	protected function getMessageTemplateFormData()
2749
+	{
2750
+		return [
2751
+			'GRP_ID'           => $this->request->getRequestParam('GRP_ID', 0, 'int'),
2752
+			'MTP_context'      => strtolower($this->request->getRequestParam('MTP_context', '')),
2753
+			'MTP_messenger'    => strtolower($this->request->getRequestParam('MTP_messenger', '')),
2754
+			'MTP_message_type' => strtolower($this->request->getRequestParam('MTP_message_type', '')),
2755
+			'MTP_user_id'      => $this->request->getRequestParam('MTP_user_id', 0, 'int'),
2756
+			'MTP_is_global'    => $this->request->getRequestParam('MTP_is_global', 0, 'int'),
2757
+			'MTP_is_override'  => $this->request->getRequestParam('MTP_is_override', 0, 'int'),
2758
+			'MTP_deleted'      => $this->request->getRequestParam('MTP_deleted', 0, 'int'),
2759
+			'MTP_is_active'    => $this->request->getRequestParam('MTP_is_active', 0, 'int'),
2760
+		];
2761
+	}
2762
+
2763
+
2764
+	/**
2765
+	 * @param int    $GRP_ID
2766
+	 * @param string $messenger
2767
+	 * @param string $message_type
2768
+	 * @return array no return on AJAX requests
2769
+	 * @throws EE_Error
2770
+	 * @throws ReflectionException
2771
+	 * @since 4.10.29.p
2772
+	 */
2773
+	private function generateNewTemplates($GRP_ID, $messenger, $message_type)
2774
+	{
2775
+		$new_templates = $this->_generate_new_templates($messenger, [$message_type], $GRP_ID);
2776
+		$success       = ! empty($new_templates);
2777
+
2778
+		// we return things differently if doing ajax
2779
+		if ($this->request->isAjax()) {
2780
+			$this->_template_args['success'] = $success;
2781
+			$this->_template_args['error']   = ! $success;
2782
+			$this->_template_args['content'] = '';
2783
+			$this->_template_args['data']    = [
2784
+				'grpID'        => $new_templates['GRP_ID'],
2785
+				'templateName' => $new_templates['template_name'],
2786
+			];
2787
+			if ($success) {
2788
+				EE_Error::overwrite_success();
2789
+				EE_Error::add_success(
2790
+					esc_html__(
2791
+						'The new template has been created and automatically selected for this event.  You can edit the new template by clicking the edit button.  Note before this template is assigned to this event, the event must be saved.',
2792
+						'event_espresso'
2793
+					)
2794
+				);
2795
+			}
2796
+			$this->_return_json();
2797
+		}
2798
+		return [
2799
+			$success,
2800
+			// 'query_args'
2801
+			[
2802
+				'id'      => $new_templates['GRP_ID'],
2803
+				'context' => $new_templates['MTP_context'],
2804
+				'action'  => 'edit_message_template',
2805
+			],
2806
+		];
2807
+	}
2808
+
2809
+
2810
+	/**
2811
+	 * @param int    $GRP_ID
2812
+	 * @param string $messenger
2813
+	 * @param string $message_type
2814
+	 * @param string $context
2815
+	 * @param array  $form_data
2816
+	 * @return array
2817
+	 * @throws EE_Error
2818
+	 * @since 4.10.29.p
2819
+	 */
2820
+	private function updateExistingTemplates(
2821
+		$GRP_ID,
2822
+		$messenger,
2823
+		$message_type,
2824
+		$context,
2825
+		array $form_data
2826
+	) {
2827
+		$success         = false;
2828
+		$template_fields = $this->getTemplateFields();
2829
+		if ($template_fields) {
2830
+			// if field data is valid, then success will be true
2831
+			$success = $this->validateTemplateFields(
2832
+				$messenger,
2833
+				$message_type,
2834
+				$context,
2835
+				$template_fields
2836
+			);
2837
+			if ($success) {
2838
+				$field_data = [];
2839
+				foreach ($template_fields as $template_field => $content) {
2840
+					// combine top-level form data with content for this field
2841
+					$field_data = $this->getTemplateFieldFormData($content, $form_data);
2842
+					$success    = $this->updateMessageTemplates($template_field, $field_data) ? $success : false;
2843
+				}
2844
+				// we can use the last set_column_values for the MTPG update
2845
+				// (because its the same for all of these specific MTPs)
2846
+				$success = $this->updateMessageTemplateGroup($field_data) ? $success : false;
2847
+			}
2848
+		}
2849
+
2850
+		return [
2851
+			$success,
2852
+			// 'query_args'
2853
+			[
2854
+				'id'      => $GRP_ID,
2855
+				'context' => $context,
2856
+				'action'  => 'edit_message_template',
2857
+			],
2858
+		];
2859
+	}
2860
+
2861
+
2862
+	/**
2863
+	 * @return array
2864
+	 * @since 4.10.29.p
2865
+	 */
2866
+	private function getTemplateFields()
2867
+	{
2868
+		$template_fields = $this->request->getRequestParam('MTP_template_fields', null, 'html', true);
2869
+		if (empty($template_fields)) {
2870
+			EE_Error::add_error(
2871
+				esc_html__(
2872
+					'There was a problem saving the template fields from the form because I didn\'t receive any actual template field data.',
2873
+					'event_espresso'
2874
+				),
2875
+				__FILE__,
2876
+				__FUNCTION__,
2877
+				__LINE__
2878
+			);
2879
+			return null;
2880
+		}
2881
+		// messages content is expected to be escaped
2882
+		return EEH_Array::addSlashesRecursively($template_fields);
2883
+	}
2884
+
2885
+
2886
+	/**
2887
+	 * @param string $messenger
2888
+	 * @param string $message_type
2889
+	 * @param string $context
2890
+	 * @param array  $template_fields
2891
+	 * @return bool
2892
+	 * @throws EE_Error
2893
+	 * @since   4.10.29.p
2894
+	 */
2895
+	private function validateTemplateFields(
2896
+		$messenger,
2897
+		$message_type,
2898
+		$context,
2899
+		array $template_fields
2900
+	) {
2901
+		// first validate all fields!
2902
+		// this filter allows client code to add its own validation to the template fields as well.
2903
+		// returning an empty array means everything passed validation.
2904
+		// errors in validation should be represented in an array with the following shape:
2905
+		// array(
2906
+		//   'fieldname' => array(
2907
+		//          'msg' => 'error message'
2908
+		//          'value' => 'value for field producing error'
2909
+		// )
2910
+		$custom_validation = (array) apply_filters(
2911
+			'FHEE__Messages_Admin_Page___insert_or_update_message_template__validates',
2912
+			[],
2913
+			$template_fields,
2914
+			$context,
2915
+			$messenger,
2916
+			$message_type
2917
+		);
2918
+
2919
+		$system_validation = $this->getMtgModel()->validate(
2920
+			$template_fields,
2921
+			$context,
2922
+			$messenger,
2923
+			$message_type
2924
+		);
2925
+
2926
+		$system_validation = ! is_array($system_validation) && $system_validation ? [] : $system_validation;
2927
+		$validates         = array_merge($custom_validation, $system_validation);
2928
+
2929
+		// if $validate returned error messages (i.e. is_array()) then we need to process them and setup an
2930
+		// appropriate response. HMM, dang this isn't correct, $validates will ALWAYS be an array.
2931
+		//  WE need to make sure there is no actual error messages in validates.
2932
+		if (empty($validates)) {
2933
+			return true;
2934
+		}
2935
+
2936
+		// add the transient so when the form loads we know which fields to highlight
2937
+		$this->_add_transient('edit_message_template', $validates);
2938
+		// setup notices
2939
+		foreach ($validates as $error) {
2940
+			if (isset($error['msg'])) {
2941
+				EE_Error::add_error($error['msg'], __FILE__, __FUNCTION__, __LINE__);
2942
+			}
2943
+		}
2944
+		return false;
2945
+	}
2946
+
2947
+
2948
+	/**
2949
+	 * @param array $field_data
2950
+	 * @param array $form_data
2951
+	 * @return array
2952
+	 * @since   4.10.29.p
2953
+	 */
2954
+	private function getTemplateFieldFormData(array $field_data, array $form_data)
2955
+	{
2956
+		return $form_data + [
2957
+				'MTP_ID'             => $field_data['MTP_ID'],
2958
+				'MTP_template_field' => $field_data['name'],
2959
+				// if they aren't allowed to use all JS, restrict them to standard allowed post tags
2960
+				'MTP_content'        => ! current_user_can('unfiltered_html')
2961
+					? $this->sanitizeMessageTemplateContent($field_data['content'])
2962
+					: $field_data['content'],
2963
+			];
2964
+	}
2965
+
2966
+
2967
+	/**
2968
+	 * @param string $template_field
2969
+	 * @param array  $form_data
2970
+	 * @return bool
2971
+	 * @throws EE_Error
2972
+	 * @since 4.10.29.p
2973
+	 */
2974
+	private function updateMessageTemplates($template_field, array $form_data)
2975
+	{
2976
+		$MTP_ID                  = $form_data['MTP_ID'];
2977
+		$message_template_fields = [
2978
+			'GRP_ID'             => $form_data['GRP_ID'],
2979
+			'MTP_template_field' => $form_data['MTP_template_field'],
2980
+			'MTP_context'        => $form_data['MTP_context'],
2981
+			'MTP_content'        => $form_data['MTP_content'],
2982
+		];
2983
+
2984
+		$hasMtpID = ! empty($MTP_ID);
2985
+		// if we have a MTP_ID for this field then update it, otherwise insert.
2986
+		// this has already been through the template field validator and sanitized, so it will be
2987
+		// safe to insert this field.  Why insert?  This typically happens when we introduce a new
2988
+		// message template field in a messenger/message type and existing users don't have the
2989
+		// default setup for it.
2990
+		// @link https://events.codebasehq.com/projects/event-espresso/tickets/9465
2991
+		$updated = $hasMtpID
2992
+			? $this->getMtpModel()->update($message_template_fields, [['MTP_ID' => $MTP_ID]])
2993
+			: $this->getMtpModel()->insert($message_template_fields);
2994
+
2995
+		$insert_failed = ! $hasMtpID && ! $updated;
2996
+		// updates will return 0 if the field was not changed (ie: no changes = nothing actually updated)
2997
+		// but we won't consider that a problem, but if it returns false, then something went BOOM!
2998
+		$update_failed = $hasMtpID && $updated === false;
2999
+
3000
+		if ($insert_failed || $update_failed) {
3001
+			EE_Error::add_error(
3002
+				sprintf(
3003
+					esc_html__('%s field was NOT updated for some reason', 'event_espresso'),
3004
+					$template_field
3005
+				),
3006
+				__FILE__,
3007
+				__FUNCTION__,
3008
+				__LINE__
3009
+			);
3010
+			return false;
3011
+		}
3012
+		return true;
3013
+	}
3014
+
3015
+
3016
+	/**
3017
+	 * @param array $form_data
3018
+	 * @return bool
3019
+	 * @throws EE_Error
3020
+	 * @since 4.10.29.p
3021
+	 */
3022
+	private function updateMessageTemplateGroup(array $form_data)
3023
+	{
3024
+		$GRP_ID  = $form_data['GRP_ID'];
3025
+		$updated = $this->getMtgModel()->update(
3026
+		// fields and values
3027
+			[
3028
+				'MTP_user_id'      => $form_data['MTP_user_id'],
3029
+				'MTP_messenger'    => $form_data['MTP_messenger'],
3030
+				'MTP_message_type' => $form_data['MTP_message_type'],
3031
+				'MTP_is_global'    => $form_data['MTP_is_global'],
3032
+				'MTP_is_override'  => $form_data['MTP_is_override'],
3033
+				'MTP_deleted'      => $form_data['MTP_deleted'],
3034
+				'MTP_is_active'    => $form_data['MTP_is_active'],
3035
+				'MTP_name'         => $this->request->getRequestParam('ee_msg_non_global_fields[MTP_name]', ''),
3036
+				'MTP_description'  => $this->request->getRequestParam(
3037
+					'ee_msg_non_global_fields[MTP_description]',
3038
+					''
3039
+				),
3040
+			],
3041
+			// where
3042
+			[['GRP_ID' => $GRP_ID]]
3043
+		);
3044
+
3045
+		if ($updated === false) {
3046
+			EE_Error::add_error(
3047
+				sprintf(
3048
+					esc_html__(
3049
+						'The Message Template Group (%d) was NOT updated for some reason',
3050
+						'event_espresso'
3051
+					),
3052
+					$form_data['GRP_ID']
3053
+				),
3054
+				__FILE__,
3055
+				__FUNCTION__,
3056
+				__LINE__
3057
+			);
3058
+			return false;
3059
+		}
3060
+		// k now we need to ensure the template_pack and template_variation fields are set.
3061
+		$template_pack      = $this->request->getRequestParam('MTP_template_pack', 'default');
3062
+		$template_variation = $this->request->getRequestParam('MTP_template_variation', 'default');
3063
+
3064
+		$message_template_group = $this->getMtgModel()->get_one_by_ID($GRP_ID);
3065
+		if ($message_template_group instanceof EE_Message_Template_Group) {
3066
+			$message_template_group->set_template_pack_name($template_pack);
3067
+			$message_template_group->set_template_pack_variation($template_variation);
3068
+		}
3069
+		return true;
3070
+	}
3071
+
3072
+
3073
+	/**
3074
+	 * recursively runs wp_kses() on message template content in a model safe manner
3075
+	 *
3076
+	 * @param array|string $content
3077
+	 * @return array|string
3078
+	 * @since   4.10.29.p
3079
+	 */
3080
+	private function sanitizeMessageTemplateContent($content)
3081
+	{
3082
+		if (is_array($content)) {
3083
+			foreach ($content as $key => $value) {
3084
+				$content[ $key ] = $this->sanitizeMessageTemplateContent($value);
3085
+			}
3086
+			return $content;
3087
+		}
3088
+		// remove slashes so wp_kses() works properly
3089
+		// wp_kses_stripslashes() only removes slashes from double-quotes,
3090
+		// so attributes using single quotes always appear invalid.
3091
+		$content = stripslashes($content);
3092
+		$content = wp_kses($content, wp_kses_allowed_html('post'));
3093
+		// But currently the models expect slashed data, so after wp_kses()
3094
+		// runs we need to re-slash the data. Sheesh.
3095
+		// See https://events.codebasehq.com/projects/event-espresso/tickets/11211#update-47321587
3096
+		return addslashes($content);
3097
+	}
3098
+
3099
+
3100
+	/**
3101
+	 * @param string $messenger
3102
+	 * @param string $message_type
3103
+	 * @param string $context
3104
+	 * @return string
3105
+	 * @since 4.10.29.p
3106
+	 */
3107
+	private function generateUpdateDescription($messenger, $message_type, $context)
3108
+	{
3109
+		// need the message type and messenger objects to be able to use the labels for the notices
3110
+		$messenger_object = $this->_message_resource_manager->get_messenger($messenger);
3111
+		$messenger_label  = $messenger_object instanceof EE_messenger
3112
+			? ucwords($messenger_object->label['singular'])
3113
+			: '';
3114
+
3115
+		$message_type_object = $this->_message_resource_manager->get_message_type($message_type);
3116
+		$message_type_label  = $message_type_object instanceof EE_message_type
3117
+			? ucwords($message_type_object->label['singular'])
3118
+			: '';
3119
+
3120
+		$context   = ucwords(str_replace('_', ' ', $context));
3121
+		$item_desc = $messenger_label && $message_type_label
3122
+			? $messenger_label . ' ' . $message_type_label . ' ' . $context . ' '
3123
+			: '';
3124
+		$item_desc .= 'Message Template';
3125
+		return $item_desc;
3126
+	}
3127
+
3128
+
3129
+	/**
3130
+	 * @param string $messenger
3131
+	 * @param string $message_type
3132
+	 * @param string $context
3133
+	 * @return bool
3134
+	 * @throws EE_Error
3135
+	 * @throws ReflectionException
3136
+	 * @since 4.10.29.p
3137
+	 */
3138
+	private function performTestSendAfterUpdate($messenger, $message_type, $context)
3139
+	{
3140
+		// was a test send triggered?
3141
+		if ($this->request->requestParamIsSet('test_button')) {
3142
+			EE_Error::overwrite_success();
3143
+			$this->_do_test_send($context, $messenger, $message_type);
3144
+			return true;
3145
+		}
3146
+		return false;
3147
+	}
3148
+
3149
+
3150
+	/**
3151
+	 * processes a test send request to do an actual messenger delivery test for the given message template being tested
3152
+	 *
3153
+	 * @param string $context      what context being tested
3154
+	 * @param string $messenger    messenger being tested
3155
+	 * @param string $message_type message type being tested
3156
+	 * @throws EE_Error
3157
+	 * @throws InvalidArgumentException
3158
+	 * @throws InvalidDataTypeException
3159
+	 * @throws InvalidInterfaceException
3160
+	 * @throws ReflectionException
3161
+	 */
3162
+	protected function _do_test_send($context, $messenger, $message_type)
3163
+	{
3164
+		// set things up for preview
3165
+		$this->request->setRequestParam('messenger', $messenger);
3166
+		$this->request->setRequestParam('message_type', $message_type);
3167
+		$this->request->setRequestParam('context', $context);
3168
+		$GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
3169
+		$this->request->setRequestParam('GRP_ID', $GRP_ID);
3170
+
3171
+		$active_messenger  = $this->_message_resource_manager->get_active_messenger($messenger);
3172
+		$test_settings_fld = $this->request->getRequestParam('test_settings_fld', [], 'string', true);
3173
+
3174
+		// let's save any existing fields that might be required by the messenger
3175
+		if (
3176
+			! empty($test_settings_fld)
3177
+			&& $active_messenger instanceof EE_messenger
3178
+			&& apply_filters(
3179
+				'FHEE__Messages_Admin_Page__do_test_send__set_existing_test_settings',
3180
+				true,
3181
+				$test_settings_fld,
3182
+				$active_messenger
3183
+			)
3184
+		) {
3185
+			$active_messenger->set_existing_test_settings($test_settings_fld);
3186
+		}
3187
+
3188
+		/**
3189
+		 * Use filter to add additional controls on whether message can send or not
3190
+		 */
3191
+		if (
3192
+			apply_filters(
3193
+				'FHEE__Messages_Admin_Page__do_test_send__can_send',
3194
+				true,
3195
+				$context,
3196
+				$this->request->requestParams(),
3197
+				$messenger,
3198
+				$message_type
3199
+			)
3200
+		) {
3201
+			if (EEM_Event::instance()->count() > 0) {
3202
+				$success = $this->_preview_message(true);
3203
+				if ($success) {
3204
+					EE_Error::add_success(esc_html__('Test message sent', 'event_espresso'));
3205
+				} else {
3206
+					EE_Error::add_error(
3207
+						esc_html__('The test message was not sent', 'event_espresso'),
3208
+						__FILE__,
3209
+						__FUNCTION__,
3210
+						__LINE__
3211
+					);
3212
+				}
3213
+			} else {
3214
+				$this->noEventsErrorMessage(true);
3215
+			}
3216
+		}
3217
+	}
3218
+
3219
+
3220
+	/**
3221
+	 * _generate_new_templates
3222
+	 * This will handle the messenger, message_type selection when "adding a new custom template" for an event and will
3223
+	 * automatically create the defaults for the event.  The user would then be redirected to edit the default context
3224
+	 * for the event.
3225
+	 *
3226
+	 *
3227
+	 * @param string $messenger      the messenger we are generating templates for
3228
+	 * @param array  $message_types  array of message types that the templates are generated for.
3229
+	 * @param int    $GRP_ID         If this is a custom template being generated then a GRP_ID needs to be included to
3230
+	 *                               indicate the message_template_group being used as the base.
3231
+	 *
3232
+	 * @param bool   $global
3233
+	 *
3234
+	 * @return array|bool array of data required for the redirect to the correct edit page or bool if
3235
+	 *                               encountering problems.
3236
+	 * @throws EE_Error
3237
+	 * @throws ReflectionException
3238
+	 */
3239
+	protected function _generate_new_templates($messenger, $message_types, $GRP_ID = 0, $global = false)
3240
+	{
3241
+		// if no $message_types are given then that's okay... this may be a messenger that just adds shortcodes, so we
3242
+		// just don't generate any templates.
3243
+		if (empty($message_types)) {
3244
+			return [];
3245
+		}
3246
+
3247
+		$templates = EEH_MSG_Template::generate_new_templates($messenger, $message_types, $GRP_ID, $global);
3248
+		return $templates[0];
3249
+	}
3250
+
3251
+
3252
+	/**
3253
+	 * [_trash_or_restore_message_template]
3254
+	 *
3255
+	 * @param boolean $trash  whether to move an item to trash/restore (TRUE) or restore it (FALSE)
3256
+	 * @param boolean $all    whether this is going to trash/restore all contexts within a template group (TRUE) OR just
3257
+	 *                        an individual context (FALSE).
3258
+	 * @return void
3259
+	 * @throws EE_Error
3260
+	 * @throws InvalidArgumentException
3261
+	 * @throws InvalidDataTypeException
3262
+	 * @throws InvalidInterfaceException
3263
+	 */
3264
+	protected function _trash_or_restore_message_template($trash = true, $all = false)
3265
+	{
3266
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3267
+
3268
+		$success = 1;
3269
+
3270
+		// incoming GRP_IDs
3271
+		if ($all) {
3272
+			// Checkboxes
3273
+			$checkboxes = $this->request->getRequestParam('checkbox', [], 'int', true);
3274
+			if (! empty($checkboxes)) {
3275
+				// if array has more than one element then success message should be plural.
3276
+				// todo: what about nonce?
3277
+				$success = count($checkboxes) > 1 ? 2 : 1;
3278
+
3279
+				// cycle through checkboxes
3280
+				while (list($GRP_ID, $value) = each($checkboxes)) {
3281
+					$trashed_or_restored = $trash
3282
+						? $this->getMtgModel()->delete_by_ID($GRP_ID)
3283
+						: $this->getMtgModel()->restore_by_ID($GRP_ID);
3284
+					if (! $trashed_or_restored) {
3285
+						$success = 0;
3286
+					}
3287
+				}
3288
+			} else {
3289
+				// grab single GRP_ID and handle
3290
+				$GRP_ID = $this->request->getRequestParam('id', 0, 'int');
3291
+				if (! empty($GRP_ID)) {
3292
+					$trashed_or_restored = $trash
3293
+						? $this->getMtgModel()->delete_by_ID($GRP_ID)
3294
+						: $this->getMtgModel()->restore_by_ID($GRP_ID);
3295
+					if (! $trashed_or_restored) {
3296
+						$success = 0;
3297
+					}
3298
+				} else {
3299
+					$success = 0;
3300
+				}
3301
+			}
3302
+		}
3303
+
3304
+		$action_desc = $trash
3305
+			? esc_html__('moved to the trash', 'event_espresso')
3306
+			: esc_html__('restored', 'event_espresso');
3307
+
3308
+		$template_switch = $this->request->getRequestParam('template_switch', false, 'bool');
3309
+		$action_desc     = $template_switch ? esc_html__('switched', 'event_espresso') : $action_desc;
3310
+
3311
+		$item_desc = $all ? _n(
3312
+			'Message Template Group',
3313
+			'Message Template Groups',
3314
+			$success,
3315
+			'event_espresso'
3316
+		) : _n('Message Template Context', 'Message Template Contexts', $success, 'event_espresso');
3317
+
3318
+		$item_desc = $template_switch
3319
+			? _n('template', 'templates', $success, 'event_espresso')
3320
+			: $item_desc;
3321
+
3322
+		$this->_redirect_after_action($success, $item_desc, $action_desc, []);
3323
+	}
3324
+
3325
+
3326
+	/**
3327
+	 * [_delete_message_template]
3328
+	 * NOTE: this handles not only the deletion of the groups but also all the templates belonging to that group.
3329
+	 *
3330
+	 * @return void
3331
+	 * @throws EE_Error
3332
+	 * @throws InvalidArgumentException
3333
+	 * @throws InvalidDataTypeException
3334
+	 * @throws InvalidInterfaceException
3335
+	 * @throws ReflectionException
3336
+	 */
3337
+	protected function _delete_message_template()
3338
+	{
3339
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3340
+
3341
+		// checkboxes
3342
+		$checkboxes = $this->request->getRequestParam('checkbox', [], 'int', true);
3343
+		if (! empty($checkboxes)) {
3344
+			// if array has more than one element then success message should be plural
3345
+			$success = count($checkboxes) > 1 ? 2 : 1;
3346
+
3347
+			// cycle through bulk action checkboxes
3348
+			while (list($GRP_ID, $value) = each($checkboxes)) {
3349
+				$success = $this->_delete_mtp_permanently($GRP_ID) ? $success : false;
3350
+			}
3351
+		} else {
3352
+			// grab single grp_id and delete
3353
+			$GRP_ID  = $this->request->getRequestParam('id', 0, 'int');
3354
+			$success = $this->_delete_mtp_permanently($GRP_ID);
3355
+		}
3356
+
3357
+		$this->_redirect_after_action($success, 'Message Templates', 'deleted', []);
3358
+	}
3359
+
3360
+
3361
+	/**
3362
+	 * helper for permanently deleting a mtP group and all related message_templates
3363
+	 *
3364
+	 * @param int  $GRP_ID        The group being deleted
3365
+	 * @param bool $include_group whether to delete the Message Template Group as well.
3366
+	 * @return bool boolean to indicate the success of the deletes or not.
3367
+	 * @throws EE_Error
3368
+	 * @throws InvalidArgumentException
3369
+	 * @throws InvalidDataTypeException
3370
+	 * @throws InvalidInterfaceException
3371
+	 * @throws ReflectionException
3372
+	 * @throws ReflectionException
3373
+	 */
3374
+	private function _delete_mtp_permanently($GRP_ID, $include_group = true)
3375
+	{
3376
+		$success = true;
3377
+		// first let's GET this group
3378
+		$MTG = $this->getMtgModel()->get_one_by_ID($GRP_ID);
3379
+		// then delete permanently all the related Message Templates
3380
+		$deleted = $MTG->delete_related_permanently('Message_Template');
3381
+
3382
+		if ($deleted === 0) {
3383
+			$success = false;
3384
+		}
3385
+
3386
+		// now delete permanently this particular group
3387
+
3388
+		if ($include_group && ! $MTG->delete_permanently()) {
3389
+			$success = false;
3390
+		}
3391
+
3392
+		return $success;
3393
+	}
3394
+
3395
+
3396
+	/**
3397
+	 *    _learn_more_about_message_templates_link
3398
+	 *
3399
+	 * @access protected
3400
+	 * @return string
3401
+	 */
3402
+	protected function _learn_more_about_message_templates_link()
3403
+	{
3404
+		return '<a class="hidden" style="margin:0 20px; cursor:pointer; font-size:12px;" >'
3405
+			   . esc_html__('learn more about how message templates works', 'event_espresso')
3406
+			   . '</a>';
3407
+	}
3408
+
3409
+
3410
+	/**
3411
+	 * Used for setting up messenger/message type activation.  This loads up the initial view.  The rest is handled by
3412
+	 * ajax and other routes.
3413
+	 *
3414
+	 * @return void
3415
+	 * @throws DomainException
3416
+	 * @throws EE_Error
3417
+	 */
3418
+	protected function _settings()
3419
+	{
3420
+		$this->_set_m_mt_settings();
3421
+
3422
+		// let's setup the messenger tabs
3423
+		$this->_template_args['admin_page_header'] = EEH_Tabbed_Content::tab_text_links(
3424
+			$this->_m_mt_settings['messenger_tabs'],
3425
+			'messenger_links',
3426
+			'|',
3427
+			$this->request->getRequestParam('selected_messenger', 'email')
3428
+		);
3429
+
3430
+		$this->_template_args['before_admin_page_content'] = '<div class="ui-widget ui-helper-clearfix">';
3431
+		$this->_template_args['after_admin_page_content']  = '</div><!-- end .ui-widget -->';
3432
+
3433
+		$this->display_admin_page_with_sidebar();
3434
+	}
3435
+
3436
+
3437
+	/**
3438
+	 * This sets the $_m_mt_settings property for when needed (used on the Messages settings page)
3439
+	 *
3440
+	 * @access protected
3441
+	 * @return void
3442
+	 * @throws DomainException
3443
+	 */
3444
+	protected function _set_m_mt_settings()
3445
+	{
3446
+		// first if this is already set then lets get out no need to regenerate data.
3447
+		if (! empty($this->_m_mt_settings)) {
3448
+			return;
3449
+		}
3450
+
3451
+		// get all installed messengers and message_types
3452
+		$messengers    = $this->_message_resource_manager->installed_messengers();
3453
+		$message_types = $this->_message_resource_manager->installed_message_types();
3454
+
3455
+
3456
+		// assemble the array for the _tab_text_links helper
3457
+
3458
+		foreach ($messengers as $messenger) {
3459
+			$active = $this->_message_resource_manager->is_messenger_active($messenger->name);
3460
+			$class = 'ee-messenger-' .  sanitize_key($messenger->label['singular']);
3461
+			$this->_m_mt_settings['messenger_tabs'][ $messenger->name ] = [
3462
+				'label' => ucwords($messenger->label['singular']),
3463
+				'class' => $active ? "{$class} messenger-active" : $class,
3464
+				'href'  => $messenger->name,
3465
+				'title' => esc_html__('Modify this Messenger', 'event_espresso'),
3466
+				'slug'  => $messenger->name,
3467
+				'obj'   => $messenger,
3468
+				'icon' => $active
3469
+					? '<span class="dashicons dashicons-yes-alt"></span>'
3470
+					: '<span class="dashicons dashicons-remove"></span>',
3471
+			];
3472
+
3473
+
3474
+			$message_types_for_messenger = $messenger->get_valid_message_types();
3475
+
3476
+			foreach ($message_types as $message_type) {
3477
+				// first we need to verify that this message type is valid with this messenger. Cause if it isn't then
3478
+				// it shouldn't show in either the inactive OR active metabox.
3479
+				if (! in_array($message_type->name, $message_types_for_messenger, true)) {
3480
+					continue;
3481
+				}
3482
+
3483
+				$a_or_i = $this->_message_resource_manager->is_message_type_active_for_messenger(
3484
+					$messenger->name,
3485
+					$message_type->name
3486
+				)
3487
+					? 'active'
3488
+					: 'inactive';
3489
+
3490
+				$this->_m_mt_settings['message_type_tabs'][ $messenger->name ][ $a_or_i ][ $message_type->name ] = [
3491
+					'label'    => ucwords($message_type->label['singular']),
3492
+					'class'    => 'message-type-' . $a_or_i,
3493
+					'slug_id'  => $message_type->name . '-messagetype-' . $messenger->name,
3494
+					'mt_nonce' => wp_create_nonce($message_type->name . '_nonce'),
3495
+					'href'     => 'espresso_' . $message_type->name . '_message_type_settings',
3496
+					'title'    => $a_or_i === 'active'
3497
+						? esc_html__('Drag this message type to the Inactive window to deactivate', 'event_espresso')
3498
+						: esc_html__('Drag this message type to the messenger to activate', 'event_espresso'),
3499
+					'content'  => $a_or_i === 'active'
3500
+						? $this->_message_type_settings_content($message_type, $messenger, true)
3501
+						: $this->_message_type_settings_content($message_type, $messenger),
3502
+					'slug'     => $message_type->name,
3503
+					'active'   => $a_or_i === 'active',
3504
+					'obj'      => $message_type,
3505
+				];
3506
+			}
3507
+		}
3508
+	}
3509
+
3510
+
3511
+	/**
3512
+	 * This just prepares the content for the message type settings
3513
+	 *
3514
+	 * @param EE_message_type $message_type The message type object
3515
+	 * @param EE_messenger    $messenger    The messenger object
3516
+	 * @param boolean         $active       Whether the message type is active or not
3517
+	 * @return string html output for the content
3518
+	 * @throws DomainException
3519
+	 */
3520
+	protected function _message_type_settings_content($message_type, $messenger, $active = false)
3521
+	{
3522
+		// get message type fields
3523
+		$fields                                         = $message_type->get_admin_settings_fields();
3524
+		$settings_template_args['template_form_fields'] = '';
3525
+
3526
+		if (! empty($fields) && $active) {
3527
+			$existing_settings = $message_type->get_existing_admin_settings($messenger->name);
3528
+			foreach ($fields as $fldname => $fldprops) {
3529
+				$field_id                         = $messenger->name . '-' . $message_type->name . '-' . $fldname;
3530
+				$template_form_field[ $field_id ] = [
3531
+					'name'       => 'message_type_settings[' . $fldname . ']',
3532
+					'label'      => $fldprops['label'],
3533
+					'input'      => $fldprops['field_type'],
3534
+					'type'       => $fldprops['value_type'],
3535
+					'required'   => $fldprops['required'],
3536
+					'validation' => $fldprops['validation'],
3537
+					'value'      => isset($existing_settings[ $fldname ])
3538
+						? $existing_settings[ $fldname ]
3539
+						: $fldprops['default'],
3540
+					'options'    => isset($fldprops['options'])
3541
+						? $fldprops['options']
3542
+						: [],
3543
+					'default'    => isset($existing_settings[ $fldname ])
3544
+						? $existing_settings[ $fldname ]
3545
+						: $fldprops['default'],
3546
+					'css_class'  => 'no-drag',
3547
+					'format'     => $fldprops['format'],
3548
+				];
3549
+			}
3550
+
3551
+
3552
+			$settings_template_args['template_form_fields'] = ! empty($template_form_field)
3553
+				? $this->_generate_admin_form_fields(
3554
+					$template_form_field,
3555
+					'string',
3556
+					'ee_mt_activate_form'
3557
+				)
3558
+				: '';
3559
+		}
3560
+
3561
+		$settings_template_args['description'] = $message_type->description;
3562
+		// we also need some hidden fields
3563
+		$hidden_fields = [
3564
+			'message_type_settings[messenger]' . $message_type->name    => [
3565
+				'type'  => 'hidden',
3566
+				'value' => $messenger->name,
3567
+			],
3568
+			'message_type_settings[message_type]' . $message_type->name => [
3569
+				'type'  => 'hidden',
3570
+				'value' => $message_type->name,
3571
+			],
3572
+			'type' . $message_type->name                                => [
3573
+				'type'  => 'hidden',
3574
+				'value' => 'message_type',
3575
+			],
3576
+		];
3577
+
3578
+		$settings_template_args['hidden_fields'] = $this->_generate_admin_form_fields(
3579
+			$hidden_fields,
3580
+			'array'
3581
+		);
3582
+		$settings_template_args['show_form']     = empty($settings_template_args['template_form_fields'])
3583
+			? ' hidden'
3584
+			: '';
3585
+
3586
+
3587
+		$template = EE_MSG_TEMPLATE_PATH . 'ee_msg_mt_settings_content.template.php';
3588
+		return EEH_Template::display_template($template, $settings_template_args, true);
3589
+	}
3590
+
3591
+
3592
+	/**
3593
+	 * Generate all the metaboxes for the message types and register them for the messages settings page.
3594
+	 *
3595
+	 * @access protected
3596
+	 * @return void
3597
+	 * @throws DomainException
3598
+	 */
3599
+	protected function _messages_settings_metaboxes()
3600
+	{
3601
+		$this->_set_m_mt_settings();
3602
+		$m_boxes         = $mt_boxes = [];
3603
+		$m_template_args = $mt_template_args = [];
3604
+
3605
+		$selected_messenger = $this->request->getRequestParam('selected_messenger', 'email');
3606
+
3607
+		if (isset($this->_m_mt_settings['messenger_tabs'])) {
3608
+			foreach ($this->_m_mt_settings['messenger_tabs'] as $messenger => $tab_array) {
3609
+				$is_messenger_active = $this->_message_resource_manager->is_messenger_active($messenger);
3610
+				$hide_on_message     = $is_messenger_active ? '' : 'hidden';
3611
+				$hide_off_message    = $is_messenger_active ? 'hidden' : '';
3612
+
3613
+				// messenger meta boxes
3614
+				$active         = $selected_messenger === $messenger;
3615
+				$active_mt_tabs = isset($this->_m_mt_settings['message_type_tabs'][ $messenger ]['active'])
3616
+					? $this->_m_mt_settings['message_type_tabs'][ $messenger ]['active']
3617
+					: '';
3618
+
3619
+				$m_boxes[ $messenger . '_a_box' ] = sprintf(
3620
+					esc_html__('%s Settings', 'event_espresso'),
3621
+					$tab_array['label']
3622
+				);
3623
+
3624
+				$m_template_args[ $messenger . '_a_box' ] = [
3625
+					'active_message_types'   => ! empty($active_mt_tabs) ? $this->_get_mt_tabs($active_mt_tabs) : '',
3626
+					'inactive_message_types' => isset(
3627
+						$this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive']
3628
+					)
3629
+						? $this->_get_mt_tabs($this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive'])
3630
+						: '',
3631
+					'content'                => $this->_get_messenger_box_content($tab_array['obj']),
3632
+					'hidden'                 => $active ? '' : ' hidden',
3633
+					'hide_on_message'        => $hide_on_message,
3634
+					'messenger'              => $messenger,
3635
+					'active'                 => $active,
3636
+				];
3637
+
3638
+				// message type meta boxes
3639
+				// (which is really just the inactive container for each messenger
3640
+				// showing inactive message types for that messenger)
3641
+				$mt_boxes[ $messenger . '_i_box' ]         = esc_html__('Inactive Message Types', 'event_espresso');
3642
+				$mt_template_args[ $messenger . '_i_box' ] = [
3643
+					'active_message_types'   => ! empty($active_mt_tabs) ? $this->_get_mt_tabs($active_mt_tabs) : '',
3644
+					'inactive_message_types' => isset(
3645
+						$this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive']
3646
+					)
3647
+						? $this->_get_mt_tabs($this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive'])
3648
+						: '',
3649
+					'hidden'                 => $active ? '' : ' hidden',
3650
+					'hide_on_message'        => $hide_on_message,
3651
+					'hide_off_message'       => $hide_off_message,
3652
+					'messenger'              => $messenger,
3653
+					'active'                 => $active,
3654
+				];
3655
+			}
3656
+		}
3657
+
3658
+
3659
+		// register messenger metaboxes
3660
+		$m_template_path = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_messenger_mt_meta_box.template.php';
3661
+		foreach ($m_boxes as $box => $label) {
3662
+			$callback_args = ['template_path' => $m_template_path, 'template_args' => $m_template_args[ $box ]];
3663
+			$msgr          = str_replace('_a_box', '', $box);
3664
+			$this->addMetaBox(
3665
+				'espresso_' . $msgr . '_settings',
3666
+				$label,
3667
+				function ($post, $metabox) {
3668
+					EEH_Template::display_template(
3669
+						$metabox['args']['template_path'],
3670
+						$metabox['args']['template_args']
3671
+					);
3672
+				},
3673
+				$this->_current_screen->id,
3674
+				'normal',
3675
+				'high',
3676
+				$callback_args
3677
+			);
3678
+		}
3679
+
3680
+		// register message type metaboxes
3681
+		$mt_template_path = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_messenger_meta_box.template.php';
3682
+		foreach ($mt_boxes as $box => $label) {
3683
+			$callback_args = [
3684
+				'template_path' => $mt_template_path,
3685
+				'template_args' => $mt_template_args[ $box ],
3686
+			];
3687
+			$mt            = str_replace('_i_box', '', $box);
3688
+			$this->addMetaBox(
3689
+				'espresso_' . $mt . '_inactive_mts',
3690
+				$label,
3691
+				function ($post, $metabox) {
3692
+					EEH_Template::display_template(
3693
+						$metabox['args']['template_path'],
3694
+						$metabox['args']['template_args']
3695
+					);
3696
+				},
3697
+				$this->_current_screen->id,
3698
+				'side',
3699
+				'high',
3700
+				$callback_args
3701
+			);
3702
+		}
3703
+
3704
+		// register metabox for global messages settings but only when on the main site.  On single site installs this
3705
+		// will always result in the metabox showing, on multisite installs the metabox will only show on the main site.
3706
+		if (is_main_site()) {
3707
+			$this->addMetaBox(
3708
+				'espresso_global_message_settings',
3709
+				esc_html__('Global Message Settings', 'event_espresso'),
3710
+				[$this, 'global_messages_settings_metabox_content'],
3711
+				$this->_current_screen->id,
3712
+				'normal',
3713
+				'low',
3714
+				[]
3715
+			);
3716
+		}
3717
+	}
3718
+
3719
+
3720
+	/**
3721
+	 *  This generates the content for the global messages settings metabox.
3722
+	 *
3723
+	 * @return void
3724
+	 * @throws EE_Error
3725
+	 * @throws InvalidArgumentException
3726
+	 * @throws ReflectionException
3727
+	 * @throws InvalidDataTypeException
3728
+	 * @throws InvalidInterfaceException
3729
+	 */
3730
+	public function global_messages_settings_metabox_content()
3731
+	{
3732
+		$form = $this->_generate_global_settings_form();
3733
+		echo wp_kses(
3734
+			$form->form_open(
3735
+				$this->add_query_args_and_nonce(['action' => 'update_global_settings'], EE_MSG_ADMIN_URL),
3736
+				'POST'
3737
+			),
3738
+			AllowedTags::getWithFormTags()
3739
+		);
3740
+		echo wp_kses($form->get_html(), AllowedTags::getWithFormTags());
3741
+		echo wp_kses($form->form_close(), AllowedTags::getWithFormTags());
3742
+	}
3743
+
3744
+
3745
+	/**
3746
+	 * This generates and returns the form object for the global messages settings.
3747
+	 *
3748
+	 * @return EE_Form_Section_Proper
3749
+	 * @throws EE_Error
3750
+	 * @throws InvalidArgumentException
3751
+	 * @throws ReflectionException
3752
+	 * @throws InvalidDataTypeException
3753
+	 * @throws InvalidInterfaceException
3754
+	 */
3755
+	protected function _generate_global_settings_form()
3756
+	{
3757
+		/** @var EE_Network_Core_Config $network_config */
3758
+		$network_config = EE_Registry::instance()->NET_CFG->core;
3759
+
3760
+		return new EE_Form_Section_Proper(
3761
+			[
3762
+				'name'            => 'global_messages_settings',
3763
+				'html_id'         => 'global_messages_settings',
3764
+				'html_class'      => 'form-table',
3765
+				'layout_strategy' => new EE_Admin_Two_Column_Layout(),
3766
+				'subsections'     => apply_filters(
3767
+					'FHEE__Messages_Admin_Page__global_messages_settings_metabox_content__form_subsections',
3768
+					[
3769
+						'do_messages_on_same_request' => new EE_Select_Input(
3770
+							[
3771
+								true  => esc_html__('On the same request', 'event_espresso'),
3772
+								false => esc_html__('On a separate request', 'event_espresso'),
3773
+							],
3774
+							[
3775
+								'default'         => $network_config->do_messages_on_same_request,
3776
+								'html_label_text' => esc_html__(
3777
+									'Generate and send all messages:',
3778
+									'event_espresso'
3779
+								),
3780
+								'html_help_text'  => esc_html__(
3781
+									'By default the messages system uses a more efficient means of processing messages on separate requests and utilizes the wp-cron scheduling system.  This makes things execute faster for people registering for your events.  However, if the wp-cron system is disabled on your site and there is no alternative in place, then you can change this so messages are always executed on the same request.',
3782
+									'event_espresso'
3783
+								),
3784
+							]
3785
+						),
3786
+						'delete_threshold'            => new EE_Select_Input(
3787
+							[
3788
+								0  => esc_html__('Forever', 'event_espresso'),
3789
+								3  => esc_html__('3 Months', 'event_espresso'),
3790
+								6  => esc_html__('6 Months', 'event_espresso'),
3791
+								9  => esc_html__('9 Months', 'event_espresso'),
3792
+								12 => esc_html__('12 Months', 'event_espresso'),
3793
+								24 => esc_html__('24 Months', 'event_espresso'),
3794
+								36 => esc_html__('36 Months', 'event_espresso'),
3795
+							],
3796
+							[
3797
+								'default'         => EE_Registry::instance()->CFG->messages->delete_threshold,
3798
+								'html_label_text' => esc_html__('Cleanup of old messages:', 'event_espresso'),
3799
+								'html_help_text'  => esc_html__(
3800
+									'You can control how long a record of processed messages is kept via this option.',
3801
+									'event_espresso'
3802
+								),
3803
+							]
3804
+						),
3805
+						'update_settings'             => new EE_Submit_Input(
3806
+							[
3807
+								'default'         => esc_html__('Update', 'event_espresso'),
3808
+								'html_label_text' => '',
3809
+							]
3810
+						),
3811
+					]
3812
+				),
3813
+			]
3814
+		);
3815
+	}
3816
+
3817
+
3818
+	/**
3819
+	 * This handles updating the global settings set on the admin page.
3820
+	 *
3821
+	 * @throws EE_Error
3822
+	 * @throws InvalidDataTypeException
3823
+	 * @throws InvalidInterfaceException
3824
+	 * @throws InvalidArgumentException
3825
+	 * @throws ReflectionException
3826
+	 */
3827
+	protected function _update_global_settings()
3828
+	{
3829
+		/** @var EE_Network_Core_Config $network_config */
3830
+		$network_config  = EE_Registry::instance()->NET_CFG->core;
3831
+		$messages_config = EE_Registry::instance()->CFG->messages;
3832
+		$form            = $this->_generate_global_settings_form();
3833
+		if ($form->was_submitted()) {
3834
+			$form->receive_form_submission();
3835
+			if ($form->is_valid()) {
3836
+				$valid_data = $form->valid_data();
3837
+				foreach ($valid_data as $property => $value) {
3838
+					$setter = 'set_' . $property;
3839
+					if (method_exists($network_config, $setter)) {
3840
+						$network_config->{$setter}($value);
3841
+					} elseif (
3842
+						property_exists($network_config, $property)
3843
+						&& $network_config->{$property} !== $value
3844
+					) {
3845
+						$network_config->{$property} = $value;
3846
+					} elseif (
3847
+						property_exists($messages_config, $property)
3848
+						&& $messages_config->{$property} !== $value
3849
+					) {
3850
+						$messages_config->{$property} = $value;
3851
+					}
3852
+				}
3853
+				// only update if the form submission was valid!
3854
+				EE_Registry::instance()->NET_CFG->update_config(true, false);
3855
+				EE_Registry::instance()->CFG->update_espresso_config();
3856
+				EE_Error::overwrite_success();
3857
+				EE_Error::add_success(esc_html__('Global message settings were updated', 'event_espresso'));
3858
+			}
3859
+		}
3860
+		$this->_redirect_after_action(0, '', '', ['action' => 'settings'], true);
3861
+	}
3862
+
3863
+
3864
+	/**
3865
+	 * this prepares the messenger tabs that can be dragged in and out of messenger boxes to activate/deactivate
3866
+	 *
3867
+	 * @param array $tab_array This is an array of message type tab details used to generate the tabs
3868
+	 * @return string html formatted tabs
3869
+	 * @throws DomainException
3870
+	 */
3871
+	protected function _get_mt_tabs($tab_array)
3872
+	{
3873
+		$tab_array = (array) $tab_array;
3874
+		$template  = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_mt_settings_tab_item.template.php';
3875
+		$tabs      = '';
3876
+
3877
+		foreach ($tab_array as $tab) {
3878
+			$tabs .= EEH_Template::display_template($template, $tab, true);
3879
+		}
3880
+
3881
+		return $tabs;
3882
+	}
3883
+
3884
+
3885
+	/**
3886
+	 * This prepares the content of the messenger meta box admin settings
3887
+	 *
3888
+	 * @param EE_messenger $messenger The messenger we're setting up content for
3889
+	 * @return string html formatted content
3890
+	 * @throws DomainException
3891
+	 */
3892
+	protected function _get_messenger_box_content(EE_messenger $messenger)
3893
+	{
3894
+
3895
+		$fields = $messenger->get_admin_settings_fields();
3896
+
3897
+		$settings_template_args['template_form_fields'] = '';
3898
+		// is $messenger active?
3899
+		$settings_template_args['active'] = $this->_message_resource_manager->is_messenger_active($messenger->name);
3900
+
3901
+
3902
+		if (! empty($fields)) {
3903
+			$existing_settings = $messenger->get_existing_admin_settings();
3904
+
3905
+			foreach ($fields as $field_name => $field_props) {
3906
+				$field_id                         = $messenger->name . '-' . $field_name;
3907
+				$template_form_field[ $field_id ] = [
3908
+					'name'       => 'messenger_settings[' . $field_id . ']',
3909
+					'label'      => $field_props['label'],
3910
+					'input'      => $field_props['field_type'],
3911
+					'type'       => $field_props['value_type'],
3912
+					'required'   => $field_props['required'],
3913
+					'validation' => $field_props['validation'],
3914
+					'value'      => $existing_settings[ $field_id ] ?? $field_props['default'],
3915
+					'css_class'  => '',
3916
+					'format'     => $field_props['format'],
3917
+				];
3918
+			}
3919
+
3920
+			$settings_template_args['template_form_fields'] = ! empty($template_form_field)
3921
+				? $this->_generate_admin_form_fields($template_form_field, 'string', 'ee_m_activate_form')
3922
+				: '';
3923
+		}
3924
+
3925
+		// we also need some hidden fields
3926
+		$settings_template_args['hidden_fields'] = [
3927
+			'messenger_settings[messenger]' . $messenger->name => [
3928
+				'type'  => 'hidden',
3929
+				'value' => $messenger->name,
3930
+			],
3931
+			'type' . $messenger->name                          => [
3932
+				'type'  => 'hidden',
3933
+				'value' => 'messenger',
3934
+			],
3935
+		];
3936
+
3937
+		// make sure any active message types that are existing are included in the hidden fields
3938
+		if (isset($this->_m_mt_settings['message_type_tabs'][ $messenger->name ]['active'])) {
3939
+			foreach ($this->_m_mt_settings['message_type_tabs'][ $messenger->name ]['active'] as $mt => $values) {
3940
+				$settings_template_args['hidden_fields'][ 'messenger_settings[message_types][' . $mt . ']' ] = [
3941
+					'type'  => 'hidden',
3942
+					'value' => $mt,
3943
+				];
3944
+			}
3945
+		}
3946
+		$settings_template_args['hidden_fields'] = $this->_generate_admin_form_fields(
3947
+			$settings_template_args['hidden_fields'],
3948
+			'array'
3949
+		);
3950
+		$active                                  =
3951
+			$this->_message_resource_manager->is_messenger_active($messenger->name);
3952
+
3953
+		$settings_template_args['messenger']           = $messenger->name;
3954
+		$settings_template_args['description']         = $messenger->description;
3955
+		$settings_template_args['show_hide_edit_form'] = $active ? '' : ' hidden';
3956
+
3957
+
3958
+		$settings_template_args['show_hide_edit_form'] = $this->_message_resource_manager->is_messenger_active(
3959
+			$messenger->name
3960
+		)
3961
+			? $settings_template_args['show_hide_edit_form']
3962
+			: ' hidden';
3963
+
3964
+		$settings_template_args['show_hide_edit_form'] = empty($settings_template_args['template_form_fields'])
3965
+			? ' hidden'
3966
+			: $settings_template_args['show_hide_edit_form'];
3967
+
3968
+
3969
+		$settings_template_args['on_off_action'] = $active ? 'messenger-off' : 'messenger-on';
3970
+		$settings_template_args['nonce']         = wp_create_nonce('activate_' . $messenger->name . '_toggle_nonce');
3971
+		$settings_template_args['on_off_status'] = $active;
3972
+		$template                                = EE_MSG_TEMPLATE_PATH . 'ee_msg_m_settings_content.template.php';
3973
+		return EEH_Template::display_template(
3974
+			$template,
3975
+			$settings_template_args,
3976
+			true
3977
+		);
3978
+	}
3979
+
3980
+
3981
+	/**
3982
+	 * used by ajax on the messages settings page to activate|deactivate the messenger
3983
+	 *
3984
+	 * @throws DomainException
3985
+	 * @throws EE_Error
3986
+	 * @throws InvalidDataTypeException
3987
+	 * @throws InvalidInterfaceException
3988
+	 * @throws InvalidArgumentException
3989
+	 * @throws ReflectionException
3990
+	 */
3991
+	public function activate_messenger_toggle()
3992
+	{
3993
+		$success = true;
3994
+		$this->_prep_default_response_for_messenger_or_message_type_toggle();
3995
+		// let's check that we have required data
3996
+
3997
+		if (! $this->_active_messenger_name) {
3998
+			EE_Error::add_error(
3999
+				esc_html__('Messenger name needed to toggle activation. None given', 'event_espresso'),
4000
+				__FILE__,
4001
+				__FUNCTION__,
4002
+				__LINE__
4003
+			);
4004
+			$success = false;
4005
+		}
4006
+
4007
+		// do a nonce check here since we're not arriving via a normal route
4008
+		$nonce     = $this->request->getRequestParam('activate_nonce', '');
4009
+		$nonce_ref = "activate_{$this->_active_messenger_name}_toggle_nonce";
4010
+
4011
+		$this->_verify_nonce($nonce, $nonce_ref);
4012
+
4013
+
4014
+		$status = $this->request->getRequestParam('status');
4015
+		if (! $status) {
4016
+			EE_Error::add_error(
4017
+				esc_html__(
4018
+					'Messenger status needed to know whether activation or deactivation is happening. No status is given',
4019
+					'event_espresso'
4020
+				),
4021
+				__FILE__,
4022
+				__FUNCTION__,
4023
+				__LINE__
4024
+			);
4025
+			$success = false;
4026
+		}
4027
+
4028
+		// do check to verify we have a valid status.
4029
+		if ($status !== 'off' && $status !== 'on') {
4030
+			EE_Error::add_error(
4031
+				sprintf(
4032
+					esc_html__('The given status (%s) is not valid. Must be "off" or "on"', 'event_espresso'),
4033
+					$status
4034
+				),
4035
+				__FILE__,
4036
+				__FUNCTION__,
4037
+				__LINE__
4038
+			);
4039
+			$success = false;
4040
+		}
4041
+
4042
+		if ($success) {
4043
+			// made it here?  Stop dawdling then!!
4044
+			$success = $status === 'off'
4045
+				? $this->_deactivate_messenger($this->_active_messenger_name)
4046
+				: $this->_activate_messenger($this->_active_messenger_name);
4047
+		}
4048
+
4049
+		$this->_template_args['success'] = $success;
4050
+
4051
+		// no special instructions so let's just do the json return (which should automatically do all the special stuff).
4052
+		$this->_return_json();
4053
+	}
4054
+
4055
+
4056
+	/**
4057
+	 * used by ajax from the messages settings page to activate|deactivate a message type
4058
+	 *
4059
+	 * @throws DomainException
4060
+	 * @throws EE_Error
4061
+	 * @throws ReflectionException
4062
+	 * @throws InvalidDataTypeException
4063
+	 * @throws InvalidInterfaceException
4064
+	 * @throws InvalidArgumentException
4065
+	 */
4066
+	public function activate_mt_toggle()
4067
+	{
4068
+		$success = true;
4069
+		$this->_prep_default_response_for_messenger_or_message_type_toggle();
4070
+
4071
+		// let's make sure we have the necessary data
4072
+		if (! $this->_active_message_type_name) {
4073
+			EE_Error::add_error(
4074
+				esc_html__('Message Type name needed to toggle activation. None given', 'event_espresso'),
4075
+				__FILE__,
4076
+				__FUNCTION__,
4077
+				__LINE__
4078
+			);
4079
+			$success = false;
4080
+		}
4081
+
4082
+		if (! $this->_active_messenger_name) {
4083
+			EE_Error::add_error(
4084
+				esc_html__('Messenger name needed to toggle activation. None given', 'event_espresso'),
4085
+				__FILE__,
4086
+				__FUNCTION__,
4087
+				__LINE__
4088
+			);
4089
+			$success = false;
4090
+		}
4091
+
4092
+		$status = $this->request->getRequestParam('status');
4093
+		if (! $status) {
4094
+			EE_Error::add_error(
4095
+				esc_html__(
4096
+					'Messenger status needed to know whether activation or deactivation is happening. No status is given',
4097
+					'event_espresso'
4098
+				),
4099
+				__FILE__,
4100
+				__FUNCTION__,
4101
+				__LINE__
4102
+			);
4103
+			$success = false;
4104
+		}
4105
+
4106
+
4107
+		// do check to verify we have a valid status.
4108
+		if ($status !== 'activate' && $status !== 'deactivate') {
4109
+			EE_Error::add_error(
4110
+				sprintf(
4111
+					esc_html__('The given status (%s) is not valid. Must be "active" or "inactive"', 'event_espresso'),
4112
+					$status
4113
+				),
4114
+				__FILE__,
4115
+				__FUNCTION__,
4116
+				__LINE__
4117
+			);
4118
+			$success = false;
4119
+		}
4120
+
4121
+
4122
+		// do a nonce check here since we're not arriving via a normal route
4123
+		$nonce = $this->request->getRequestParam('mt_nonce', '');
4124
+		$this->_verify_nonce($nonce, "{$this->_active_message_type_name}_nonce");
4125
+
4126
+		if ($success) {
4127
+			// made it here? um, what are you waiting for then?
4128
+			$success = $status === 'deactivate'
4129
+				? $this->_deactivate_message_type_for_messenger(
4130
+					$this->_active_messenger_name,
4131
+					$this->_active_message_type_name
4132
+				)
4133
+				: $this->_activate_message_type_for_messenger(
4134
+					$this->_active_messenger_name,
4135
+					$this->_active_message_type_name
4136
+				);
4137
+		}
4138
+
4139
+		$this->_template_args['success'] = $success;
4140
+		$this->_return_json();
4141
+	}
4142
+
4143
+
4144
+	/**
4145
+	 * Takes care of processing activating a messenger and preparing the appropriate response.
4146
+	 *
4147
+	 * @param string $messenger_name The name of the messenger being activated
4148
+	 * @return bool
4149
+	 * @throws DomainException
4150
+	 * @throws EE_Error
4151
+	 * @throws InvalidArgumentException
4152
+	 * @throws ReflectionException
4153
+	 * @throws InvalidDataTypeException
4154
+	 * @throws InvalidInterfaceException
4155
+	 */
4156
+	protected function _activate_messenger($messenger_name)
4157
+	{
4158
+		$active_messenger          = $this->_message_resource_manager->get_messenger($messenger_name);
4159
+		$message_types_to_activate = $active_messenger instanceof EE_Messenger
4160
+			? $active_messenger->get_default_message_types()
4161
+			: [];
4162
+
4163
+		// ensure is active
4164
+		$this->_message_resource_manager->activate_messenger($active_messenger, $message_types_to_activate);
4165
+
4166
+		// set response_data for reload
4167
+		foreach ($message_types_to_activate as $message_type_name) {
4168
+			$message_type = $this->_message_resource_manager->get_message_type($message_type_name);
4169
+			if (
4170
+				$this->_message_resource_manager->is_message_type_active_for_messenger(
4171
+					$messenger_name,
4172
+					$message_type_name
4173
+				)
4174
+				&& $message_type instanceof EE_message_type
4175
+			) {
4176
+				$this->_template_args['data']['active_mts'][] = $message_type_name;
4177
+				if ($message_type->get_admin_settings_fields()) {
4178
+					$this->_template_args['data']['mt_reload'][] = $message_type_name;
4179
+				}
4180
+			}
4181
+		}
4182
+
4183
+		// add success message for activating messenger
4184
+		return $this->_setup_response_message_for_activating_messenger_with_message_types($active_messenger);
4185
+	}
4186
+
4187
+
4188
+	/**
4189
+	 * Takes care of processing deactivating a messenger and preparing the appropriate response.
4190
+	 *
4191
+	 * @param string $messenger_name The name of the messenger being activated
4192
+	 * @return bool
4193
+	 * @throws DomainException
4194
+	 * @throws EE_Error
4195
+	 * @throws InvalidArgumentException
4196
+	 * @throws ReflectionException
4197
+	 * @throws InvalidDataTypeException
4198
+	 * @throws InvalidInterfaceException
4199
+	 */
4200
+	protected function _deactivate_messenger($messenger_name)
4201
+	{
4202
+		$active_messenger = $this->_message_resource_manager->get_messenger($messenger_name);
4203
+		$this->_message_resource_manager->deactivate_messenger($messenger_name);
4204
+
4205
+		return $this->_setup_response_message_for_deactivating_messenger_with_message_types($active_messenger);
4206
+	}
4207
+
4208
+
4209
+	/**
4210
+	 * Takes care of processing activating a message type for a messenger and preparing the appropriate response.
4211
+	 *
4212
+	 * @param string $messenger_name    The name of the messenger the message type is being activated for.
4213
+	 * @param string $message_type_name The name of the message type being activated for the messenger
4214
+	 * @return bool
4215
+	 * @throws DomainException
4216
+	 * @throws EE_Error
4217
+	 * @throws InvalidArgumentException
4218
+	 * @throws ReflectionException
4219
+	 * @throws InvalidDataTypeException
4220
+	 * @throws InvalidInterfaceException
4221
+	 */
4222
+	protected function _activate_message_type_for_messenger($messenger_name, $message_type_name)
4223
+	{
4224
+		$active_messenger         = $this->_message_resource_manager->get_messenger($messenger_name);
4225
+		$message_type_to_activate = $this->_message_resource_manager->get_message_type($message_type_name);
4226
+
4227
+		// ensure is active
4228
+		$this->_message_resource_manager->activate_messenger($active_messenger, $message_type_name);
4229
+
4230
+		// set response for load
4231
+		if (
4232
+			$this->_message_resource_manager->is_message_type_active_for_messenger(
4233
+				$messenger_name,
4234
+				$message_type_name
4235
+			)
4236
+		) {
4237
+			$this->_template_args['data']['active_mts'][] = $message_type_name;
4238
+			if ($message_type_to_activate->get_admin_settings_fields()) {
4239
+				$this->_template_args['data']['mt_reload'][] = $message_type_name;
4240
+			}
4241
+		}
4242
+
4243
+		return $this->_setup_response_message_for_activating_messenger_with_message_types(
4244
+			$active_messenger,
4245
+			$message_type_to_activate
4246
+		);
4247
+	}
4248
+
4249
+
4250
+	/**
4251
+	 * Takes care of processing deactivating a message type for a messenger and preparing the appropriate response.
4252
+	 *
4253
+	 * @param string $messenger_name    The name of the messenger the message type is being deactivated for.
4254
+	 * @param string $message_type_name The name of the message type being deactivated for the messenger
4255
+	 * @return bool
4256
+	 * @throws DomainException
4257
+	 * @throws EE_Error
4258
+	 * @throws InvalidArgumentException
4259
+	 * @throws ReflectionException
4260
+	 * @throws InvalidDataTypeException
4261
+	 * @throws InvalidInterfaceException
4262
+	 */
4263
+	protected function _deactivate_message_type_for_messenger($messenger_name, $message_type_name)
4264
+	{
4265
+		$active_messenger = $this->_message_resource_manager->get_messenger($messenger_name);
4266
+		/** @var EE_message_type $message_type_to_activate This will be present because it can't be toggled if it isn't */
4267
+		$message_type_to_deactivate = $this->_message_resource_manager->get_message_type($message_type_name);
4268
+		$this->_message_resource_manager->deactivate_message_type_for_messenger($message_type_name, $messenger_name);
4269
+
4270
+		return $this->_setup_response_message_for_deactivating_messenger_with_message_types(
4271
+			$active_messenger,
4272
+			$message_type_to_deactivate
4273
+		);
4274
+	}
4275
+
4276
+
4277
+	/**
4278
+	 * This just initializes the defaults for activating messenger and message type responses.
4279
+	 */
4280
+	protected function _prep_default_response_for_messenger_or_message_type_toggle()
4281
+	{
4282
+		$this->_template_args['data']['active_mts'] = [];
4283
+		$this->_template_args['data']['mt_reload']  = [];
4284
+	}
4285
+
4286
+
4287
+	/**
4288
+	 * Setup appropriate response for activating a messenger and/or message types
4289
+	 *
4290
+	 * @param EE_messenger         $messenger
4291
+	 * @param EE_message_type|null $message_type
4292
+	 * @return bool
4293
+	 * @throws DomainException
4294
+	 * @throws EE_Error
4295
+	 * @throws InvalidArgumentException
4296
+	 * @throws ReflectionException
4297
+	 * @throws InvalidDataTypeException
4298
+	 * @throws InvalidInterfaceException
4299
+	 */
4300
+	protected function _setup_response_message_for_activating_messenger_with_message_types(
4301
+		$messenger,
4302
+		EE_Message_Type $message_type = null
4303
+	) {
4304
+		// if $messenger isn't a valid messenger object then get out.
4305
+		if (! $messenger instanceof EE_Messenger) {
4306
+			EE_Error::add_error(
4307
+				esc_html__('The messenger being activated is not a valid messenger', 'event_espresso'),
4308
+				__FILE__,
4309
+				__FUNCTION__,
4310
+				__LINE__
4311
+			);
4312
+			return false;
4313
+		}
4314
+		// activated
4315
+		if ($this->_template_args['data']['active_mts']) {
4316
+			EE_Error::overwrite_success();
4317
+			// activated a message type with the messenger
4318
+			if ($message_type instanceof EE_message_type) {
4319
+				EE_Error::add_success(
4320
+					sprintf(
4321
+						esc_html__(
4322
+							'%s message type has been successfully activated with the %s messenger',
4323
+							'event_espresso'
4324
+						),
4325
+						ucwords($message_type->label['singular']),
4326
+						ucwords($messenger->label['singular'])
4327
+					)
4328
+				);
4329
+
4330
+				// if message type was invoice then let's make sure we activate the invoice payment method.
4331
+				if ($message_type->name === 'invoice') {
4332
+					EE_Registry::instance()->load_lib('Payment_Method_Manager');
4333
+					$pm = EE_Payment_Method_Manager::instance()->activate_a_payment_method_of_type('Invoice');
4334
+					if ($pm instanceof EE_Payment_Method) {
4335
+						EE_Error::add_attention(
4336
+							esc_html__(
4337
+								'Activating the invoice message type also automatically activates the invoice payment method.  If you do not wish the invoice payment method to be active, or to change its settings, visit the payment method admin page.',
4338
+								'event_espresso'
4339
+							)
4340
+						);
4341
+					}
4342
+				}
4343
+				// just toggles the entire messenger
4344
+			} else {
4345
+				EE_Error::add_success(
4346
+					sprintf(
4347
+						esc_html__('%s messenger has been successfully activated', 'event_espresso'),
4348
+						ucwords($messenger->label['singular'])
4349
+					)
4350
+				);
4351
+			}
4352
+
4353
+			return true;
4354
+
4355
+			// possible error condition. This will happen when our active_mts data is empty because it is validated for actual active
4356
+			// message types after the activation process.  However its possible some messengers don't HAVE any default_message_types
4357
+			// in which case we just give a success message for the messenger being successfully activated.
4358
+		} else {
4359
+			if (! $messenger->get_default_message_types()) {
4360
+				// messenger doesn't have any default message types so still a success.
4361
+				EE_Error::add_success(
4362
+					sprintf(
4363
+						esc_html__('%s messenger was successfully activated.', 'event_espresso'),
4364
+						ucwords($messenger->label['singular'])
4365
+					)
4366
+				);
4367
+
4368
+				return true;
4369
+			} else {
4370
+				EE_Error::add_error(
4371
+					$message_type instanceof EE_message_type
4372
+					? sprintf(
4373
+						esc_html__(
4374
+							'%s message type was not successfully activated with the %s messenger',
4375
+							'event_espresso'
4376
+						),
4377
+						ucwords($message_type->label['singular']),
4378
+						ucwords($messenger->label['singular'])
4379
+					)
4380
+					: sprintf(
4381
+						esc_html__('%s messenger was not successfully activated', 'event_espresso'),
4382
+						ucwords($messenger->label['singular'])
4383
+					),
4384
+					__FILE__,
4385
+					__FUNCTION__,
4386
+					__LINE__
4387
+				);
4388
+
4389
+				return false;
4390
+			}
4391
+		}
4392
+	}
4393
+
4394
+
4395
+	/**
4396
+	 * This sets up the appropriate response for deactivating a messenger and/or message type.
4397
+	 *
4398
+	 * @param EE_messenger         $messenger
4399
+	 * @param EE_message_type|null $message_type
4400
+	 * @return bool
4401
+	 * @throws DomainException
4402
+	 * @throws EE_Error
4403
+	 * @throws InvalidArgumentException
4404
+	 * @throws ReflectionException
4405
+	 * @throws InvalidDataTypeException
4406
+	 * @throws InvalidInterfaceException
4407
+	 */
4408
+	protected function _setup_response_message_for_deactivating_messenger_with_message_types(
4409
+		$messenger,
4410
+		EE_message_type $message_type = null
4411
+	) {
4412
+		EE_Error::overwrite_success();
4413
+
4414
+		// if $messenger isn't a valid messenger object then get out.
4415
+		if (! $messenger instanceof EE_Messenger) {
4416
+			EE_Error::add_error(
4417
+				esc_html__('The messenger being deactivated is not a valid messenger', 'event_espresso'),
4418
+				__FILE__,
4419
+				__FUNCTION__,
4420
+				__LINE__
4421
+			);
4422
+
4423
+			return false;
4424
+		}
4425
+
4426
+		if ($message_type instanceof EE_message_type) {
4427
+			$message_type_name = $message_type->name;
4428
+			EE_Error::add_success(
4429
+				sprintf(
4430
+					esc_html__(
4431
+						'%s message type has been successfully deactivated for the %s messenger.',
4432
+						'event_espresso'
4433
+					),
4434
+					ucwords($message_type->label['singular']),
4435
+					ucwords($messenger->label['singular'])
4436
+				)
4437
+			);
4438
+		} else {
4439
+			$message_type_name = '';
4440
+			EE_Error::add_success(
4441
+				sprintf(
4442
+					esc_html__('%s messenger has been successfully deactivated.', 'event_espresso'),
4443
+					ucwords($messenger->label['singular'])
4444
+				)
4445
+			);
4446
+		}
4447
+
4448
+		// if messenger was html or message type was invoice then let's make sure we deactivate invoice payment method.
4449
+		if (
4450
+			$messenger->name === 'html'
4451
+			&& (
4452
+				is_null($message_type)
4453
+				|| $message_type_name === 'invoice'
4454
+			)
4455
+		) {
4456
+			EE_Registry::instance()->load_lib('Payment_Method_Manager');
4457
+			$count_updated = EE_Payment_Method_Manager::instance()->deactivate_payment_method('invoice');
4458
+			if ($count_updated > 0) {
4459
+				$msg = $message_type_name === 'invoice'
4460
+					? esc_html__(
4461
+						'Deactivating the invoice message type also automatically deactivates the invoice payment method. In order for invoices to be generated the invoice message type must be active. If you completed this action by mistake, simply reactivate the invoice message type and then visit the payment methods admin page to reactivate the invoice payment method.',
4462
+						'event_espresso'
4463
+					)
4464
+					: esc_html__(
4465
+						'Deactivating the html messenger also automatically deactivates the invoice payment method.  In order for invoices to be generated the html messenger must be be active.  If you completed this action by mistake, simply reactivate the html messenger, then visit the payment methods admin page to reactivate the invoice payment method.',
4466
+						'event_espresso'
4467
+					);
4468
+				EE_Error::add_attention($msg);
4469
+			}
4470
+		}
4471
+
4472
+		return true;
4473
+	}
4474
+
4475
+
4476
+	/**
4477
+	 * handles updating a message type form on messenger activation IF the message type has settings fields. (via ajax)
4478
+	 *
4479
+	 * @throws DomainException
4480
+	 * @throws EE_Error
4481
+	 * @throws EE_Error
4482
+	 */
4483
+	public function update_mt_form()
4484
+	{
4485
+		if (! $this->_active_messenger_name || ! $this->_active_message_type_name) {
4486
+			EE_Error::add_error(
4487
+				esc_html__('Require message type or messenger to send an updated form', 'event_espresso'),
4488
+				__FILE__,
4489
+				__FUNCTION__,
4490
+				__LINE__
4491
+			);
4492
+			$this->_return_json();
4493
+		}
4494
+
4495
+		$message_types = $this->get_installed_message_types();
4496
+		$message_type  = $message_types[ $this->_active_message_type_name ];
4497
+		$messenger     = $this->_message_resource_manager->get_active_messenger($this->_active_messenger_name);
4498
+		$content       = $this->_message_type_settings_content($message_type, $messenger, true);
4499
+
4500
+		$this->_template_args['success'] = true;
4501
+		$this->_template_args['content'] = $content;
4502
+		$this->_return_json();
4503
+	}
4504
+
4505
+
4506
+	/**
4507
+	 * this handles saving the settings for a messenger or message type
4508
+	 *
4509
+	 * @throws EE_Error
4510
+	 * @throws EE_Error
4511
+	 */
4512
+	public function save_settings()
4513
+	{
4514
+		$type = $this->request->getRequestParam('type');
4515
+		if (! $type) {
4516
+			EE_Error::add_error(
4517
+				esc_html__(
4518
+					'Cannot save settings because type is unknown (messenger settings or message type settings?)',
4519
+					'event_espresso'
4520
+				),
4521
+				__FILE__,
4522
+				__FUNCTION__,
4523
+				__LINE__
4524
+			);
4525
+			$this->_template_args['error'] = true;
4526
+			$this->_return_json();
4527
+		}
4528
+
4529
+
4530
+		if ($type === 'messenger') {
4531
+			// this should be an array.
4532
+			$settings  = $this->request->getRequestParam('messenger_settings', [], 'string', true);
4533
+			$messenger = $settings['messenger'];
4534
+			// remove messenger and message_types from settings array
4535
+			unset($settings['messenger'], $settings['message_types']);
4536
+			$this->_message_resource_manager->add_settings_for_messenger($messenger, $settings);
4537
+		} elseif ($type === 'message_type') {
4538
+			$settings     = $this->request->getRequestParam('message_type_settings', [], 'string', true);
4539
+			$messenger    = $settings['messenger'];
4540
+			$message_type = $settings['message_type'];
4541
+			// remove messenger and message_types from settings array
4542
+			unset($settings['messenger'], $settings['message_types']);
4543
+			$this->_message_resource_manager->add_settings_for_message_type($messenger, $message_type, $settings);
4544
+		}
4545
+
4546
+		// okay we should have the data all setup.  Now we just update!
4547
+		$success = $this->_message_resource_manager->update_active_messengers_option();
4548
+
4549
+		if ($success) {
4550
+			EE_Error::add_success(esc_html__('Settings updated', 'event_espresso'));
4551
+		} else {
4552
+			EE_Error::add_error(
4553
+				esc_html__('Settings did not get updated', 'event_espresso'),
4554
+				__FILE__,
4555
+				__FUNCTION__,
4556
+				__LINE__
4557
+			);
4558
+		}
4559
+
4560
+		$this->_template_args['success'] = $success;
4561
+		$this->_return_json();
4562
+	}
4563
+
4564
+
4565
+
4566
+
4567
+	/**  EE MESSAGE PROCESSING ACTIONS **/
4568
+
4569
+
4570
+	/**
4571
+	 * This immediately generates any EE_Message ID's that are selected that are EEM_Message::status_incomplete
4572
+	 * However, this does not send immediately, it just queues for sending.
4573
+	 *
4574
+	 * @throws EE_Error
4575
+	 * @throws InvalidDataTypeException
4576
+	 * @throws InvalidInterfaceException
4577
+	 * @throws InvalidArgumentException
4578
+	 * @throws ReflectionException
4579
+	 * @since 4.9.0
4580
+	 */
4581
+	protected function _generate_now()
4582
+	{
4583
+		EED_Messages::generate_now($this->_get_msg_ids_from_request());
4584
+		$this->_redirect_after_action(false, '', '', [], true);
4585
+	}
4586
+
4587
+
4588
+	/**
4589
+	 * This immediately generates AND sends any EE_Message's selected that are EEM_Message::status_incomplete or that
4590
+	 * are EEM_Message::status_resend or EEM_Message::status_idle
4591
+	 *
4592
+	 * @throws EE_Error
4593
+	 * @throws InvalidDataTypeException
4594
+	 * @throws InvalidInterfaceException
4595
+	 * @throws InvalidArgumentException
4596
+	 * @throws ReflectionException
4597
+	 * @since 4.9.0
4598
+	 */
4599
+	protected function _generate_and_send_now()
4600
+	{
4601
+		EED_Messages::generate_and_send_now($this->_get_msg_ids_from_request());
4602
+		$this->_redirect_after_action(false, '', '', [], true);
4603
+	}
4604
+
4605
+
4606
+	/**
4607
+	 * This queues any EEM_Message::status_sent EE_Message ids in the request for resending.
4608
+	 *
4609
+	 * @throws EE_Error
4610
+	 * @throws InvalidDataTypeException
4611
+	 * @throws InvalidInterfaceException
4612
+	 * @throws InvalidArgumentException
4613
+	 * @throws ReflectionException
4614
+	 * @since 4.9.0
4615
+	 */
4616
+	protected function _queue_for_resending()
4617
+	{
4618
+		EED_Messages::queue_for_resending($this->_get_msg_ids_from_request());
4619
+		$this->_redirect_after_action(false, '', '', [], true);
4620
+	}
4621
+
4622
+
4623
+	/**
4624
+	 *  This sends immediately any EEM_Message::status_idle or EEM_Message::status_resend messages in the queue
4625
+	 *
4626
+	 * @throws EE_Error
4627
+	 * @throws InvalidDataTypeException
4628
+	 * @throws InvalidInterfaceException
4629
+	 * @throws InvalidArgumentException
4630
+	 * @throws ReflectionException
4631
+	 * @since 4.9.0
4632
+	 */
4633
+	protected function _send_now()
4634
+	{
4635
+		EED_Messages::send_now($this->_get_msg_ids_from_request());
4636
+		$this->_redirect_after_action(false, '', '', [], true);
4637
+	}
4638
+
4639
+
4640
+	/**
4641
+	 * Deletes EE_messages for IDs in the request.
4642
+	 *
4643
+	 * @throws EE_Error
4644
+	 * @throws InvalidDataTypeException
4645
+	 * @throws InvalidInterfaceException
4646
+	 * @throws InvalidArgumentException
4647
+	 * @since 4.9.0
4648
+	 */
4649
+	protected function _delete_ee_messages()
4650
+	{
4651
+		$MSG_IDs       = $this->_get_msg_ids_from_request();
4652
+		$deleted_count = 0;
4653
+		foreach ($MSG_IDs as $MSG_ID) {
4654
+			if ($this->getMsgModel()->delete_by_ID($MSG_ID)) {
4655
+				$deleted_count++;
4656
+			}
4657
+		}
4658
+		if ($deleted_count) {
4659
+			EE_Error::add_success(
4660
+				esc_html(
4661
+					_n(
4662
+						'Message successfully deleted',
4663
+						'Messages successfully deleted',
4664
+						$deleted_count,
4665
+						'event_espresso'
4666
+					)
4667
+				)
4668
+			);
4669
+		} else {
4670
+			EE_Error::add_error(
4671
+				_n('The message was not deleted.', 'The messages were not deleted', count($MSG_IDs), 'event_espresso'),
4672
+				__FILE__,
4673
+				__FUNCTION__,
4674
+				__LINE__
4675
+			);
4676
+		}
4677
+		$this->_redirect_after_action(false, '', '', [], true);
4678
+	}
4679
+
4680
+
4681
+	/**
4682
+	 *  This looks for 'MSG_ID' key in the request and returns an array of MSG_ID's if present.
4683
+	 *
4684
+	 * @return array
4685
+	 * @since 4.9.0
4686
+	 */
4687
+	protected function _get_msg_ids_from_request()
4688
+	{
4689
+		$MSG_IDs = $this->request->getRequestParam('MSG_ID', [], 'string', true);
4690
+		if (empty($MSG_IDs)) {
4691
+			return [];
4692
+		}
4693
+		// if 'MSG_ID' was just a single ID (not an array)
4694
+		// then $MSG_IDs will be something like [123] so $MSG_IDs[0] should be 123
4695
+		// otherwise, $MSG_IDs was already an array where message IDs were used as the keys
4696
+		return count($MSG_IDs) === 1 && isset($MSG_IDs[0])
4697
+			? $MSG_IDs
4698
+			: array_keys($MSG_IDs);
4699
+	}
4700 4700
 }
Please login to merge, or discard this patch.
Spacing   +221 added lines, -221 removed lines patch added patch discarded remove patch
@@ -115,7 +115,7 @@  discard block
 block discarded – undo
115 115
      */
116 116
     public function getMsgModel()
117 117
     {
118
-        if (! $this->MSG_MODEL instanceof EEM_Message) {
118
+        if ( ! $this->MSG_MODEL instanceof EEM_Message) {
119 119
             $this->MSG_MODEL = EEM_Message::instance();
120 120
         }
121 121
         return $this->MSG_MODEL;
@@ -128,7 +128,7 @@  discard block
 block discarded – undo
128 128
      */
129 129
     public function getMtpModel()
130 130
     {
131
-        if (! $this->MTP_MODEL instanceof EEM_Message_Template) {
131
+        if ( ! $this->MTP_MODEL instanceof EEM_Message_Template) {
132 132
             $this->MTP_MODEL = EEM_Message_Template::instance();
133 133
         }
134 134
         return $this->MTP_MODEL;
@@ -141,7 +141,7 @@  discard block
 block discarded – undo
141 141
      */
142 142
     public function getMtgModel()
143 143
     {
144
-        if (! $this->MTG_MODEL instanceof EEM_Message_Template_Group) {
144
+        if ( ! $this->MTG_MODEL instanceof EEM_Message_Template_Group) {
145 145
             $this->MTG_MODEL = EEM_Message_Template_Group::instance();
146 146
         }
147 147
         return $this->MTG_MODEL;
@@ -211,8 +211,8 @@  discard block
 block discarded – undo
211 211
         $i = 1;
212 212
         foreach ($active_messengers as $active_messenger) {
213 213
             if ($active_messenger instanceof EE_Message) {
214
-                $m_values[ $i ]['id']   = $active_messenger->messenger();
215
-                $m_values[ $i ]['text'] = ucwords($active_messenger->messenger_label());
214
+                $m_values[$i]['id']   = $active_messenger->messenger();
215
+                $m_values[$i]['text'] = ucwords($active_messenger->messenger_label());
216 216
                 $i++;
217 217
             }
218 218
         }
@@ -248,8 +248,8 @@  discard block
 block discarded – undo
248 248
         $i               = 1;
249 249
         foreach ($active_messages as $active_message) {
250 250
             if ($active_message instanceof EE_Message) {
251
-                $mt_values[ $i ]['id']   = $active_message->message_type();
252
-                $mt_values[ $i ]['text'] = ucwords($active_message->message_type_label());
251
+                $mt_values[$i]['id']   = $active_message->message_type();
252
+                $mt_values[$i]['text'] = ucwords($active_message->message_type_label());
253 253
                 $i++;
254 254
             }
255 255
         }
@@ -288,7 +288,7 @@  discard block
 block discarded – undo
288 288
                 if ($message_type instanceof EE_message_type) {
289 289
                     $message_type_contexts = $message_type->get_contexts();
290 290
                     foreach ($message_type_contexts as $context => $context_details) {
291
-                        $contexts[ $context ] = $context_details['label'];
291
+                        $contexts[$context] = $context_details['label'];
292 292
                     }
293 293
                 }
294 294
             }
@@ -321,7 +321,7 @@  discard block
 block discarded – undo
321 321
             ['none_selected' => esc_html__('Show All Messengers', 'event_espresso')],
322 322
             $messenger_options
323 323
         );
324
-        $input             = new EE_Select_Input(
324
+        $input = new EE_Select_Input(
325 325
             $messenger_options,
326 326
             [
327 327
                 'html_name'  => 'ee_messenger_filter_by',
@@ -358,7 +358,7 @@  discard block
 block discarded – undo
358 358
             ['none_selected' => esc_html__('Show All Message Types', 'event_espresso')],
359 359
             $message_type_options
360 360
         );
361
-        $input                = new EE_Select_Input(
361
+        $input = new EE_Select_Input(
362 362
             $message_type_options,
363 363
             [
364 364
                 'html_name'  => 'ee_message_type_filter_by',
@@ -395,7 +395,7 @@  discard block
 block discarded – undo
395 395
             ['none_selected' => esc_html__('Show all Contexts', 'event_espresso')],
396 396
             $context_options
397 397
         );
398
-        $input           = new EE_Select_Input(
398
+        $input = new EE_Select_Input(
399 399
             $context_options,
400 400
             [
401 401
                 'html_name'  => 'ee_context_filter_by',
@@ -776,53 +776,53 @@  discard block
 block discarded – undo
776 776
 
777 777
     public function messages_help_tab()
778 778
     {
779
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_help_tab.template.php');
779
+        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH.'ee_msg_messages_help_tab.template.php');
780 780
     }
781 781
 
782 782
 
783 783
     public function messengers_help_tab()
784 784
     {
785
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messenger_help_tab.template.php');
785
+        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH.'ee_msg_messenger_help_tab.template.php');
786 786
     }
787 787
 
788 788
 
789 789
     public function message_types_help_tab()
790 790
     {
791
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_message_type_help_tab.template.php');
791
+        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH.'ee_msg_message_type_help_tab.template.php');
792 792
     }
793 793
 
794 794
 
795 795
     public function messages_overview_help_tab()
796 796
     {
797
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_overview_help_tab.template.php');
797
+        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH.'ee_msg_overview_help_tab.template.php');
798 798
     }
799 799
 
800 800
 
801 801
     public function message_templates_help_tab()
802 802
     {
803
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_message_templates_help_tab.template.php');
803
+        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH.'ee_msg_message_templates_help_tab.template.php');
804 804
     }
805 805
 
806 806
 
807 807
     public function edit_message_template_help_tab()
808 808
     {
809
-        $args['img1'] = '<img src="' . EE_MSG_ASSETS_URL . 'images/editor.png' . '" alt="'
809
+        $args['img1'] = '<img src="'.EE_MSG_ASSETS_URL.'images/editor.png'.'" alt="'
810 810
                         . esc_attr__('Editor Title', 'event_espresso')
811 811
                         . '" />';
812
-        $args['img2'] = '<img src="' . EE_MSG_ASSETS_URL . 'images/switch-context.png' . '" alt="'
812
+        $args['img2'] = '<img src="'.EE_MSG_ASSETS_URL.'images/switch-context.png'.'" alt="'
813 813
                         . esc_attr__('Context Switcher and Preview', 'event_espresso')
814 814
                         . '" />';
815
-        $args['img3'] = '<img class="left" src="' . EE_MSG_ASSETS_URL . 'images/form-fields.png' . '" alt="'
815
+        $args['img3'] = '<img class="left" src="'.EE_MSG_ASSETS_URL.'images/form-fields.png'.'" alt="'
816 816
                         . esc_attr__('Message Template Form Fields', 'event_espresso')
817 817
                         . '" />';
818
-        $args['img4'] = '<img class="right" src="' . EE_MSG_ASSETS_URL . 'images/shortcodes-metabox.png' . '" alt="'
818
+        $args['img4'] = '<img class="right" src="'.EE_MSG_ASSETS_URL.'images/shortcodes-metabox.png'.'" alt="'
819 819
                         . esc_attr__('Shortcodes Metabox', 'event_espresso')
820 820
                         . '" />';
821
-        $args['img5'] = '<img class="right" src="' . EE_MSG_ASSETS_URL . 'images/publish-meta-box.png' . '" alt="'
821
+        $args['img5'] = '<img class="right" src="'.EE_MSG_ASSETS_URL.'images/publish-meta-box.png'.'" alt="'
822 822
                         . esc_attr__('Publish Metabox', 'event_espresso')
823 823
                         . '" />';
824 824
         EEH_Template::display_template(
825
-            EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_templates_editor_help_tab.template.php',
825
+            EE_MSG_TEMPLATE_PATH.'ee_msg_messages_templates_editor_help_tab.template.php',
826 826
             $args
827 827
         );
828 828
     }
@@ -837,7 +837,7 @@  discard block
 block discarded – undo
837 837
         $this->_set_shortcodes();
838 838
         $args['shortcodes'] = $this->_shortcodes;
839 839
         EEH_Template::display_template(
840
-            EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_shortcodes_help_tab.template.php',
840
+            EE_MSG_TEMPLATE_PATH.'ee_msg_messages_shortcodes_help_tab.template.php',
841 841
             $args
842 842
         );
843 843
     }
@@ -845,16 +845,16 @@  discard block
 block discarded – undo
845 845
 
846 846
     public function preview_message_help_tab()
847 847
     {
848
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_preview_help_tab.template.php');
848
+        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH.'ee_msg_preview_help_tab.template.php');
849 849
     }
850 850
 
851 851
 
852 852
     public function settings_help_tab()
853 853
     {
854
-        $args['img1'] = '<img class="inline-text" src="' . EE_MSG_ASSETS_URL . 'images/email-tab-active.png'
855
-                        . '" alt="' . esc_attr__('Active Email Tab', 'event_espresso') . '" />';
856
-        $args['img2'] = '<img class="inline-text" src="' . EE_MSG_ASSETS_URL . 'images/email-tab-inactive.png'
857
-                        . '" alt="' . esc_attr__('Inactive Email Tab', 'event_espresso') . '" />';
854
+        $args['img1'] = '<img class="inline-text" src="'.EE_MSG_ASSETS_URL.'images/email-tab-active.png'
855
+                        . '" alt="'.esc_attr__('Active Email Tab', 'event_espresso').'" />';
856
+        $args['img2'] = '<img class="inline-text" src="'.EE_MSG_ASSETS_URL.'images/email-tab-inactive.png'
857
+                        . '" alt="'.esc_attr__('Inactive Email Tab', 'event_espresso').'" />';
858 858
         $args['img3'] = '<div class="ee-switch">'
859 859
                         . '<input class="ee-switch__input" id="ee-on-off-toggle-on" type="checkbox" checked>'
860 860
                         . '<label class="ee-switch__toggle" for="ee-on-off-toggle-on"></label>'
@@ -863,25 +863,25 @@  discard block
 block discarded – undo
863 863
                         . '<input class="ee-switch__input" id="ee-on-off-toggle-off" type="checkbox">'
864 864
                         . '<label class="ee-switch__toggle" for="ee-on-off-toggle-off"></label>'
865 865
                         . '</div>';
866
-        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH . 'ee_msg_messages_settings_help_tab.template.php', $args);
866
+        EEH_Template::display_template(EE_MSG_TEMPLATE_PATH.'ee_msg_messages_settings_help_tab.template.php', $args);
867 867
     }
868 868
 
869 869
 
870 870
     public function load_scripts_styles()
871 871
     {
872
-        wp_register_style('espresso_ee_msg', EE_MSG_ASSETS_URL . 'ee_message_admin.css', EVENT_ESPRESSO_VERSION);
872
+        wp_register_style('espresso_ee_msg', EE_MSG_ASSETS_URL.'ee_message_admin.css', EVENT_ESPRESSO_VERSION);
873 873
         wp_enqueue_style('espresso_ee_msg');
874 874
 
875 875
         wp_register_script(
876 876
             'ee-messages-settings',
877
-            EE_MSG_ASSETS_URL . 'ee-messages-settings.js',
877
+            EE_MSG_ASSETS_URL.'ee-messages-settings.js',
878 878
             ['jquery-ui-droppable', 'ee-serialize-full-array'],
879 879
             EVENT_ESPRESSO_VERSION,
880 880
             true
881 881
         );
882 882
         wp_register_script(
883 883
             'ee-msg-list-table-js',
884
-            EE_MSG_ASSETS_URL . 'ee_message_admin_list_table.js',
884
+            EE_MSG_ASSETS_URL.'ee_message_admin_list_table.js',
885 885
             ['ee-dialog'],
886 886
             EVENT_ESPRESSO_VERSION
887 887
         );
@@ -924,7 +924,7 @@  discard block
 block discarded – undo
924 924
 
925 925
         $this->_set_shortcodes();
926 926
 
927
-        EE_Registry::$i18n_js_strings['confirm_default_reset']        = sprintf(
927
+        EE_Registry::$i18n_js_strings['confirm_default_reset'] = sprintf(
928 928
             esc_html__(
929 929
                 'Are you sure you want to reset the %s %s message templates?  Remember continuing will reset the templates for all contexts in this messenger and message type group.',
930 930
                 'event_espresso'
@@ -936,14 +936,14 @@  discard block
 block discarded – undo
936 936
             'Switching the template pack for a messages template will reset the content for the template so the new layout is loaded.  Any custom content in the existing template will be lost. Are you sure you wish to do this?',
937 937
             'event_espresso'
938 938
         );
939
-        EE_Registry::$i18n_js_strings['server_error']                 = esc_html__(
939
+        EE_Registry::$i18n_js_strings['server_error'] = esc_html__(
940 940
             'An unknown error occurred on the server while attempting to process your request. Please refresh the page and try again or contact support.',
941 941
             'event_espresso'
942 942
         );
943 943
 
944 944
         wp_register_script(
945 945
             'ee_msgs_edit_js',
946
-            EE_MSG_ASSETS_URL . 'ee_message_editor.js',
946
+            EE_MSG_ASSETS_URL.'ee_message_editor.js',
947 947
             ['jquery'],
948 948
             EVENT_ESPRESSO_VERSION
949 949
         );
@@ -986,7 +986,7 @@  discard block
 block discarded – undo
986 986
     {
987 987
         wp_register_style(
988 988
             'ee-message-settings',
989
-            EE_MSG_ASSETS_URL . 'ee_message_settings.css',
989
+            EE_MSG_ASSETS_URL.'ee_message_settings.css',
990 990
             [],
991 991
             EVENT_ESPRESSO_VERSION
992 992
         );
@@ -1072,7 +1072,7 @@  discard block
 block discarded – undo
1072 1072
             }
1073 1073
             $status_bulk_actions = $common_bulk_actions;
1074 1074
             // unset bulk actions not applying to status
1075
-            if (! empty($status_bulk_actions)) {
1075
+            if ( ! empty($status_bulk_actions)) {
1076 1076
                 switch ($status) {
1077 1077
                     case EEM_Message::status_idle:
1078 1078
                     case EEM_Message::status_resend:
@@ -1101,7 +1101,7 @@  discard block
 block discarded – undo
1101 1101
                 continue;
1102 1102
             }
1103 1103
 
1104
-            $this->_views[ strtolower($status) ] = [
1104
+            $this->_views[strtolower($status)] = [
1105 1105
                 'slug'        => strtolower($status),
1106 1106
                 'label'       => EEH_Template::pretty_status($status, false, 'sentence'),
1107 1107
                 'count'       => 0,
@@ -1149,7 +1149,7 @@  discard block
 block discarded – undo
1149 1149
             if ($action_item === 'see_notifications_for') {
1150 1150
                 continue;
1151 1151
             }
1152
-            $action_items[ $action_item ] = [
1152
+            $action_items[$action_item] = [
1153 1153
                 'class' => $action_details['css_class'],
1154 1154
                 'desc'  => $action_details['label'],
1155 1155
             ];
@@ -1158,37 +1158,37 @@  discard block
 block discarded – undo
1158 1158
         /** @var array $status_items status legend setup */
1159 1159
         $status_items = [
1160 1160
             'sent_status'                => [
1161
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_sent,
1161
+                'class' => 'ee-status-legend ee-status-bg--'.EEM_Message::status_sent,
1162 1162
                 'desc'  => EEH_Template::pretty_status(EEM_Message::status_sent, false, 'sentence'),
1163 1163
             ],
1164 1164
             'idle_status'                => [
1165
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_idle,
1165
+                'class' => 'ee-status-legend ee-status-bg--'.EEM_Message::status_idle,
1166 1166
                 'desc'  => EEH_Template::pretty_status(EEM_Message::status_idle, false, 'sentence'),
1167 1167
             ],
1168 1168
             'failed_status'              => [
1169
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_failed,
1169
+                'class' => 'ee-status-legend ee-status-bg--'.EEM_Message::status_failed,
1170 1170
                 'desc'  => EEH_Template::pretty_status(EEM_Message::status_failed, false, 'sentence'),
1171 1171
             ],
1172 1172
             'messenger_executing_status' => [
1173
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_messenger_executing,
1173
+                'class' => 'ee-status-legend ee-status-bg--'.EEM_Message::status_messenger_executing,
1174 1174
                 'desc'  => EEH_Template::pretty_status(EEM_Message::status_messenger_executing, false, 'sentence'),
1175 1175
             ],
1176 1176
             'resend_status'              => [
1177
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_resend,
1177
+                'class' => 'ee-status-legend ee-status-bg--'.EEM_Message::status_resend,
1178 1178
                 'desc'  => EEH_Template::pretty_status(EEM_Message::status_resend, false, 'sentence'),
1179 1179
             ],
1180 1180
             'incomplete_status'          => [
1181
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_incomplete,
1181
+                'class' => 'ee-status-legend ee-status-bg--'.EEM_Message::status_incomplete,
1182 1182
                 'desc'  => EEH_Template::pretty_status(EEM_Message::status_incomplete, false, 'sentence'),
1183 1183
             ],
1184 1184
             'retry_status'               => [
1185
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_retry,
1185
+                'class' => 'ee-status-legend ee-status-bg--'.EEM_Message::status_retry,
1186 1186
                 'desc'  => EEH_Template::pretty_status(EEM_Message::status_retry, false, 'sentence'),
1187 1187
             ],
1188 1188
         ];
1189 1189
         if (EEM_Message::debug()) {
1190 1190
             $status_items['debug_only_status'] = [
1191
-                'class' => 'ee-status-legend ee-status-bg--' . EEM_Message::status_debug_only,
1191
+                'class' => 'ee-status-legend ee-status-bg--'.EEM_Message::status_debug_only,
1192 1192
                 'desc'  => EEH_Template::pretty_status(EEM_Message::status_debug_only, false, 'sentence'),
1193 1193
             ];
1194 1194
         }
@@ -1203,11 +1203,11 @@  discard block
 block discarded – undo
1203 1203
     protected function _custom_mtps_preview()
1204 1204
     {
1205 1205
         $this->_admin_page_title              = esc_html__('Custom Message Templates (Preview)', 'event_espresso');
1206
-        $this->_template_args['preview_img']  = '<img src="' . EE_MSG_ASSETS_URL . 'images/custom_mtps_preview.png"'
1207
-                                                . ' alt="' . esc_attr__(
1206
+        $this->_template_args['preview_img']  = '<img src="'.EE_MSG_ASSETS_URL.'images/custom_mtps_preview.png"'
1207
+                                                . ' alt="'.esc_attr__(
1208 1208
                                                     'Preview Custom Message Templates screenshot',
1209 1209
                                                     'event_espresso'
1210
-                                                ) . '" />';
1210
+                                                ).'" />';
1211 1211
         $this->_template_args['preview_text'] = '<strong>'
1212 1212
                                                 . esc_html__(
1213 1213
                                                     'Custom Message Templates is a feature that is only available in the premium version of Event Espresso 4 which is available with a support license purchase on EventEspresso.com. With the Custom Message Templates feature, you are able to create custom message templates and assign them on a per-event basis.',
@@ -1283,7 +1283,7 @@  discard block
 block discarded – undo
1283 1283
         $installed               = [];
1284 1284
 
1285 1285
         foreach ($installed_message_types as $message_type) {
1286
-            $installed[ $message_type->name ] = $message_type;
1286
+            $installed[$message_type->name] = $message_type;
1287 1287
         }
1288 1288
 
1289 1289
         return $installed;
@@ -1409,7 +1409,7 @@  discard block
 block discarded – undo
1409 1409
         // we need to assemble the title from Various details
1410 1410
         $context_label = sprintf(
1411 1411
             esc_html__('(%s %s)', 'event_espresso'),
1412
-            $c_config[ $context ]['label'],
1412
+            $c_config[$context]['label'],
1413 1413
             ucwords($c_label['label'])
1414 1414
         );
1415 1415
 
@@ -1431,7 +1431,7 @@  discard block
 block discarded – undo
1431 1431
             $message_template_group->message_type()
1432 1432
         );
1433 1433
 
1434
-        if (! $template_field_structure) {
1434
+        if ( ! $template_field_structure) {
1435 1435
             $template_field_structure = false;
1436 1436
             $template_fields          = esc_html__(
1437 1437
                 'There was an error in assembling the fields for this display (you should see an error message)',
@@ -1445,21 +1445,21 @@  discard block
 block discarded – undo
1445 1445
 
1446 1446
         // if we have the extra key.. then we need to remove the content index from the template_field_structure as it
1447 1447
         // will get handled in the "extra" array.
1448
-        if (is_array($template_field_structure[ $context ]) && isset($template_field_structure[ $context ]['extra'])) {
1449
-            foreach ($template_field_structure[ $context ]['extra'] as $reference_field => $new_fields) {
1450
-                unset($template_field_structure[ $context ][ $reference_field ]);
1448
+        if (is_array($template_field_structure[$context]) && isset($template_field_structure[$context]['extra'])) {
1449
+            foreach ($template_field_structure[$context]['extra'] as $reference_field => $new_fields) {
1450
+                unset($template_field_structure[$context][$reference_field]);
1451 1451
             }
1452 1452
         }
1453 1453
 
1454 1454
         // let's loop through the template_field_structure and actually assemble the input fields!
1455
-        if (! empty($template_field_structure)) {
1456
-            foreach ($template_field_structure[ $context ] as $template_field => $field_setup_array) {
1455
+        if ( ! empty($template_field_structure)) {
1456
+            foreach ($template_field_structure[$context] as $template_field => $field_setup_array) {
1457 1457
                 // if this is an 'extra' template field then we need to remove any existing fields that are keyed up in
1458 1458
                 // the extra array and reset them.
1459 1459
                 if ($template_field === 'extra') {
1460 1460
                     $this->_template_args['is_extra_fields'] = true;
1461 1461
                     foreach ($field_setup_array as $reference_field => $new_fields_array) {
1462
-                        $message_template = $message_templates[ $context ][ $reference_field ];
1462
+                        $message_template = $message_templates[$context][$reference_field];
1463 1463
                         $content          = $message_template instanceof EE_Message_Template
1464 1464
                             ? $message_template->get('MTP_content')
1465 1465
                             : '';
@@ -1468,7 +1468,7 @@  discard block
 block discarded – undo
1468 1468
                             $continue = false;
1469 1469
                             if (isset($extra_array['shortcodes_required'])) {
1470 1470
                                 foreach ((array) $extra_array['shortcodes_required'] as $shortcode) {
1471
-                                    if (! array_key_exists($shortcode, $this->_shortcodes)) {
1471
+                                    if ( ! array_key_exists($shortcode, $this->_shortcodes)) {
1472 1472
                                         $continue = true;
1473 1473
                                     }
1474 1474
                                 }
@@ -1477,53 +1477,53 @@  discard block
 block discarded – undo
1477 1477
                                 }
1478 1478
                             }
1479 1479
 
1480
-                            $field_id = $reference_field . '-' . $extra_field . '-content';
1480
+                            $field_id = $reference_field.'-'.$extra_field.'-content';
1481 1481
 
1482
-                            $template_form_fields[ $field_id ]         = $extra_array;
1483
-                            $template_form_fields[ $field_id ]['name'] = 'MTP_template_fields['
1482
+                            $template_form_fields[$field_id]         = $extra_array;
1483
+                            $template_form_fields[$field_id]['name'] = 'MTP_template_fields['
1484 1484
                                                                          . $reference_field
1485 1485
                                                                          . '][content]['
1486
-                                                                         . $extra_field . ']';
1487
-                            $css_class                                 = isset($extra_array['css_class'])
1486
+                                                                         . $extra_field.']';
1487
+                            $css_class = isset($extra_array['css_class'])
1488 1488
                                 ? $extra_array['css_class']
1489 1489
                                 : '';
1490 1490
 
1491
-                            $template_form_fields[ $field_id ]['css_class'] = ! empty($v_fields)
1491
+                            $template_form_fields[$field_id]['css_class'] = ! empty($v_fields)
1492 1492
                                                                               && in_array($extra_field, $v_fields, true)
1493 1493
                                                                               && (
1494
-                                                                                  is_array($validators[ $extra_field ])
1495
-                                                                                  && isset($validators[ $extra_field ]['msg'])
1494
+                                                                                  is_array($validators[$extra_field])
1495
+                                                                                  && isset($validators[$extra_field]['msg'])
1496 1496
                                                                               )
1497
-                                ? 'validate-error ' . $css_class
1497
+                                ? 'validate-error '.$css_class
1498 1498
                                 : $css_class;
1499 1499
 
1500
-                            $template_form_fields[ $field_id ]['value'] = ! empty($message_templates)
1501
-                                                                          && isset($content[ $extra_field ])
1502
-                                ? $content[ $extra_field ]
1500
+                            $template_form_fields[$field_id]['value'] = ! empty($message_templates)
1501
+                                                                          && isset($content[$extra_field])
1502
+                                ? $content[$extra_field]
1503 1503
                                 : '';
1504 1504
 
1505 1505
                             // do we have a validation error?  if we do then let's use that value instead
1506
-                            $template_form_fields[ $field_id ]['value'] = isset($validators[ $extra_field ])
1507
-                                ? $validators[ $extra_field ]['value']
1508
-                                : $template_form_fields[ $field_id ]['value'];
1506
+                            $template_form_fields[$field_id]['value'] = isset($validators[$extra_field])
1507
+                                ? $validators[$extra_field]['value']
1508
+                                : $template_form_fields[$field_id]['value'];
1509 1509
 
1510 1510
 
1511
-                            $template_form_fields[ $field_id ]['db-col'] = 'MTP_content';
1511
+                            $template_form_fields[$field_id]['db-col'] = 'MTP_content';
1512 1512
 
1513 1513
                             // shortcode selector
1514 1514
                             $field_name_to_use                                   = $extra_field === 'main'
1515 1515
                                 ? 'content'
1516 1516
                                 : $extra_field;
1517
-                            $template_form_fields[ $field_id ]['append_content'] = $this->_get_shortcode_selector(
1517
+                            $template_form_fields[$field_id]['append_content'] = $this->_get_shortcode_selector(
1518 1518
                                 $field_name_to_use,
1519 1519
                                 $field_id
1520 1520
                             );
1521 1521
                         }
1522
-                        $template_field_MTP_id           = $reference_field . '-MTP_ID';
1523
-                        $template_field_template_name_id = $reference_field . '-name';
1522
+                        $template_field_MTP_id           = $reference_field.'-MTP_ID';
1523
+                        $template_field_template_name_id = $reference_field.'-name';
1524 1524
 
1525
-                        $template_form_fields[ $template_field_MTP_id ] = [
1526
-                            'name'       => 'MTP_template_fields[' . $reference_field . '][MTP_ID]',
1525
+                        $template_form_fields[$template_field_MTP_id] = [
1526
+                            'name'       => 'MTP_template_fields['.$reference_field.'][MTP_ID]',
1527 1527
                             'label'      => null,
1528 1528
                             'input'      => 'hidden',
1529 1529
                             'type'       => 'int',
@@ -1535,8 +1535,8 @@  discard block
 block discarded – undo
1535 1535
                             'db-col'     => 'MTP_ID',
1536 1536
                         ];
1537 1537
 
1538
-                        $template_form_fields[ $template_field_template_name_id ] = [
1539
-                            'name'       => 'MTP_template_fields[' . $reference_field . '][name]',
1538
+                        $template_form_fields[$template_field_template_name_id] = [
1539
+                            'name'       => 'MTP_template_fields['.$reference_field.'][name]',
1540 1540
                             'label'      => null,
1541 1541
                             'input'      => 'hidden',
1542 1542
                             'type'       => 'string',
@@ -1550,38 +1550,38 @@  discard block
 block discarded – undo
1550 1550
                     }
1551 1551
                     continue; // skip the next stuff, we got the necessary fields here for this dataset.
1552 1552
                 } else {
1553
-                    $field_id                                   = $template_field . '-content';
1554
-                    $template_form_fields[ $field_id ]          = $field_setup_array;
1555
-                    $template_form_fields[ $field_id ]['name']  =
1556
-                        'MTP_template_fields[' . $template_field . '][content]';
1553
+                    $field_id                                   = $template_field.'-content';
1554
+                    $template_form_fields[$field_id]          = $field_setup_array;
1555
+                    $template_form_fields[$field_id]['name']  =
1556
+                        'MTP_template_fields['.$template_field.'][content]';
1557 1557
                     $message_template                           =
1558
-                        isset($message_templates[ $context ][ $template_field ])
1559
-                            ? $message_templates[ $context ][ $template_field ]
1558
+                        isset($message_templates[$context][$template_field])
1559
+                            ? $message_templates[$context][$template_field]
1560 1560
                             : null;
1561
-                    $template_form_fields[ $field_id ]['value'] = ! empty($message_templates)
1562
-                                                                  && is_array($message_templates[ $context ])
1561
+                    $template_form_fields[$field_id]['value'] = ! empty($message_templates)
1562
+                                                                  && is_array($message_templates[$context])
1563 1563
                                                                   && $message_template instanceof EE_Message_Template
1564 1564
                         ? $message_template->get('MTP_content')
1565 1565
                         : '';
1566 1566
 
1567 1567
                     // do we have a validator error for this field?  if we do then we'll use that value instead
1568
-                    $template_form_fields[ $field_id ]['value'] = isset($validators[ $template_field ])
1569
-                        ? $validators[ $template_field ]['value']
1570
-                        : $template_form_fields[ $field_id ]['value'];
1568
+                    $template_form_fields[$field_id]['value'] = isset($validators[$template_field])
1569
+                        ? $validators[$template_field]['value']
1570
+                        : $template_form_fields[$field_id]['value'];
1571 1571
 
1572 1572
 
1573
-                    $template_form_fields[ $field_id ]['db-col']    = 'MTP_content';
1573
+                    $template_form_fields[$field_id]['db-col']    = 'MTP_content';
1574 1574
                     $css_class                                      = isset($field_setup_array['css_class'])
1575 1575
                         ? $field_setup_array['css_class']
1576 1576
                         : '';
1577
-                    $template_form_fields[ $field_id ]['css_class'] = ! empty($v_fields)
1577
+                    $template_form_fields[$field_id]['css_class'] = ! empty($v_fields)
1578 1578
                                                                       && in_array($template_field, $v_fields, true)
1579
-                                                                      && isset($validators[ $template_field ]['msg'])
1580
-                        ? 'validate-error ' . $css_class
1579
+                                                                      && isset($validators[$template_field]['msg'])
1580
+                        ? 'validate-error '.$css_class
1581 1581
                         : $css_class;
1582 1582
 
1583 1583
                     // shortcode selector
1584
-                    $template_form_fields[ $field_id ]['append_content'] = $this->_get_shortcode_selector(
1584
+                    $template_form_fields[$field_id]['append_content'] = $this->_get_shortcode_selector(
1585 1585
                         $template_field,
1586 1586
                         $field_id
1587 1587
                     );
@@ -1589,12 +1589,12 @@  discard block
 block discarded – undo
1589 1589
 
1590 1590
                 // k took care of content field(s) now let's take care of others.
1591 1591
 
1592
-                $template_field_MTP_id                 = $template_field . '-MTP_ID';
1593
-                $template_field_field_template_name_id = $template_field . '-name';
1592
+                $template_field_MTP_id                 = $template_field.'-MTP_ID';
1593
+                $template_field_field_template_name_id = $template_field.'-name';
1594 1594
 
1595 1595
                 // foreach template field there are actually two form fields created
1596
-                $template_form_fields[ $template_field_MTP_id ] = [
1597
-                    'name'       => 'MTP_template_fields[' . $template_field . '][MTP_ID]',
1596
+                $template_form_fields[$template_field_MTP_id] = [
1597
+                    'name'       => 'MTP_template_fields['.$template_field.'][MTP_ID]',
1598 1598
                     'label'      => null,
1599 1599
                     'input'      => 'hidden',
1600 1600
                     'type'       => 'int',
@@ -1606,8 +1606,8 @@  discard block
 block discarded – undo
1606 1606
                     'db-col'     => 'MTP_ID',
1607 1607
                 ];
1608 1608
 
1609
-                $template_form_fields[ $template_field_field_template_name_id ] = [
1610
-                    'name'       => 'MTP_template_fields[' . $template_field . '][name]',
1609
+                $template_form_fields[$template_field_field_template_name_id] = [
1610
+                    'name'       => 'MTP_template_fields['.$template_field.'][name]',
1611 1611
                     'label'      => null,
1612 1612
                     'input'      => 'hidden',
1613 1613
                     'type'       => 'string',
@@ -1724,7 +1724,7 @@  discard block
 block discarded – undo
1724 1724
                 'format'     => '%d',
1725 1725
                 'db-col'     => 'MTP_deleted',
1726 1726
             ];
1727
-            $sidebar_form_fields['ee-msg-author']  = [
1727
+            $sidebar_form_fields['ee-msg-author'] = [
1728 1728
                 'name'       => 'MTP_user_id',
1729 1729
                 'label'      => esc_html__('Author', 'event_espresso'),
1730 1730
                 'input'      => 'hidden',
@@ -1743,17 +1743,17 @@  discard block
 block discarded – undo
1743 1743
                 'value' => $action,
1744 1744
             ];
1745 1745
 
1746
-            $sidebar_form_fields['ee-msg-id']        = [
1746
+            $sidebar_form_fields['ee-msg-id'] = [
1747 1747
                 'name'  => 'id',
1748 1748
                 'input' => 'hidden',
1749 1749
                 'type'  => 'int',
1750 1750
                 'value' => $GRP_ID,
1751 1751
             ];
1752 1752
             $sidebar_form_fields['ee-msg-evt-nonce'] = [
1753
-                'name'  => $action . '_nonce',
1753
+                'name'  => $action.'_nonce',
1754 1754
                 'input' => 'hidden',
1755 1755
                 'type'  => 'string',
1756
-                'value' => wp_create_nonce($action . '_nonce'),
1756
+                'value' => wp_create_nonce($action.'_nonce'),
1757 1757
             ];
1758 1758
 
1759 1759
             $template_switch = $this->request->getRequestParam('template_switch');
@@ -1784,7 +1784,7 @@  discard block
 block discarded – undo
1784 1784
         );
1785 1785
 
1786 1786
         // add preview button
1787
-        $preview_url    = parent::add_query_args_and_nonce(
1787
+        $preview_url = parent::add_query_args_and_nonce(
1788 1788
             [
1789 1789
                 'message_type' => $message_template_group->message_type(),
1790 1790
                 'messenger'    => $message_template_group->messenger(),
@@ -1795,7 +1795,7 @@  discard block
 block discarded – undo
1795 1795
             ],
1796 1796
             $this->_admin_base_url
1797 1797
         );
1798
-        $preview_button = '<a href="' . $preview_url . '" class="button--secondary messages-preview-button">'
1798
+        $preview_button = '<a href="'.$preview_url.'" class="button--secondary messages-preview-button">'
1799 1799
                           . esc_html__('Preview', 'event_espresso')
1800 1800
                           . '</a>';
1801 1801
 
@@ -1833,11 +1833,11 @@  discard block
 block discarded – undo
1833 1833
         $this->_template_args['before_admin_page_content'] .= $this->add_context_switcher();
1834 1834
         $this->_template_args['before_admin_page_content'] .= '</div>';
1835 1835
         $this->_template_args['before_admin_page_content'] .= $this->_add_form_element_before();
1836
-        $this->_template_args['after_admin_page_content']  = $this->_add_form_element_after();
1836
+        $this->_template_args['after_admin_page_content'] = $this->_add_form_element_after();
1837 1837
 
1838 1838
         $this->_template_path = $this->_template_args['GRP_ID']
1839 1839
             ? EE_MSG_TEMPLATE_PATH . 'ee_msg_details_main_edit_meta_box.template.php'
1840
-            : EE_MSG_TEMPLATE_PATH . 'ee_msg_details_main_add_meta_box.template.php';
1840
+            : EE_MSG_TEMPLATE_PATH.'ee_msg_details_main_add_meta_box.template.php';
1841 1841
 
1842 1842
         // send along EE_Message_Template_Group object for further template use.
1843 1843
         $this->_template_args['MTP'] = $message_template_group;
@@ -1893,7 +1893,7 @@  discard block
 block discarded – undo
1893 1893
     ) {
1894 1894
         $template_args = [
1895 1895
             'context'                   => $context,
1896
-            'nonce'                     => wp_create_nonce('activate_' . $context . '_toggle_nonce'),
1896
+            'nonce'                     => wp_create_nonce('activate_'.$context.'_toggle_nonce'),
1897 1897
             'is_active'                 => $message_template_group->is_context_active($context),
1898 1898
             'on_off_action'             => $message_template_group->is_context_active($context)
1899 1899
                 ? 'context-off'
@@ -1902,7 +1902,7 @@  discard block
 block discarded – undo
1902 1902
             'message_template_group_id' => $message_template_group->ID(),
1903 1903
         ];
1904 1904
         return EEH_Template::display_template(
1905
-            EE_MSG_TEMPLATE_PATH . 'ee_msg_editor_active_context_element.template.php',
1905
+            EE_MSG_TEMPLATE_PATH.'ee_msg_editor_active_context_element.template.php',
1906 1906
             $template_args,
1907 1907
             true
1908 1908
         );
@@ -1959,7 +1959,7 @@  discard block
 block discarded – undo
1959 1959
         }
1960 1960
         $message_template_group_id = $this->request->getRequestParam('message_template_group_id', 0, 'int');
1961 1961
         $message_template_group    = $this->getMtgModel()->get_one_by_ID($message_template_group_id);
1962
-        if (! $message_template_group instanceof EE_Message_Template_Group) {
1962
+        if ( ! $message_template_group instanceof EE_Message_Template_Group) {
1963 1963
             EE_Error::add_error(
1964 1964
                 sprintf(
1965 1965
                     esc_html__(
@@ -2090,7 +2090,7 @@  discard block
 block discarded – undo
2090 2090
         $messenger    = $this->request->getRequestParam('msgr');
2091 2091
         $message_type = $this->request->getRequestParam('mt');
2092 2092
         // we need to make sure we've got the info we need.
2093
-        if (! ($GRP_ID && $messenger && $message_type)) {
2093
+        if ( ! ($GRP_ID && $messenger && $message_type)) {
2094 2094
             EE_Error::add_error(
2095 2095
                 esc_html__(
2096 2096
                     'In order to reset the template to its default we require the messenger, message type, and message template GRP_ID to know what is being reset.  At least one of these is missing.',
@@ -2127,7 +2127,7 @@  discard block
 block discarded – undo
2127 2127
         }
2128 2128
 
2129 2129
         // any error messages?
2130
-        if (! $success) {
2130
+        if ( ! $success) {
2131 2131
             EE_Error::add_error(
2132 2132
                 esc_html__(
2133 2133
                     'Something went wrong with deleting existing templates. Unable to reset to default',
@@ -2176,7 +2176,7 @@  discard block
 block discarded – undo
2176 2176
     {
2177 2177
         // first make sure we've got the necessary parameters
2178 2178
         $GRP_ID = $this->request->getRequestParam('GRP_ID', 0, 'int');
2179
-        if (! ($GRP_ID && $this->_active_messenger_name && $this->_active_message_type_name)) {
2179
+        if ( ! ($GRP_ID && $this->_active_messenger_name && $this->_active_message_type_name)) {
2180 2180
             EE_Error::add_error(
2181 2181
                 esc_html__('Missing necessary parameters for displaying preview', 'event_espresso'),
2182 2182
                 __FILE__,
@@ -2202,7 +2202,7 @@  discard block
 block discarded – undo
2202 2202
         $EVT_ID = $this->request->getRequestParam('evt_id', 0, 'int');
2203 2203
 
2204 2204
         // let's add a button to go back to the edit view
2205
-        $query_args             = [
2205
+        $query_args = [
2206 2206
             'id'      => $GRP_ID,
2207 2207
             'evt_id'  => $EVT_ID,
2208 2208
             'context' => $context,
@@ -2223,7 +2223,7 @@  discard block
 block discarded – undo
2223 2223
         $preview_title = sprintf(
2224 2224
             esc_html__('Viewing Preview for %s %s Message Template', 'event_espresso'),
2225 2225
             $active_messenger_label,
2226
-            ucwords($message_types[ $this->_active_message_type_name ]->label['singular'])
2226
+            ucwords($message_types[$this->_active_message_type_name]->label['singular'])
2227 2227
         );
2228 2228
         if (empty($preview)) {
2229 2229
             $this->noEventsErrorMessage();
@@ -2231,7 +2231,7 @@  discard block
 block discarded – undo
2231 2231
         // setup display of preview.
2232 2232
         $this->_admin_page_title                    = $preview_title;
2233 2233
         $this->_template_args['admin_page_title']   = $preview_title;
2234
-        $this->_template_args['admin_page_content'] = $preview_button . '<br />' . $preview;
2234
+        $this->_template_args['admin_page_content'] = $preview_button.'<br />'.$preview;
2235 2235
         $this->_template_args['data']['force_json'] = true;
2236 2236
 
2237 2237
         return '';
@@ -2252,7 +2252,7 @@  discard block
 block discarded – undo
2252 2252
             ],
2253 2253
             admin_url('admin.php')
2254 2254
         );
2255
-        $message    = $test_send
2255
+        $message = $test_send
2256 2256
             ? esc_html__(
2257 2257
                 'A test message could not be sent for this message template because there are no events created yet. The preview system uses actual events for generating the test message. %1$sGo see your events%2$s!',
2258 2258
                 'event_espresso'
@@ -2345,10 +2345,10 @@  discard block
 block discarded – undo
2345 2345
             // only include template packs that support this messenger and message type!
2346 2346
             $supports = $tp->get_supports();
2347 2347
             if (
2348
-                ! isset($supports[ $this->_message_template_group->messenger() ])
2348
+                ! isset($supports[$this->_message_template_group->messenger()])
2349 2349
                 || ! in_array(
2350 2350
                     $this->_message_template_group->message_type(),
2351
-                    $supports[ $this->_message_template_group->messenger() ],
2351
+                    $supports[$this->_message_template_group->messenger()],
2352 2352
                     true
2353 2353
                 )
2354 2354
             ) {
@@ -2372,7 +2372,7 @@  discard block
 block discarded – undo
2372 2372
         }
2373 2373
 
2374 2374
         // setup variation select values for the currently selected template.
2375
-        $variations               = $this->_message_template_group->get_template_pack()->get_variations(
2375
+        $variations = $this->_message_template_group->get_template_pack()->get_variations(
2376 2376
             $this->_message_template_group->messenger(),
2377 2377
             $this->_message_template_group->message_type()
2378 2378
         );
@@ -2386,12 +2386,12 @@  discard block
 block discarded – undo
2386 2386
 
2387 2387
         $template_pack_labels = $this->_message_template_group->messenger_obj()->get_supports_labels();
2388 2388
 
2389
-        $template_args['template_packs_selector']        = EEH_Form_Fields::select_input(
2389
+        $template_args['template_packs_selector'] = EEH_Form_Fields::select_input(
2390 2390
             'MTP_template_pack',
2391 2391
             $tp_select_values,
2392 2392
             $this->_message_template_group->get_template_pack_name()
2393 2393
         );
2394
-        $template_args['variations_selector']            = EEH_Form_Fields::select_input(
2394
+        $template_args['variations_selector'] = EEH_Form_Fields::select_input(
2395 2395
             'MTP_template_variation',
2396 2396
             $variations_select_values,
2397 2397
             $this->_message_template_group->get_template_pack_variation()
@@ -2401,7 +2401,7 @@  discard block
 block discarded – undo
2401 2401
         $template_args['template_pack_description']      = $template_pack_labels->template_pack_description;
2402 2402
         $template_args['template_variation_description'] = $template_pack_labels->template_variation_description;
2403 2403
 
2404
-        $template = EE_MSG_TEMPLATE_PATH . 'template_pack_and_variations_metabox.template.php';
2404
+        $template = EE_MSG_TEMPLATE_PATH.'template_pack_and_variations_metabox.template.php';
2405 2405
 
2406 2406
         EEH_Template::display_template($template, $template_args);
2407 2407
     }
@@ -2427,33 +2427,33 @@  discard block
 block discarded – undo
2427 2427
         // first we need to see if there are any fields
2428 2428
         $fields = $this->_message_template_group->messenger_obj()->get_test_settings_fields();
2429 2429
 
2430
-        if (! empty($fields)) {
2430
+        if ( ! empty($fields)) {
2431 2431
             // yup there be fields
2432 2432
             foreach ($fields as $field => $config) {
2433
-                $field_id = $this->_message_template_group->messenger() . '_' . $field;
2433
+                $field_id = $this->_message_template_group->messenger().'_'.$field;
2434 2434
                 $existing = $this->_message_template_group->messenger_obj()->get_existing_test_settings();
2435 2435
                 $default  = isset($config['default']) ? $config['default'] : '';
2436 2436
                 $default  = isset($config['value']) ? $config['value'] : $default;
2437 2437
 
2438 2438
                 // if type is hidden and the value is empty
2439 2439
                 // something may have gone wrong so let's correct with the defaults
2440
-                $fix                = $config['input'] === 'hidden'
2441
-                                      && isset($existing[ $field ])
2442
-                                      && empty($existing[ $field ])
2440
+                $fix = $config['input'] === 'hidden'
2441
+                                      && isset($existing[$field])
2442
+                                      && empty($existing[$field])
2443 2443
                     ? $default
2444 2444
                     : '';
2445
-                $existing[ $field ] = isset($existing[ $field ]) && empty($fix)
2446
-                    ? $existing[ $field ]
2445
+                $existing[$field] = isset($existing[$field]) && empty($fix)
2446
+                    ? $existing[$field]
2447 2447
                     : $fix;
2448 2448
 
2449
-                $template_form_fields[ $field_id ] = [
2450
-                    'name'       => 'test_settings_fld[' . $field . ']',
2449
+                $template_form_fields[$field_id] = [
2450
+                    'name'       => 'test_settings_fld['.$field.']',
2451 2451
                     'label'      => $config['label'],
2452 2452
                     'input'      => $config['input'],
2453 2453
                     'type'       => $config['type'],
2454 2454
                     'required'   => $config['required'],
2455 2455
                     'validation' => $config['validation'],
2456
-                    'value'      => isset($existing[ $field ]) ? $existing[ $field ] : $default,
2456
+                    'value'      => isset($existing[$field]) ? $existing[$field] : $default,
2457 2457
                     'css_class'  => $config['css_class'],
2458 2458
                     'options'    => isset($config['options']) ? $config['options'] : [],
2459 2459
                     'default'    => $default,
@@ -2467,7 +2467,7 @@  discard block
 block discarded – undo
2467 2467
             : '';
2468 2468
 
2469 2469
         // print out $test_settings_fields
2470
-        if (! empty($test_settings_html)) {
2470
+        if ( ! empty($test_settings_html)) {
2471 2471
             $test_settings_html .= '<input type="submit" class="button--primary mtp-test-button alignright" ';
2472 2472
             $test_settings_html .= 'name="test_button" value="';
2473 2473
             $test_settings_html .= esc_html__('Test Send', 'event_espresso');
@@ -2513,7 +2513,7 @@  discard block
 block discarded – undo
2513 2513
         ];
2514 2514
 
2515 2515
         return EEH_Template::display_template(
2516
-            EE_MSG_TEMPLATE_PATH . 'shortcode_selector_skeleton.template.php',
2516
+            EE_MSG_TEMPLATE_PATH.'shortcode_selector_skeleton.template.php',
2517 2517
             $template_args,
2518 2518
             true
2519 2519
         );
@@ -2539,7 +2539,7 @@  discard block
 block discarded – undo
2539 2539
         // $messenger = $this->_message_template_group->messenger_obj();
2540 2540
         // now let's set the content depending on the status of the shortcodes array
2541 2541
         if (empty($shortcodes)) {
2542
-            echo '<p>' . esc_html__('There are no valid shortcodes available', 'event_espresso') . '</p>';
2542
+            echo '<p>'.esc_html__('There are no valid shortcodes available', 'event_espresso').'</p>';
2543 2543
             return;
2544 2544
         }
2545 2545
         ?>
@@ -2575,7 +2575,7 @@  discard block
 block discarded – undo
2575 2575
     {
2576 2576
 
2577 2577
         // no need to run this if the property is already set
2578
-        if (! empty($this->_shortcodes)) {
2578
+        if ( ! empty($this->_shortcodes)) {
2579 2579
             return;
2580 2580
         }
2581 2581
 
@@ -2630,7 +2630,7 @@  discard block
 block discarded – undo
2630 2630
     protected function _set_message_template_group()
2631 2631
     {
2632 2632
         // get out if this is already set.
2633
-        if (! empty($this->_message_template_group)) {
2633
+        if ( ! empty($this->_message_template_group)) {
2634 2634
             return;
2635 2635
         }
2636 2636
 
@@ -2676,8 +2676,8 @@  discard block
 block discarded – undo
2676 2676
                     <?php
2677 2677
                 }
2678 2678
                 // setup nonce_url
2679
-                wp_nonce_field($args['action'] . '_nonce', $args['action'] . '_nonce', false);
2680
-                $id = 'ee-' . sanitize_key($context_label['label']) . '-select';
2679
+                wp_nonce_field($args['action'].'_nonce', $args['action'].'_nonce', false);
2680
+                $id = 'ee-'.sanitize_key($context_label['label']).'-select';
2681 2681
                 ?>
2682 2682
                 <label for='<?php echo esc_attr($id); ?>' class='screen-reader-text'>
2683 2683
                     <?php esc_html_e('message context options', 'event_espresso'); ?>
@@ -2690,7 +2690,7 @@  discard block
 block discarded – undo
2690 2690
                             $checked = ($context === $args['context']) ? 'selected' : '';
2691 2691
                             ?>
2692 2692
                             <option value="<?php echo esc_attr($context); ?>" <?php echo esc_attr($checked); ?>>
2693
-                                <?php echo esc_html($context_details[ $context ]['label']); ?>
2693
+                                <?php echo esc_html($context_details[$context]['label']); ?>
2694 2694
                             </option>
2695 2695
                         <?php endforeach;
2696 2696
                     endif; ?>
@@ -3081,7 +3081,7 @@  discard block
 block discarded – undo
3081 3081
     {
3082 3082
         if (is_array($content)) {
3083 3083
             foreach ($content as $key => $value) {
3084
-                $content[ $key ] = $this->sanitizeMessageTemplateContent($value);
3084
+                $content[$key] = $this->sanitizeMessageTemplateContent($value);
3085 3085
             }
3086 3086
             return $content;
3087 3087
         }
@@ -3119,7 +3119,7 @@  discard block
 block discarded – undo
3119 3119
 
3120 3120
         $context   = ucwords(str_replace('_', ' ', $context));
3121 3121
         $item_desc = $messenger_label && $message_type_label
3122
-            ? $messenger_label . ' ' . $message_type_label . ' ' . $context . ' '
3122
+            ? $messenger_label.' '.$message_type_label.' '.$context.' '
3123 3123
             : '';
3124 3124
         $item_desc .= 'Message Template';
3125 3125
         return $item_desc;
@@ -3271,7 +3271,7 @@  discard block
 block discarded – undo
3271 3271
         if ($all) {
3272 3272
             // Checkboxes
3273 3273
             $checkboxes = $this->request->getRequestParam('checkbox', [], 'int', true);
3274
-            if (! empty($checkboxes)) {
3274
+            if ( ! empty($checkboxes)) {
3275 3275
                 // if array has more than one element then success message should be plural.
3276 3276
                 // todo: what about nonce?
3277 3277
                 $success = count($checkboxes) > 1 ? 2 : 1;
@@ -3281,18 +3281,18 @@  discard block
 block discarded – undo
3281 3281
                     $trashed_or_restored = $trash
3282 3282
                         ? $this->getMtgModel()->delete_by_ID($GRP_ID)
3283 3283
                         : $this->getMtgModel()->restore_by_ID($GRP_ID);
3284
-                    if (! $trashed_or_restored) {
3284
+                    if ( ! $trashed_or_restored) {
3285 3285
                         $success = 0;
3286 3286
                     }
3287 3287
                 }
3288 3288
             } else {
3289 3289
                 // grab single GRP_ID and handle
3290 3290
                 $GRP_ID = $this->request->getRequestParam('id', 0, 'int');
3291
-                if (! empty($GRP_ID)) {
3291
+                if ( ! empty($GRP_ID)) {
3292 3292
                     $trashed_or_restored = $trash
3293 3293
                         ? $this->getMtgModel()->delete_by_ID($GRP_ID)
3294 3294
                         : $this->getMtgModel()->restore_by_ID($GRP_ID);
3295
-                    if (! $trashed_or_restored) {
3295
+                    if ( ! $trashed_or_restored) {
3296 3296
                         $success = 0;
3297 3297
                     }
3298 3298
                 } else {
@@ -3340,7 +3340,7 @@  discard block
 block discarded – undo
3340 3340
 
3341 3341
         // checkboxes
3342 3342
         $checkboxes = $this->request->getRequestParam('checkbox', [], 'int', true);
3343
-        if (! empty($checkboxes)) {
3343
+        if ( ! empty($checkboxes)) {
3344 3344
             // if array has more than one element then success message should be plural
3345 3345
             $success = count($checkboxes) > 1 ? 2 : 1;
3346 3346
 
@@ -3444,7 +3444,7 @@  discard block
 block discarded – undo
3444 3444
     protected function _set_m_mt_settings()
3445 3445
     {
3446 3446
         // first if this is already set then lets get out no need to regenerate data.
3447
-        if (! empty($this->_m_mt_settings)) {
3447
+        if ( ! empty($this->_m_mt_settings)) {
3448 3448
             return;
3449 3449
         }
3450 3450
 
@@ -3457,8 +3457,8 @@  discard block
 block discarded – undo
3457 3457
 
3458 3458
         foreach ($messengers as $messenger) {
3459 3459
             $active = $this->_message_resource_manager->is_messenger_active($messenger->name);
3460
-            $class = 'ee-messenger-' .  sanitize_key($messenger->label['singular']);
3461
-            $this->_m_mt_settings['messenger_tabs'][ $messenger->name ] = [
3460
+            $class = 'ee-messenger-'.sanitize_key($messenger->label['singular']);
3461
+            $this->_m_mt_settings['messenger_tabs'][$messenger->name] = [
3462 3462
                 'label' => ucwords($messenger->label['singular']),
3463 3463
                 'class' => $active ? "{$class} messenger-active" : $class,
3464 3464
                 'href'  => $messenger->name,
@@ -3476,7 +3476,7 @@  discard block
 block discarded – undo
3476 3476
             foreach ($message_types as $message_type) {
3477 3477
                 // first we need to verify that this message type is valid with this messenger. Cause if it isn't then
3478 3478
                 // it shouldn't show in either the inactive OR active metabox.
3479
-                if (! in_array($message_type->name, $message_types_for_messenger, true)) {
3479
+                if ( ! in_array($message_type->name, $message_types_for_messenger, true)) {
3480 3480
                     continue;
3481 3481
                 }
3482 3482
 
@@ -3487,12 +3487,12 @@  discard block
 block discarded – undo
3487 3487
                     ? 'active'
3488 3488
                     : 'inactive';
3489 3489
 
3490
-                $this->_m_mt_settings['message_type_tabs'][ $messenger->name ][ $a_or_i ][ $message_type->name ] = [
3490
+                $this->_m_mt_settings['message_type_tabs'][$messenger->name][$a_or_i][$message_type->name] = [
3491 3491
                     'label'    => ucwords($message_type->label['singular']),
3492
-                    'class'    => 'message-type-' . $a_or_i,
3493
-                    'slug_id'  => $message_type->name . '-messagetype-' . $messenger->name,
3494
-                    'mt_nonce' => wp_create_nonce($message_type->name . '_nonce'),
3495
-                    'href'     => 'espresso_' . $message_type->name . '_message_type_settings',
3492
+                    'class'    => 'message-type-'.$a_or_i,
3493
+                    'slug_id'  => $message_type->name.'-messagetype-'.$messenger->name,
3494
+                    'mt_nonce' => wp_create_nonce($message_type->name.'_nonce'),
3495
+                    'href'     => 'espresso_'.$message_type->name.'_message_type_settings',
3496 3496
                     'title'    => $a_or_i === 'active'
3497 3497
                         ? esc_html__('Drag this message type to the Inactive window to deactivate', 'event_espresso')
3498 3498
                         : esc_html__('Drag this message type to the messenger to activate', 'event_espresso'),
@@ -3523,25 +3523,25 @@  discard block
 block discarded – undo
3523 3523
         $fields                                         = $message_type->get_admin_settings_fields();
3524 3524
         $settings_template_args['template_form_fields'] = '';
3525 3525
 
3526
-        if (! empty($fields) && $active) {
3526
+        if ( ! empty($fields) && $active) {
3527 3527
             $existing_settings = $message_type->get_existing_admin_settings($messenger->name);
3528 3528
             foreach ($fields as $fldname => $fldprops) {
3529
-                $field_id                         = $messenger->name . '-' . $message_type->name . '-' . $fldname;
3530
-                $template_form_field[ $field_id ] = [
3531
-                    'name'       => 'message_type_settings[' . $fldname . ']',
3529
+                $field_id                         = $messenger->name.'-'.$message_type->name.'-'.$fldname;
3530
+                $template_form_field[$field_id] = [
3531
+                    'name'       => 'message_type_settings['.$fldname.']',
3532 3532
                     'label'      => $fldprops['label'],
3533 3533
                     'input'      => $fldprops['field_type'],
3534 3534
                     'type'       => $fldprops['value_type'],
3535 3535
                     'required'   => $fldprops['required'],
3536 3536
                     'validation' => $fldprops['validation'],
3537
-                    'value'      => isset($existing_settings[ $fldname ])
3538
-                        ? $existing_settings[ $fldname ]
3537
+                    'value'      => isset($existing_settings[$fldname])
3538
+                        ? $existing_settings[$fldname]
3539 3539
                         : $fldprops['default'],
3540 3540
                     'options'    => isset($fldprops['options'])
3541 3541
                         ? $fldprops['options']
3542 3542
                         : [],
3543
-                    'default'    => isset($existing_settings[ $fldname ])
3544
-                        ? $existing_settings[ $fldname ]
3543
+                    'default'    => isset($existing_settings[$fldname])
3544
+                        ? $existing_settings[$fldname]
3545 3545
                         : $fldprops['default'],
3546 3546
                     'css_class'  => 'no-drag',
3547 3547
                     'format'     => $fldprops['format'],
@@ -3561,15 +3561,15 @@  discard block
 block discarded – undo
3561 3561
         $settings_template_args['description'] = $message_type->description;
3562 3562
         // we also need some hidden fields
3563 3563
         $hidden_fields = [
3564
-            'message_type_settings[messenger]' . $message_type->name    => [
3564
+            'message_type_settings[messenger]'.$message_type->name    => [
3565 3565
                 'type'  => 'hidden',
3566 3566
                 'value' => $messenger->name,
3567 3567
             ],
3568
-            'message_type_settings[message_type]' . $message_type->name => [
3568
+            'message_type_settings[message_type]'.$message_type->name => [
3569 3569
                 'type'  => 'hidden',
3570 3570
                 'value' => $message_type->name,
3571 3571
             ],
3572
-            'type' . $message_type->name                                => [
3572
+            'type'.$message_type->name                                => [
3573 3573
                 'type'  => 'hidden',
3574 3574
                 'value' => 'message_type',
3575 3575
             ],
@@ -3579,12 +3579,12 @@  discard block
 block discarded – undo
3579 3579
             $hidden_fields,
3580 3580
             'array'
3581 3581
         );
3582
-        $settings_template_args['show_form']     = empty($settings_template_args['template_form_fields'])
3582
+        $settings_template_args['show_form'] = empty($settings_template_args['template_form_fields'])
3583 3583
             ? ' hidden'
3584 3584
             : '';
3585 3585
 
3586 3586
 
3587
-        $template = EE_MSG_TEMPLATE_PATH . 'ee_msg_mt_settings_content.template.php';
3587
+        $template = EE_MSG_TEMPLATE_PATH.'ee_msg_mt_settings_content.template.php';
3588 3588
         return EEH_Template::display_template($template, $settings_template_args, true);
3589 3589
     }
3590 3590
 
@@ -3612,21 +3612,21 @@  discard block
 block discarded – undo
3612 3612
 
3613 3613
                 // messenger meta boxes
3614 3614
                 $active         = $selected_messenger === $messenger;
3615
-                $active_mt_tabs = isset($this->_m_mt_settings['message_type_tabs'][ $messenger ]['active'])
3616
-                    ? $this->_m_mt_settings['message_type_tabs'][ $messenger ]['active']
3615
+                $active_mt_tabs = isset($this->_m_mt_settings['message_type_tabs'][$messenger]['active'])
3616
+                    ? $this->_m_mt_settings['message_type_tabs'][$messenger]['active']
3617 3617
                     : '';
3618 3618
 
3619
-                $m_boxes[ $messenger . '_a_box' ] = sprintf(
3619
+                $m_boxes[$messenger.'_a_box'] = sprintf(
3620 3620
                     esc_html__('%s Settings', 'event_espresso'),
3621 3621
                     $tab_array['label']
3622 3622
                 );
3623 3623
 
3624
-                $m_template_args[ $messenger . '_a_box' ] = [
3624
+                $m_template_args[$messenger.'_a_box'] = [
3625 3625
                     'active_message_types'   => ! empty($active_mt_tabs) ? $this->_get_mt_tabs($active_mt_tabs) : '',
3626 3626
                     'inactive_message_types' => isset(
3627
-                        $this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive']
3627
+                        $this->_m_mt_settings['message_type_tabs'][$messenger]['inactive']
3628 3628
                     )
3629
-                        ? $this->_get_mt_tabs($this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive'])
3629
+                        ? $this->_get_mt_tabs($this->_m_mt_settings['message_type_tabs'][$messenger]['inactive'])
3630 3630
                         : '',
3631 3631
                     'content'                => $this->_get_messenger_box_content($tab_array['obj']),
3632 3632
                     'hidden'                 => $active ? '' : ' hidden',
@@ -3638,13 +3638,13 @@  discard block
 block discarded – undo
3638 3638
                 // message type meta boxes
3639 3639
                 // (which is really just the inactive container for each messenger
3640 3640
                 // showing inactive message types for that messenger)
3641
-                $mt_boxes[ $messenger . '_i_box' ]         = esc_html__('Inactive Message Types', 'event_espresso');
3642
-                $mt_template_args[ $messenger . '_i_box' ] = [
3641
+                $mt_boxes[$messenger.'_i_box']         = esc_html__('Inactive Message Types', 'event_espresso');
3642
+                $mt_template_args[$messenger.'_i_box'] = [
3643 3643
                     'active_message_types'   => ! empty($active_mt_tabs) ? $this->_get_mt_tabs($active_mt_tabs) : '',
3644 3644
                     'inactive_message_types' => isset(
3645
-                        $this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive']
3645
+                        $this->_m_mt_settings['message_type_tabs'][$messenger]['inactive']
3646 3646
                     )
3647
-                        ? $this->_get_mt_tabs($this->_m_mt_settings['message_type_tabs'][ $messenger ]['inactive'])
3647
+                        ? $this->_get_mt_tabs($this->_m_mt_settings['message_type_tabs'][$messenger]['inactive'])
3648 3648
                         : '',
3649 3649
                     'hidden'                 => $active ? '' : ' hidden',
3650 3650
                     'hide_on_message'        => $hide_on_message,
@@ -3657,14 +3657,14 @@  discard block
 block discarded – undo
3657 3657
 
3658 3658
 
3659 3659
         // register messenger metaboxes
3660
-        $m_template_path = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_messenger_mt_meta_box.template.php';
3660
+        $m_template_path = EE_MSG_TEMPLATE_PATH.'ee_msg_details_messenger_mt_meta_box.template.php';
3661 3661
         foreach ($m_boxes as $box => $label) {
3662
-            $callback_args = ['template_path' => $m_template_path, 'template_args' => $m_template_args[ $box ]];
3662
+            $callback_args = ['template_path' => $m_template_path, 'template_args' => $m_template_args[$box]];
3663 3663
             $msgr          = str_replace('_a_box', '', $box);
3664 3664
             $this->addMetaBox(
3665
-                'espresso_' . $msgr . '_settings',
3665
+                'espresso_'.$msgr.'_settings',
3666 3666
                 $label,
3667
-                function ($post, $metabox) {
3667
+                function($post, $metabox) {
3668 3668
                     EEH_Template::display_template(
3669 3669
                         $metabox['args']['template_path'],
3670 3670
                         $metabox['args']['template_args']
@@ -3678,17 +3678,17 @@  discard block
 block discarded – undo
3678 3678
         }
3679 3679
 
3680 3680
         // register message type metaboxes
3681
-        $mt_template_path = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_messenger_meta_box.template.php';
3681
+        $mt_template_path = EE_MSG_TEMPLATE_PATH.'ee_msg_details_messenger_meta_box.template.php';
3682 3682
         foreach ($mt_boxes as $box => $label) {
3683 3683
             $callback_args = [
3684 3684
                 'template_path' => $mt_template_path,
3685
-                'template_args' => $mt_template_args[ $box ],
3685
+                'template_args' => $mt_template_args[$box],
3686 3686
             ];
3687
-            $mt            = str_replace('_i_box', '', $box);
3687
+            $mt = str_replace('_i_box', '', $box);
3688 3688
             $this->addMetaBox(
3689
-                'espresso_' . $mt . '_inactive_mts',
3689
+                'espresso_'.$mt.'_inactive_mts',
3690 3690
                 $label,
3691
-                function ($post, $metabox) {
3691
+                function($post, $metabox) {
3692 3692
                     EEH_Template::display_template(
3693 3693
                         $metabox['args']['template_path'],
3694 3694
                         $metabox['args']['template_args']
@@ -3835,7 +3835,7 @@  discard block
 block discarded – undo
3835 3835
             if ($form->is_valid()) {
3836 3836
                 $valid_data = $form->valid_data();
3837 3837
                 foreach ($valid_data as $property => $value) {
3838
-                    $setter = 'set_' . $property;
3838
+                    $setter = 'set_'.$property;
3839 3839
                     if (method_exists($network_config, $setter)) {
3840 3840
                         $network_config->{$setter}($value);
3841 3841
                     } elseif (
@@ -3871,7 +3871,7 @@  discard block
 block discarded – undo
3871 3871
     protected function _get_mt_tabs($tab_array)
3872 3872
     {
3873 3873
         $tab_array = (array) $tab_array;
3874
-        $template  = EE_MSG_TEMPLATE_PATH . 'ee_msg_details_mt_settings_tab_item.template.php';
3874
+        $template  = EE_MSG_TEMPLATE_PATH.'ee_msg_details_mt_settings_tab_item.template.php';
3875 3875
         $tabs      = '';
3876 3876
 
3877 3877
         foreach ($tab_array as $tab) {
@@ -3899,19 +3899,19 @@  discard block
 block discarded – undo
3899 3899
         $settings_template_args['active'] = $this->_message_resource_manager->is_messenger_active($messenger->name);
3900 3900
 
3901 3901
 
3902
-        if (! empty($fields)) {
3902
+        if ( ! empty($fields)) {
3903 3903
             $existing_settings = $messenger->get_existing_admin_settings();
3904 3904
 
3905 3905
             foreach ($fields as $field_name => $field_props) {
3906
-                $field_id                         = $messenger->name . '-' . $field_name;
3907
-                $template_form_field[ $field_id ] = [
3908
-                    'name'       => 'messenger_settings[' . $field_id . ']',
3906
+                $field_id                         = $messenger->name.'-'.$field_name;
3907
+                $template_form_field[$field_id] = [
3908
+                    'name'       => 'messenger_settings['.$field_id.']',
3909 3909
                     'label'      => $field_props['label'],
3910 3910
                     'input'      => $field_props['field_type'],
3911 3911
                     'type'       => $field_props['value_type'],
3912 3912
                     'required'   => $field_props['required'],
3913 3913
                     'validation' => $field_props['validation'],
3914
-                    'value'      => $existing_settings[ $field_id ] ?? $field_props['default'],
3914
+                    'value'      => $existing_settings[$field_id] ?? $field_props['default'],
3915 3915
                     'css_class'  => '',
3916 3916
                     'format'     => $field_props['format'],
3917 3917
                 ];
@@ -3924,20 +3924,20 @@  discard block
 block discarded – undo
3924 3924
 
3925 3925
         // we also need some hidden fields
3926 3926
         $settings_template_args['hidden_fields'] = [
3927
-            'messenger_settings[messenger]' . $messenger->name => [
3927
+            'messenger_settings[messenger]'.$messenger->name => [
3928 3928
                 'type'  => 'hidden',
3929 3929
                 'value' => $messenger->name,
3930 3930
             ],
3931
-            'type' . $messenger->name                          => [
3931
+            'type'.$messenger->name                          => [
3932 3932
                 'type'  => 'hidden',
3933 3933
                 'value' => 'messenger',
3934 3934
             ],
3935 3935
         ];
3936 3936
 
3937 3937
         // make sure any active message types that are existing are included in the hidden fields
3938
-        if (isset($this->_m_mt_settings['message_type_tabs'][ $messenger->name ]['active'])) {
3939
-            foreach ($this->_m_mt_settings['message_type_tabs'][ $messenger->name ]['active'] as $mt => $values) {
3940
-                $settings_template_args['hidden_fields'][ 'messenger_settings[message_types][' . $mt . ']' ] = [
3938
+        if (isset($this->_m_mt_settings['message_type_tabs'][$messenger->name]['active'])) {
3939
+            foreach ($this->_m_mt_settings['message_type_tabs'][$messenger->name]['active'] as $mt => $values) {
3940
+                $settings_template_args['hidden_fields']['messenger_settings[message_types]['.$mt.']'] = [
3941 3941
                     'type'  => 'hidden',
3942 3942
                     'value' => $mt,
3943 3943
                 ];
@@ -3947,7 +3947,7 @@  discard block
 block discarded – undo
3947 3947
             $settings_template_args['hidden_fields'],
3948 3948
             'array'
3949 3949
         );
3950
-        $active                                  =
3950
+        $active =
3951 3951
             $this->_message_resource_manager->is_messenger_active($messenger->name);
3952 3952
 
3953 3953
         $settings_template_args['messenger']           = $messenger->name;
@@ -3967,9 +3967,9 @@  discard block
 block discarded – undo
3967 3967
 
3968 3968
 
3969 3969
         $settings_template_args['on_off_action'] = $active ? 'messenger-off' : 'messenger-on';
3970
-        $settings_template_args['nonce']         = wp_create_nonce('activate_' . $messenger->name . '_toggle_nonce');
3970
+        $settings_template_args['nonce']         = wp_create_nonce('activate_'.$messenger->name.'_toggle_nonce');
3971 3971
         $settings_template_args['on_off_status'] = $active;
3972
-        $template                                = EE_MSG_TEMPLATE_PATH . 'ee_msg_m_settings_content.template.php';
3972
+        $template                                = EE_MSG_TEMPLATE_PATH.'ee_msg_m_settings_content.template.php';
3973 3973
         return EEH_Template::display_template(
3974 3974
             $template,
3975 3975
             $settings_template_args,
@@ -3994,7 +3994,7 @@  discard block
 block discarded – undo
3994 3994
         $this->_prep_default_response_for_messenger_or_message_type_toggle();
3995 3995
         // let's check that we have required data
3996 3996
 
3997
-        if (! $this->_active_messenger_name) {
3997
+        if ( ! $this->_active_messenger_name) {
3998 3998
             EE_Error::add_error(
3999 3999
                 esc_html__('Messenger name needed to toggle activation. None given', 'event_espresso'),
4000 4000
                 __FILE__,
@@ -4012,7 +4012,7 @@  discard block
 block discarded – undo
4012 4012
 
4013 4013
 
4014 4014
         $status = $this->request->getRequestParam('status');
4015
-        if (! $status) {
4015
+        if ( ! $status) {
4016 4016
             EE_Error::add_error(
4017 4017
                 esc_html__(
4018 4018
                     'Messenger status needed to know whether activation or deactivation is happening. No status is given',
@@ -4069,7 +4069,7 @@  discard block
 block discarded – undo
4069 4069
         $this->_prep_default_response_for_messenger_or_message_type_toggle();
4070 4070
 
4071 4071
         // let's make sure we have the necessary data
4072
-        if (! $this->_active_message_type_name) {
4072
+        if ( ! $this->_active_message_type_name) {
4073 4073
             EE_Error::add_error(
4074 4074
                 esc_html__('Message Type name needed to toggle activation. None given', 'event_espresso'),
4075 4075
                 __FILE__,
@@ -4079,7 +4079,7 @@  discard block
 block discarded – undo
4079 4079
             $success = false;
4080 4080
         }
4081 4081
 
4082
-        if (! $this->_active_messenger_name) {
4082
+        if ( ! $this->_active_messenger_name) {
4083 4083
             EE_Error::add_error(
4084 4084
                 esc_html__('Messenger name needed to toggle activation. None given', 'event_espresso'),
4085 4085
                 __FILE__,
@@ -4090,7 +4090,7 @@  discard block
 block discarded – undo
4090 4090
         }
4091 4091
 
4092 4092
         $status = $this->request->getRequestParam('status');
4093
-        if (! $status) {
4093
+        if ( ! $status) {
4094 4094
             EE_Error::add_error(
4095 4095
                 esc_html__(
4096 4096
                     'Messenger status needed to know whether activation or deactivation is happening. No status is given',
@@ -4302,7 +4302,7 @@  discard block
 block discarded – undo
4302 4302
         EE_Message_Type $message_type = null
4303 4303
     ) {
4304 4304
         // if $messenger isn't a valid messenger object then get out.
4305
-        if (! $messenger instanceof EE_Messenger) {
4305
+        if ( ! $messenger instanceof EE_Messenger) {
4306 4306
             EE_Error::add_error(
4307 4307
                 esc_html__('The messenger being activated is not a valid messenger', 'event_espresso'),
4308 4308
                 __FILE__,
@@ -4356,7 +4356,7 @@  discard block
 block discarded – undo
4356 4356
             // message types after the activation process.  However its possible some messengers don't HAVE any default_message_types
4357 4357
             // in which case we just give a success message for the messenger being successfully activated.
4358 4358
         } else {
4359
-            if (! $messenger->get_default_message_types()) {
4359
+            if ( ! $messenger->get_default_message_types()) {
4360 4360
                 // messenger doesn't have any default message types so still a success.
4361 4361
                 EE_Error::add_success(
4362 4362
                     sprintf(
@@ -4412,7 +4412,7 @@  discard block
 block discarded – undo
4412 4412
         EE_Error::overwrite_success();
4413 4413
 
4414 4414
         // if $messenger isn't a valid messenger object then get out.
4415
-        if (! $messenger instanceof EE_Messenger) {
4415
+        if ( ! $messenger instanceof EE_Messenger) {
4416 4416
             EE_Error::add_error(
4417 4417
                 esc_html__('The messenger being deactivated is not a valid messenger', 'event_espresso'),
4418 4418
                 __FILE__,
@@ -4482,7 +4482,7 @@  discard block
 block discarded – undo
4482 4482
      */
4483 4483
     public function update_mt_form()
4484 4484
     {
4485
-        if (! $this->_active_messenger_name || ! $this->_active_message_type_name) {
4485
+        if ( ! $this->_active_messenger_name || ! $this->_active_message_type_name) {
4486 4486
             EE_Error::add_error(
4487 4487
                 esc_html__('Require message type or messenger to send an updated form', 'event_espresso'),
4488 4488
                 __FILE__,
@@ -4493,7 +4493,7 @@  discard block
 block discarded – undo
4493 4493
         }
4494 4494
 
4495 4495
         $message_types = $this->get_installed_message_types();
4496
-        $message_type  = $message_types[ $this->_active_message_type_name ];
4496
+        $message_type  = $message_types[$this->_active_message_type_name];
4497 4497
         $messenger     = $this->_message_resource_manager->get_active_messenger($this->_active_messenger_name);
4498 4498
         $content       = $this->_message_type_settings_content($message_type, $messenger, true);
4499 4499
 
@@ -4512,7 +4512,7 @@  discard block
 block discarded – undo
4512 4512
     public function save_settings()
4513 4513
     {
4514 4514
         $type = $this->request->getRequestParam('type');
4515
-        if (! $type) {
4515
+        if ( ! $type) {
4516 4516
             EE_Error::add_error(
4517 4517
                 esc_html__(
4518 4518
                     'Cannot save settings because type is unknown (messenger settings or message type settings?)',
Please login to merge, or discard this patch.
core/data_migration_scripts/EE_DMS_Core_4_5_0.dms.php 1 patch
Indentation   +229 added lines, -229 removed lines patch added patch discarded remove patch
@@ -10,9 +10,9 @@  discard block
 block discarded – undo
10 10
 $stages = glob(EE_CORE . 'data_migration_scripts/4_5_0_stages/*');
11 11
 $class_to_filepath = array();
12 12
 foreach ($stages as $filepath) {
13
-    $matches = array();
14
-    preg_match('~4_5_0_stages/(.*).dmsstage.php~', $filepath, $matches);
15
-    $class_to_filepath[ $matches[1] ] = $filepath;
13
+	$matches = array();
14
+	preg_match('~4_5_0_stages/(.*).dmsstage.php~', $filepath, $matches);
15
+	$class_to_filepath[ $matches[1] ] = $filepath;
16 16
 }
17 17
 // give addons a chance to autoload their stages too
18 18
 $class_to_filepath = apply_filters('FHEE__EE_DMS_4_5_0__autoloaded_stages', $class_to_filepath);
@@ -30,59 +30,59 @@  discard block
 block discarded – undo
30 30
  */
31 31
 class EE_DMS_Core_4_5_0 extends EE_Data_Migration_Script_Base
32 32
 {
33
-    /**
34
-     * EE_DMS_Core_4_5_0 constructor.
35
-     *
36
-     * @param TableManager  $table_manager
37
-     * @param TableAnalysis $table_analysis
38
-     */
39
-    public function __construct(TableManager $table_manager = null, TableAnalysis $table_analysis = null)
40
-    {
41
-        $this->_pretty_name = esc_html__("Data Update to Event Espresso 4.5.0", "event_espresso");
42
-        $this->_priority = 10;
43
-        $this->_migration_stages = array(
44
-            new EE_DMS_4_5_0_update_wp_user_for_tickets(),
45
-            new EE_DMS_4_5_0_update_wp_user_for_prices(),
46
-            new EE_DMS_4_5_0_update_wp_user_for_price_types(),
47
-            new EE_DMS_4_5_0_update_wp_user_for_question_groups(),
48
-            new EE_DMS_4_5_0_invoice_settings(),
49
-        );
50
-        parent::__construct($table_manager, $table_analysis);
51
-    }
33
+	/**
34
+	 * EE_DMS_Core_4_5_0 constructor.
35
+	 *
36
+	 * @param TableManager  $table_manager
37
+	 * @param TableAnalysis $table_analysis
38
+	 */
39
+	public function __construct(TableManager $table_manager = null, TableAnalysis $table_analysis = null)
40
+	{
41
+		$this->_pretty_name = esc_html__("Data Update to Event Espresso 4.5.0", "event_espresso");
42
+		$this->_priority = 10;
43
+		$this->_migration_stages = array(
44
+			new EE_DMS_4_5_0_update_wp_user_for_tickets(),
45
+			new EE_DMS_4_5_0_update_wp_user_for_prices(),
46
+			new EE_DMS_4_5_0_update_wp_user_for_price_types(),
47
+			new EE_DMS_4_5_0_update_wp_user_for_question_groups(),
48
+			new EE_DMS_4_5_0_invoice_settings(),
49
+		);
50
+		parent::__construct($table_manager, $table_analysis);
51
+	}
52 52
 
53 53
 
54 54
 
55
-    public function can_migrate_from_version($version_array)
56
-    {
57
-        $version_string = $version_array['Core'];
58
-        if (version_compare($version_string, '4.5.0.decaf', '<') && version_compare($version_string, '4.3.0.decaf', '>=')) {
55
+	public function can_migrate_from_version($version_array)
56
+	{
57
+		$version_string = $version_array['Core'];
58
+		if (version_compare($version_string, '4.5.0.decaf', '<') && version_compare($version_string, '4.3.0.decaf', '>=')) {
59 59
 //          echo "$version_string can be migrated from";
60
-            return true;
61
-        } elseif (! $version_string) {
60
+			return true;
61
+		} elseif (! $version_string) {
62 62
 //          echo "no version string provided: $version_string";
63
-            // no version string provided... this must be pre 4.3
64
-            return false;// changed mind. dont want people thinking they should migrate yet because they cant
65
-        } else {
63
+			// no version string provided... this must be pre 4.3
64
+			return false;// changed mind. dont want people thinking they should migrate yet because they cant
65
+		} else {
66 66
 //          echo "$version_string doesnt apply";
67
-            return false;
68
-        }
69
-    }
67
+			return false;
68
+		}
69
+	}
70 70
 
71 71
 
72 72
 
73
-    public function schema_changes_before_migration()
74
-    {
75
-        // relies on 4.1's EEH_Activation::create_table
76
-        require_once(EE_HELPERS . 'EEH_Activation.helper.php');
77
-        $table_name = 'esp_answer';
78
-        $sql = "ANS_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
73
+	public function schema_changes_before_migration()
74
+	{
75
+		// relies on 4.1's EEH_Activation::create_table
76
+		require_once(EE_HELPERS . 'EEH_Activation.helper.php');
77
+		$table_name = 'esp_answer';
78
+		$sql = "ANS_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
79 79
 					REG_ID int(10) unsigned NOT NULL,
80 80
 					QST_ID int(10) unsigned NOT NULL,
81 81
 					ANS_value text NOT NULL,
82 82
 					PRIMARY KEY  (ANS_ID)";
83
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
84
-        $table_name = 'esp_attendee_meta';
85
-        $sql = "ATTM_ID int(10) unsigned NOT	NULL AUTO_INCREMENT,
83
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
84
+		$table_name = 'esp_attendee_meta';
85
+		$sql = "ATTM_ID int(10) unsigned NOT	NULL AUTO_INCREMENT,
86 86
 						ATT_ID bigint(20) unsigned NOT NULL,
87 87
 						ATT_fname varchar(45) NOT NULL,
88 88
 						ATT_lname varchar(45) NOT	NULL,
@@ -98,9 +98,9 @@  discard block
 block discarded – undo
98 98
 								KEY ATT_fname (ATT_fname),
99 99
 								KEY ATT_lname (ATT_lname),
100 100
 								KEY ATT_email (ATT_email(191))";
101
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB ');
102
-        $table_name = 'esp_country';
103
-        $sql = "CNT_ISO varchar(2) COLLATE utf8_bin NOT NULL,
101
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB ');
102
+		$table_name = 'esp_country';
103
+		$sql = "CNT_ISO varchar(2) COLLATE utf8_bin NOT NULL,
104 104
 					  CNT_ISO3 varchar(3) COLLATE utf8_bin NOT NULL,
105 105
 					  RGN_ID tinyint(3) unsigned DEFAULT NULL,
106 106
 					  CNT_name varchar(45) COLLATE utf8_bin NOT NULL,
@@ -116,9 +116,9 @@  discard block
 block discarded – undo
116 116
 					  CNT_is_EU tinyint(1) DEFAULT '0',
117 117
 					  CNT_active tinyint(1) DEFAULT '0',
118 118
 					  PRIMARY KEY  (CNT_ISO)";
119
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
120
-        $table_name = 'esp_datetime';
121
-        $sql = "DTT_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
119
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
120
+		$table_name = 'esp_datetime';
121
+		$sql = "DTT_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
122 122
 				  EVT_ID bigint(20) unsigned NOT NULL,
123 123
 				  DTT_name varchar(255) NOT NULL DEFAULT '',
124 124
 				  DTT_description text NOT NULL,
@@ -133,9 +133,9 @@  discard block
 block discarded – undo
133 133
 						PRIMARY KEY  (DTT_ID),
134 134
 						KEY EVT_ID (EVT_ID),
135 135
 						KEY DTT_is_primary (DTT_is_primary)";
136
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
137
-        $table_name = 'esp_event_meta';
138
-        $sql = "
136
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
137
+		$table_name = 'esp_event_meta';
138
+		$sql = "
139 139
 			EVTM_ID int(10) NOT NULL AUTO_INCREMENT,
140 140
 			EVT_ID bigint(20) unsigned NOT NULL,
141 141
 			EVT_display_desc tinyint(1) unsigned NOT NULL DEFAULT 1,
@@ -150,31 +150,31 @@  discard block
 block discarded – undo
150 150
 			EVT_external_URL varchar(200) NULL,
151 151
 			EVT_donations tinyint(1) NULL,
152 152
 			PRIMARY KEY  (EVTM_ID)";
153
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
154
-        $table_name = 'esp_event_question_group';
155
-        $sql = "EQG_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
153
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
154
+		$table_name = 'esp_event_question_group';
155
+		$sql = "EQG_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
156 156
 					EVT_ID bigint(20) unsigned NOT NULL,
157 157
 					QSG_ID int(10) unsigned NOT NULL,
158 158
 					EQG_primary tinyint(1) unsigned NOT NULL DEFAULT 0,
159 159
 					PRIMARY KEY  (EQG_ID)";
160
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
161
-        $table_name = 'esp_event_venue';
162
-        $sql = "EVV_ID int(11) NOT NULL AUTO_INCREMENT,
160
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
161
+		$table_name = 'esp_event_venue';
162
+		$sql = "EVV_ID int(11) NOT NULL AUTO_INCREMENT,
163 163
 				EVT_ID bigint(20) unsigned NOT NULL,
164 164
 				VNU_ID bigint(20) unsigned NOT NULL,
165 165
 				EVV_primary tinyint(1) unsigned NOT NULL DEFAULT 0,
166 166
 				PRIMARY KEY  (EVV_ID)";
167
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
168
-        $table_name = 'esp_extra_meta';
169
-        $sql = "EXM_ID int(11) NOT NULL AUTO_INCREMENT,
167
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
168
+		$table_name = 'esp_extra_meta';
169
+		$sql = "EXM_ID int(11) NOT NULL AUTO_INCREMENT,
170 170
 				OBJ_ID int(11) DEFAULT NULL,
171 171
 				EXM_type varchar(45) DEFAULT NULL,
172 172
 				EXM_key varchar(45) DEFAULT NULL,
173 173
 				EXM_value text,
174 174
 				PRIMARY KEY  (EXM_ID)";
175
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
176
-        $table_name = 'esp_line_item';
177
-        $sql = "LIN_ID int(11) NOT NULL AUTO_INCREMENT,
175
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
176
+		$table_name = 'esp_line_item';
177
+		$sql = "LIN_ID int(11) NOT NULL AUTO_INCREMENT,
178 178
 				LIN_code varchar(245) NOT NULL DEFAULT '',
179 179
 				TXN_ID int(11) DEFAULT NULL,
180 180
 				LIN_name varchar(245) NOT NULL DEFAULT '',
@@ -190,19 +190,19 @@  discard block
 block discarded – undo
190 190
 				OBJ_ID int(11) DEFAULT NULL,
191 191
 				OBJ_type varchar(45)DEFAULT NULL,
192 192
 				PRIMARY KEY  (LIN_ID)";
193
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
194
-        $table_name = 'esp_message_template';
195
-        $sql = "MTP_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
193
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
194
+		$table_name = 'esp_message_template';
195
+		$sql = "MTP_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
196 196
 					GRP_ID int(10) unsigned NOT NULL,
197 197
 					MTP_context varchar(50) NOT NULL,
198 198
 					MTP_template_field varchar(30) NOT NULL,
199 199
 					MTP_content text NOT NULL,
200 200
 					PRIMARY KEY  (MTP_ID),
201 201
 					KEY GRP_ID (GRP_ID)";
202
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
203
-        $this->_get_table_manager()->dropIndex('esp_message_template_group', 'EVT_ID');
204
-        $table_name = 'esp_message_template_group';
205
-        $sql = "GRP_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
202
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
203
+		$this->_get_table_manager()->dropIndex('esp_message_template_group', 'EVT_ID');
204
+		$table_name = 'esp_message_template_group';
205
+		$sql = "GRP_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
206 206
 					MTP_user_id int(10) NOT NULL DEFAULT '1',
207 207
 					MTP_name varchar(245) NOT NULL DEFAULT '',
208 208
 					MTP_description varchar(245) NOT NULL DEFAULT '',
@@ -214,17 +214,17 @@  discard block
 block discarded – undo
214 214
 					MTP_is_active tinyint(1) NOT NULL DEFAULT '1',
215 215
 					PRIMARY KEY  (GRP_ID),
216 216
 					KEY MTP_user_id (MTP_user_id)";
217
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
218
-        $table_name = 'esp_event_message_template';
219
-        $sql = "EMT_ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
217
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
218
+		$table_name = 'esp_event_message_template';
219
+		$sql = "EMT_ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
220 220
 					EVT_ID bigint(20) unsigned NOT NULL DEFAULT 0,
221 221
 					GRP_ID int(10) unsigned NOT NULL DEFAULT 0,
222 222
 					PRIMARY KEY  (EMT_ID),
223 223
 					KEY EVT_ID (EVT_ID),
224 224
 					KEY GRP_ID (GRP_ID)";
225
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
226
-        $table_name = 'esp_payment';
227
-        $sql = "PAY_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
225
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
226
+		$table_name = 'esp_payment';
227
+		$sql = "PAY_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
228 228
 					TXN_ID int(10) unsigned DEFAULT NULL,
229 229
 					STS_ID varchar(3) COLLATE utf8_bin DEFAULT NULL,
230 230
 					PAY_timestamp datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
@@ -240,28 +240,28 @@  discard block
 block discarded – undo
240 240
 					PRIMARY KEY  (PAY_ID),
241 241
 					KEY TXN_ID (TXN_ID),
242 242
 					KEY PAY_timestamp (PAY_timestamp)";
243
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB ');
244
-        $table_name = "esp_ticket_price";
245
-        $sql = "TKP_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
243
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB ');
244
+		$table_name = "esp_ticket_price";
245
+		$sql = "TKP_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
246 246
 					  TKT_ID int(10) unsigned NOT NULL,
247 247
 					  PRC_ID int(10) unsigned NOT NULL,
248 248
 					  PRIMARY KEY  (TKP_ID)";
249
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
250
-        $table_name = "esp_datetime_ticket";
251
-        $sql = "DTK_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
249
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
250
+		$table_name = "esp_datetime_ticket";
251
+		$sql = "DTK_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
252 252
 					  DTT_ID int(10) unsigned NOT NULL,
253 253
 					  TKT_ID int(10) unsigned NOT NULL,
254 254
 					  PRIMARY KEY  (DTK_ID)";
255
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
256
-        $table_name = "esp_ticket_template";
257
-        $sql = "TTM_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
255
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
256
+		$table_name = "esp_ticket_template";
257
+		$sql = "TTM_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
258 258
 					  TTM_name varchar(45) NOT NULL,
259 259
 					  TTM_description text,
260 260
 					  TTM_file varchar(45),
261 261
 					  PRIMARY KEY  (TTM_ID)";
262
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
263
-        $table_name = 'esp_question';
264
-        $sql = 'QST_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
262
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
263
+		$table_name = 'esp_question';
264
+		$sql = 'QST_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
265 265
 					QST_display_text text NOT NULL,
266 266
 					QST_admin_label varchar(255) NOT NULL,
267 267
 					QST_system varchar(25) DEFAULT NULL,
@@ -273,25 +273,25 @@  discard block
 block discarded – undo
273 273
 					QST_wp_user bigint(20) unsigned NULL,
274 274
 					QST_deleted tinyint(1) unsigned NOT NULL DEFAULT 0,
275 275
 					PRIMARY KEY  (QST_ID)';
276
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
277
-        $table_name = 'esp_question_group_question';
278
-        $sql = "QGQ_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
276
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
277
+		$table_name = 'esp_question_group_question';
278
+		$sql = "QGQ_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
279 279
 					QSG_ID int(10) unsigned NOT NULL,
280 280
 					QST_ID int(10) unsigned NOT NULL,
281 281
 					QGQ_order int(10) unsigned NOT NULL DEFAULT 0,
282 282
 					PRIMARY KEY  (QGQ_ID) ";
283
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
284
-        $table_name = 'esp_question_option';
285
-        $sql = "QSO_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
283
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
284
+		$table_name = 'esp_question_option';
285
+		$sql = "QSO_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
286 286
 					QSO_value varchar(255) NOT NULL,
287 287
 					QSO_desc text NOT NULL,
288 288
 					QST_ID int(10) unsigned NOT NULL,
289 289
 					QSO_order int(10) unsigned NOT NULL DEFAULT 0,
290 290
 					QSO_deleted tinyint(1) unsigned NOT NULL DEFAULT 0,
291 291
 					PRIMARY KEY  (QSO_ID)";
292
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
293
-        $table_name = 'esp_registration';
294
-        $sql = "REG_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
292
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
293
+		$table_name = 'esp_registration';
294
+		$sql = "REG_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
295 295
 					  EVT_ID bigint(20) unsigned NOT NULL,
296 296
 					  ATT_ID bigint(20) unsigned NOT NULL,
297 297
 					  TXN_ID int(10) unsigned NOT NULL,
@@ -314,25 +314,25 @@  discard block
 block discarded – undo
314 314
 					  KEY STS_ID (STS_ID),
315 315
 					  KEY REG_url_link (REG_url_link),
316 316
 					  KEY REG_code (REG_code)";
317
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB ');
318
-        $table_name = 'esp_checkin';
319
-        $sql = "CHK_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
317
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB ');
318
+		$table_name = 'esp_checkin';
319
+		$sql = "CHK_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
320 320
 					REG_ID int(10) unsigned NOT NULL,
321 321
 					DTT_ID int(10) unsigned NOT NULL,
322 322
 					CHK_in tinyint(1) unsigned NOT NULL DEFAULT 1,
323 323
 					CHK_timestamp datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
324 324
 					PRIMARY KEY  (CHK_ID)";
325
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
326
-        $table_name = 'esp_state';
327
-        $sql = "STA_ID smallint(5) unsigned NOT NULL AUTO_INCREMENT,
325
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
326
+		$table_name = 'esp_state';
327
+		$sql = "STA_ID smallint(5) unsigned NOT NULL AUTO_INCREMENT,
328 328
 					  CNT_ISO varchar(2) COLLATE utf8_bin NOT NULL,
329 329
 					  STA_abbrev varchar(6) COLLATE utf8_bin NOT NULL,
330 330
 					  STA_name varchar(100) COLLATE utf8_bin NOT NULL,
331 331
 					  STA_active tinyint(1) DEFAULT '1',
332 332
 					  PRIMARY KEY  (STA_ID)";
333
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
334
-        $table_name = 'esp_status';
335
-        $sql = "STS_ID varchar(3) COLLATE utf8_bin NOT NULL,
333
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
334
+		$table_name = 'esp_status';
335
+		$sql = "STS_ID varchar(3) COLLATE utf8_bin NOT NULL,
336 336
 					  STS_code varchar(45) COLLATE utf8_bin NOT NULL,
337 337
 					  STS_type set('event','registration','transaction','payment','email') COLLATE utf8_bin NOT NULL,
338 338
 					  STS_can_edit tinyint(1) NOT NULL DEFAULT 0,
@@ -340,9 +340,9 @@  discard block
 block discarded – undo
340 340
 					  STS_open tinyint(1) NOT NULL DEFAULT 1,
341 341
 					  UNIQUE KEY STS_ID_UNIQUE (STS_ID),
342 342
 					  KEY STS_type (STS_type)";
343
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
344
-        $table_name = 'esp_transaction';
345
-        $sql = "TXN_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
343
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
344
+		$table_name = 'esp_transaction';
345
+		$sql = "TXN_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
346 346
 					  TXN_timestamp datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
347 347
 					  TXN_total decimal(10,3) DEFAULT '0.00',
348 348
 					  TXN_paid decimal(10,3) NOT NULL DEFAULT '0.00',
@@ -353,9 +353,9 @@  discard block
 block discarded – undo
353 353
 					  PRIMARY KEY  (TXN_ID),
354 354
 					  KEY TXN_timestamp (TXN_timestamp),
355 355
 					  KEY STS_ID (STS_ID)";
356
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
357
-        $table_name = 'esp_venue_meta';
358
-        $sql = "VNUM_ID int(11) NOT NULL AUTO_INCREMENT,
356
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
357
+		$table_name = 'esp_venue_meta';
358
+		$sql = "VNUM_ID int(11) NOT NULL AUTO_INCREMENT,
359 359
 			VNU_ID bigint(20) unsigned NOT NULL DEFAULT 0,
360 360
 			VNU_address varchar(255) DEFAULT NULL,
361 361
 			VNU_address2 varchar(255) DEFAULT NULL,
@@ -373,10 +373,10 @@  discard block
 block discarded – undo
373 373
 			PRIMARY KEY  (VNUM_ID),
374 374
 			KEY STA_ID (STA_ID),
375 375
 			KEY CNT_ISO (CNT_ISO)";
376
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
377
-        // modified tables
378
-        $table_name = "esp_price";
379
-        $sql = "PRC_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
376
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
377
+		// modified tables
378
+		$table_name = "esp_price";
379
+		$sql = "PRC_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
380 380
 					  PRT_ID tinyint(3) unsigned NOT NULL,
381 381
 					  PRC_amount decimal(10,3) NOT NULL DEFAULT '0.00',
382 382
 					  PRC_name varchar(245) NOT NULL,
@@ -388,9 +388,9 @@  discard block
 block discarded – undo
388 388
 					  PRC_wp_user bigint(20) unsigned NULL,
389 389
 					  PRC_parent int(10) unsigned DEFAULT 0,
390 390
 					  PRIMARY KEY  (PRC_ID)";
391
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
392
-        $table_name = "esp_price_type";
393
-        $sql = "PRT_ID tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
391
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
392
+		$table_name = "esp_price_type";
393
+		$sql = "PRT_ID tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
394 394
 				  PRT_name varchar(45) NOT NULL,
395 395
 				  PBT_ID tinyint(3) unsigned NOT NULL DEFAULT '1',
396 396
 				  PRT_is_percent tinyint(1) NOT NULL DEFAULT '0',
@@ -399,9 +399,9 @@  discard block
 block discarded – undo
399 399
 				  PRT_deleted tinyint(1) NOT NULL DEFAULT '0',
400 400
 				  UNIQUE KEY PRT_name_UNIQUE (PRT_name),
401 401
 				  PRIMARY KEY  (PRT_ID)";
402
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB ');
403
-        $table_name = "esp_ticket";
404
-        $sql = "TKT_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
402
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB ');
403
+		$table_name = "esp_ticket";
404
+		$sql = "TKT_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
405 405
 					  TTM_ID int(10) unsigned NOT NULL,
406 406
 					  TKT_name varchar(245) NOT NULL DEFAULT '',
407 407
 					  TKT_description text NOT NULL,
@@ -422,10 +422,10 @@  discard block
 block discarded – undo
422 422
 					  TKT_parent int(10) unsigned DEFAULT '0',
423 423
 					  TKT_deleted tinyint(1) NOT NULL DEFAULT '0',
424 424
 					  PRIMARY KEY  (TKT_ID)";
425
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
426
-        $this->_get_table_manager()->dropIndex('esp_question_group', 'QSG_identifier_UNIQUE');
427
-        $table_name = 'esp_question_group';
428
-        $sql = 'QSG_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
425
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
426
+		$this->_get_table_manager()->dropIndex('esp_question_group', 'QSG_identifier_UNIQUE');
427
+		$table_name = 'esp_question_group';
428
+		$sql = 'QSG_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
429 429
 					QSG_name varchar(255) NOT NULL,
430 430
 					QSG_identifier varchar(100) NOT NULL,
431 431
 					QSG_desc text NULL,
@@ -437,133 +437,133 @@  discard block
 block discarded – undo
437 437
 					QSG_wp_user bigint(20) unsigned NULL,
438 438
 					PRIMARY KEY  (QSG_ID),
439 439
 					UNIQUE KEY QSG_identifier_UNIQUE (QSG_identifier ASC)';
440
-        $this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
441
-        $script_4_1_defaults = EE_Registry::instance()->load_dms('Core_4_1_0');
442
-        // (because many need to convert old string states to foreign keys into the states table)
443
-        $script_4_1_defaults->insert_default_states();
444
-        $script_4_1_defaults->insert_default_countries();
445
-        // schema on price, price_types and tickets has changed so use the DEFAULT method in here instead of 4.1's and later.
446
-        $this->insert_default_price_types();
447
-        $this->insert_default_prices();
448
-        $this->insert_default_tickets();
449
-        // setting up the config wp option pretty well counts as a 'schema change', or at least should happen ehre
450
-        EE_Config::instance()->update_espresso_config(false, true);
451
-        return true;
452
-    }
440
+		$this->_table_should_exist_previously($table_name, $sql, 'ENGINE=InnoDB');
441
+		$script_4_1_defaults = EE_Registry::instance()->load_dms('Core_4_1_0');
442
+		// (because many need to convert old string states to foreign keys into the states table)
443
+		$script_4_1_defaults->insert_default_states();
444
+		$script_4_1_defaults->insert_default_countries();
445
+		// schema on price, price_types and tickets has changed so use the DEFAULT method in here instead of 4.1's and later.
446
+		$this->insert_default_price_types();
447
+		$this->insert_default_prices();
448
+		$this->insert_default_tickets();
449
+		// setting up the config wp option pretty well counts as a 'schema change', or at least should happen ehre
450
+		EE_Config::instance()->update_espresso_config(false, true);
451
+		return true;
452
+	}
453 453
 
454 454
 
455 455
 
456
-    /**
457
-     * @return boolean
458
-     */
459
-    public function schema_changes_after_migration()
460
-    {
461
-        return true;
462
-    }
456
+	/**
457
+	 * @return boolean
458
+	 */
459
+	public function schema_changes_after_migration()
460
+	{
461
+		return true;
462
+	}
463 463
 
464 464
 
465 465
 
466
-    public function migration_page_hooks()
467
-    {
468
-    }
466
+	public function migration_page_hooks()
467
+	{
468
+	}
469 469
 
470 470
 
471 471
 
472
-    /**
473
-     * insert_default_price_types
474
-     *
475
-     * @since 4.5.0
476
-     * @return void
477
-     */
478
-    public function insert_default_price_types()
479
-    {
480
-        global $wpdb;
481
-        $price_type_table = $wpdb->prefix . "esp_price_type";
482
-        if ($this->_get_table_analysis()->tableExists($price_type_table)) {
483
-            $SQL = 'SELECT COUNT(PRT_ID) FROM ' . $price_type_table;
484
-            $price_types_exist = $wpdb->get_var($SQL);
485
-            if (! $price_types_exist) {
486
-                $user_id = EEH_Activation::get_default_creator_id();
487
-                $SQL = "INSERT INTO $price_type_table ( PRT_ID, PRT_name, PBT_ID, PRT_is_percent, PRT_order, PRT_wp_user, PRT_deleted ) VALUES
472
+	/**
473
+	 * insert_default_price_types
474
+	 *
475
+	 * @since 4.5.0
476
+	 * @return void
477
+	 */
478
+	public function insert_default_price_types()
479
+	{
480
+		global $wpdb;
481
+		$price_type_table = $wpdb->prefix . "esp_price_type";
482
+		if ($this->_get_table_analysis()->tableExists($price_type_table)) {
483
+			$SQL = 'SELECT COUNT(PRT_ID) FROM ' . $price_type_table;
484
+			$price_types_exist = $wpdb->get_var($SQL);
485
+			if (! $price_types_exist) {
486
+				$user_id = EEH_Activation::get_default_creator_id();
487
+				$SQL = "INSERT INTO $price_type_table ( PRT_ID, PRT_name, PBT_ID, PRT_is_percent, PRT_order, PRT_wp_user, PRT_deleted ) VALUES
488 488
 							(1, '" . esc_html__('Base Price', 'event_espresso') . "', 1,  0, 0, $user_id, 0),
489 489
 							(2, '" . esc_html__('Percent Discount', 'event_espresso') . "', 2,  1, 20, $user_id, 0),
490 490
 							(3, '" . esc_html__('Dollar Discount', 'event_espresso') . "', 2,  0, 30, $user_id, 0),
491 491
 							(4, '" . esc_html__('Percent Surcharge', 'event_espresso') . "', 3,  1, 40, $user_id,  0),
492 492
 							(5, '" . esc_html__('Dollar Surcharge', 'event_espresso') . "', 3,  0, 50, $user_id, 0);";
493
-                $SQL = apply_filters('FHEE__EE_DMS_4_5_0__insert_default_price_types__SQL', $SQL);
494
-                $wpdb->query($SQL);
495
-            }
496
-        }
497
-    }
493
+				$SQL = apply_filters('FHEE__EE_DMS_4_5_0__insert_default_price_types__SQL', $SQL);
494
+				$wpdb->query($SQL);
495
+			}
496
+		}
497
+	}
498 498
 
499 499
 
500 500
 
501
-    /**
502
-     * insert DEFAULT prices.
503
-     *  If we're INSTALLING 4.x CAF, then we add a few extra DEFAULT prices
504
-     * when EEH_Activaion's initialize_db_content is called via  ahook in
505
-     * EE_Brewing_regular
506
-     *
507
-     * @since 4.5.0
508
-     * @return void
509
-     */
510
-    public function insert_default_prices()
511
-    {
512
-        global $wpdb;
513
-        $price_table = $wpdb->prefix . "esp_price";
514
-        if ($this->_get_table_analysis()->tableExists($price_table)) {
515
-            $SQL = 'SELECT COUNT(PRC_ID) FROM ' . $price_table;
516
-            $prices_exist = $wpdb->get_var($SQL);
517
-            if (! $prices_exist) {
518
-                $user_id = EEH_Activation::get_default_creator_id();
519
-                $SQL = "INSERT INTO $price_table
501
+	/**
502
+	 * insert DEFAULT prices.
503
+	 *  If we're INSTALLING 4.x CAF, then we add a few extra DEFAULT prices
504
+	 * when EEH_Activaion's initialize_db_content is called via  ahook in
505
+	 * EE_Brewing_regular
506
+	 *
507
+	 * @since 4.5.0
508
+	 * @return void
509
+	 */
510
+	public function insert_default_prices()
511
+	{
512
+		global $wpdb;
513
+		$price_table = $wpdb->prefix . "esp_price";
514
+		if ($this->_get_table_analysis()->tableExists($price_table)) {
515
+			$SQL = 'SELECT COUNT(PRC_ID) FROM ' . $price_table;
516
+			$prices_exist = $wpdb->get_var($SQL);
517
+			if (! $prices_exist) {
518
+				$user_id = EEH_Activation::get_default_creator_id();
519
+				$SQL = "INSERT INTO $price_table
520 520
 							(PRC_ID, PRT_ID, PRC_amount, PRC_name, PRC_desc,  PRC_is_default, PRC_overrides, PRC_wp_user, PRC_order, PRC_deleted, PRC_parent ) VALUES
521 521
 							(1, 1, '0.00', 'Admission', '', 1, NULL, $user_id, 0, 0, 0);";
522
-                $SQL = apply_filters('FHEE__EE_DMS_4_5_0__insert_default_prices__SQL', $SQL);
523
-                $wpdb->query($SQL);
524
-            }
525
-        }
526
-    }
522
+				$SQL = apply_filters('FHEE__EE_DMS_4_5_0__insert_default_prices__SQL', $SQL);
523
+				$wpdb->query($SQL);
524
+			}
525
+		}
526
+	}
527 527
 
528 528
 
529 529
 
530
-    /**
531
-     * insert DEFAULT ticket
532
-     * Almost identical to EE_DMS_Core_4_3_0::insert_default_tickets, except is aware of the TKT_wp_user field
533
-     *
534
-     * @since 4.5.0
535
-     * @return void
536
-     */
537
-    public function insert_default_tickets()
538
-    {
539
-        global $wpdb;
540
-        $ticket_table = $wpdb->prefix . "esp_ticket";
541
-        if ($this->_get_table_analysis()->tableExists($ticket_table)) {
542
-            $SQL = 'SELECT COUNT(TKT_ID) FROM ' . $ticket_table;
543
-            $tickets_exist = $wpdb->get_var($SQL);
544
-            if (! $tickets_exist) {
545
-                $user_id = EEH_Activation::get_default_creator_id();
546
-                $SQL = "INSERT INTO $ticket_table
530
+	/**
531
+	 * insert DEFAULT ticket
532
+	 * Almost identical to EE_DMS_Core_4_3_0::insert_default_tickets, except is aware of the TKT_wp_user field
533
+	 *
534
+	 * @since 4.5.0
535
+	 * @return void
536
+	 */
537
+	public function insert_default_tickets()
538
+	{
539
+		global $wpdb;
540
+		$ticket_table = $wpdb->prefix . "esp_ticket";
541
+		if ($this->_get_table_analysis()->tableExists($ticket_table)) {
542
+			$SQL = 'SELECT COUNT(TKT_ID) FROM ' . $ticket_table;
543
+			$tickets_exist = $wpdb->get_var($SQL);
544
+			if (! $tickets_exist) {
545
+				$user_id = EEH_Activation::get_default_creator_id();
546
+				$SQL = "INSERT INTO $ticket_table
547 547
 					( TKT_ID, TTM_ID, TKT_name, TKT_description, TKT_qty, TKT_sold, TKT_uses, TKT_required, TKT_min, TKT_max, TKT_price, TKT_start_date, TKT_end_date, TKT_taxable, TKT_order, TKT_row, TKT_is_default, TKT_parent, TKT_wp_user, TKT_deleted ) VALUES
548 548
 					( 1, 0, '"
549
-                       . esc_html__("Free Ticket", "event_espresso")
550
-                       . "', '', 100, 0, -1, 0, 0, -1, 0.00, '0000-00-00 00:00:00', '0000-00-00 00:00:00', 0, 0, 1, 1, 0, $user_id, 0);";
551
-                $SQL = apply_filters('FHEE__EE_DMS_4_5_0__insert_default_tickets__SQL', $SQL);
552
-                $wpdb->query($SQL);
553
-            }
554
-        }
555
-        $ticket_price_table = $wpdb->prefix . "esp_ticket_price";
556
-        if ($this->_get_table_analysis()->tableExists($ticket_price_table)) {
557
-            $SQL = 'SELECT COUNT(TKP_ID) FROM ' . $ticket_price_table;
558
-            $ticket_prc_exist = $wpdb->get_var($SQL);
559
-            if (! $ticket_prc_exist) {
560
-                $SQL = "INSERT INTO $ticket_price_table
549
+					   . esc_html__("Free Ticket", "event_espresso")
550
+					   . "', '', 100, 0, -1, 0, 0, -1, 0.00, '0000-00-00 00:00:00', '0000-00-00 00:00:00', 0, 0, 1, 1, 0, $user_id, 0);";
551
+				$SQL = apply_filters('FHEE__EE_DMS_4_5_0__insert_default_tickets__SQL', $SQL);
552
+				$wpdb->query($SQL);
553
+			}
554
+		}
555
+		$ticket_price_table = $wpdb->prefix . "esp_ticket_price";
556
+		if ($this->_get_table_analysis()->tableExists($ticket_price_table)) {
557
+			$SQL = 'SELECT COUNT(TKP_ID) FROM ' . $ticket_price_table;
558
+			$ticket_prc_exist = $wpdb->get_var($SQL);
559
+			if (! $ticket_prc_exist) {
560
+				$SQL = "INSERT INTO $ticket_price_table
561 561
 				( TKP_ID, TKT_ID, PRC_ID ) VALUES
562 562
 				( 1, 1, 1 )
563 563
 				";
564
-                $SQL = apply_filters('FHEE__EE_DMS_4_5_0__insert_default_tickets__SQL__ticket_price', $SQL);
565
-                $wpdb->query($SQL);
566
-            }
567
-        }
568
-    }
564
+				$SQL = apply_filters('FHEE__EE_DMS_4_5_0__insert_default_tickets__SQL__ticket_price', $SQL);
565
+				$wpdb->query($SQL);
566
+			}
567
+		}
568
+	}
569 569
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Base.model.php 1 patch
Indentation   +6531 added lines, -6531 removed lines patch added patch discarded remove patch
@@ -39,6537 +39,6537 @@
 block discarded – undo
39 39
  */
40 40
 abstract class EEM_Base extends EE_Base implements ResettableInterface
41 41
 {
42
-    /**
43
-     * Flag to indicate whether the values provided to EEM_Base have already been prepared
44
-     * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
45
-     * They almost always WILL NOT, but it's not necessarily a requirement.
46
-     * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
47
-     *
48
-     * @var boolean
49
-     */
50
-    private $_values_already_prepared_by_model_object = 0;
51
-
52
-    /**
53
-     * when $_values_already_prepared_by_model_object equals this, we assume
54
-     * the data is just like form input that needs to have the model fields'
55
-     * prepare_for_set and prepare_for_use_in_db called on it
56
-     */
57
-    const not_prepared_by_model_object = 0;
58
-
59
-    /**
60
-     * when $_values_already_prepared_by_model_object equals this, we
61
-     * assume this value is coming from a model object and doesn't need to have
62
-     * prepare_for_set called on it, just prepare_for_use_in_db is used
63
-     */
64
-    const prepared_by_model_object = 1;
65
-
66
-    /**
67
-     * when $_values_already_prepared_by_model_object equals this, we assume
68
-     * the values are already to be used in the database (ie no processing is done
69
-     * on them by the model's fields)
70
-     */
71
-    const prepared_for_use_in_db = 2;
72
-
73
-
74
-    protected $singular_item = 'Item';
75
-
76
-    protected $plural_item   = 'Items';
77
-
78
-    /**
79
-     * @type EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
80
-     */
81
-    protected $_tables;
82
-
83
-    /**
84
-     * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
85
-     * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
86
-     * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
87
-     *
88
-     * @var EE_Model_Field_Base[][] $_fields
89
-     */
90
-    protected $_fields;
91
-
92
-    /**
93
-     * array of different kinds of relations
94
-     *
95
-     * @var EE_Model_Relation_Base[] $_model_relations
96
-     */
97
-    protected $_model_relations = [];
98
-
99
-    /**
100
-     * @var EE_Index[] $_indexes
101
-     */
102
-    protected $_indexes = array();
103
-
104
-    /**
105
-     * Default strategy for getting where conditions on this model. This strategy is used to get default
106
-     * where conditions which are added to get_all, update, and delete queries. They can be overridden
107
-     * by setting the same columns as used in these queries in the query yourself.
108
-     *
109
-     * @var EE_Default_Where_Conditions
110
-     */
111
-    protected $_default_where_conditions_strategy;
112
-
113
-    /**
114
-     * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
115
-     * This is particularly useful when you want something between 'none' and 'default'
116
-     *
117
-     * @var EE_Default_Where_Conditions
118
-     */
119
-    protected $_minimum_where_conditions_strategy;
120
-
121
-    /**
122
-     * String describing how to find the "owner" of this model's objects.
123
-     * When there is a foreign key on this model to the wp_users table, this isn't needed.
124
-     * But when there isn't, this indicates which related model, or transiently-related model,
125
-     * has the foreign key to the wp_users table.
126
-     * Eg, for EEM_Registration this would be 'Event' because registrations are directly
127
-     * related to events, and events have a foreign key to wp_users.
128
-     * On EEM_Transaction, this would be 'Transaction.Event'
129
-     *
130
-     * @var string
131
-     */
132
-    protected $_model_chain_to_wp_user = '';
133
-
134
-    /**
135
-     * String describing how to find the model with a password controlling access to this model. This property has the
136
-     * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
137
-     * This value is the path of models to follow to arrive at the model with the password field.
138
-     * If it is an empty string, it means this model has the password field. If it is null, it means there is no
139
-     * model with a password that should affect reading this on the front-end.
140
-     * Eg this is an empty string for the Event model because it has a password.
141
-     * This is null for the Registration model, because its event's password has no bearing on whether
142
-     * you can read the registration or not on the front-end (it just depends on your capabilities.)
143
-     * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
144
-     * should hide tickets for datetimes for events that have a password set.
145
-     * @var string |null
146
-     */
147
-    protected $model_chain_to_password = null;
148
-
149
-    /**
150
-     * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
151
-     * don't need it (particularly CPT models)
152
-     *
153
-     * @var bool
154
-     */
155
-    protected $_ignore_where_strategy = false;
156
-
157
-    /**
158
-     * String used in caps relating to this model. Eg, if the caps relating to this
159
-     * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
160
-     *
161
-     * @var string. If null it hasn't been initialized yet. If false then we
162
-     * have indicated capabilities don't apply to this
163
-     */
164
-    protected $_caps_slug = null;
165
-
166
-    /**
167
-     * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
168
-     * and next-level keys are capability names, and each's value is a
169
-     * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
170
-     * they specify which context to use (ie, frontend, backend, edit or delete)
171
-     * and then each capability in the corresponding sub-array that they're missing
172
-     * adds the where conditions onto the query.
173
-     *
174
-     * @var array
175
-     */
176
-    protected $_cap_restrictions = array(
177
-        self::caps_read       => array(),
178
-        self::caps_read_admin => array(),
179
-        self::caps_edit       => array(),
180
-        self::caps_delete     => array(),
181
-    );
182
-
183
-    /**
184
-     * Array defining which cap restriction generators to use to create default
185
-     * cap restrictions to put in EEM_Base::_cap_restrictions.
186
-     * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
187
-     * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
188
-     * automatically set this to false (not just null).
189
-     *
190
-     * @var EE_Restriction_Generator_Base[]
191
-     */
192
-    protected $_cap_restriction_generators = array();
193
-
194
-    /**
195
-     * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
196
-     */
197
-    const caps_read       = 'read';
198
-
199
-    const caps_read_admin = 'read_admin';
200
-
201
-    const caps_edit       = 'edit';
202
-
203
-    const caps_delete     = 'delete';
204
-
205
-    /**
206
-     * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
207
-     * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
208
-     * maps to 'read' because when looking for relevant permissions we're going to use
209
-     * 'read' in teh capabilities names like 'ee_read_events' etc.
210
-     *
211
-     * @var array
212
-     */
213
-    protected $_cap_contexts_to_cap_action_map = array(
214
-        self::caps_read       => 'read',
215
-        self::caps_read_admin => 'read',
216
-        self::caps_edit       => 'edit',
217
-        self::caps_delete     => 'delete',
218
-    );
219
-
220
-    /**
221
-     * Timezone
222
-     * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
223
-     * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
224
-     * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
225
-     * EE_Datetime_Field data type will have access to it.
226
-     *
227
-     * @var string
228
-     */
229
-    protected $_timezone;
230
-
231
-
232
-    /**
233
-     * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
234
-     * multisite.
235
-     *
236
-     * @var int
237
-     */
238
-    protected static $_model_query_blog_id;
239
-
240
-    /**
241
-     * A copy of _fields, except the array keys are the model names pointed to by
242
-     * the field
243
-     *
244
-     * @var EE_Model_Field_Base[]
245
-     */
246
-    private $_cache_foreign_key_to_fields = array();
247
-
248
-    /**
249
-     * Cached list of all the fields on the model, indexed by their name
250
-     *
251
-     * @var EE_Model_Field_Base[]
252
-     */
253
-    private $_cached_fields = null;
254
-
255
-    /**
256
-     * Cached list of all the fields on the model, except those that are
257
-     * marked as only pertinent to the database
258
-     *
259
-     * @var EE_Model_Field_Base[]
260
-     */
261
-    private $_cached_fields_non_db_only = null;
262
-
263
-    /**
264
-     * A cached reference to the primary key for quick lookup
265
-     *
266
-     * @var EE_Model_Field_Base
267
-     */
268
-    private $_primary_key_field = null;
269
-
270
-    /**
271
-     * Flag indicating whether this model has a primary key or not
272
-     *
273
-     * @var boolean
274
-     */
275
-    protected $_has_primary_key_field = null;
276
-
277
-    /**
278
-     * array in the format:  [ FK alias => full PK ]
279
-     * where keys are local column name aliases for foreign keys
280
-     * and values are the fully qualified column name for the primary key they represent
281
-     *  ex:
282
-     *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
283
-     *
284
-     * @var array $foreign_key_aliases
285
-     */
286
-    protected $foreign_key_aliases = [];
287
-
288
-    /**
289
-     * Whether or not this model is based off a table in WP core only (CPTs should set
290
-     * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
291
-     * This should be true for models that deal with data that should exist independent of EE.
292
-     * For example, if the model can read and insert data that isn't used by EE, this should be true.
293
-     * It would be false, however, if you could guarantee the model would only interact with EE data,
294
-     * even if it uses a WP core table (eg event and venue models set this to false for that reason:
295
-     * they can only read and insert events and venues custom post types, not arbitrary post types)
296
-     * @var boolean
297
-     */
298
-    protected $_wp_core_model = false;
299
-
300
-    /**
301
-     * @var bool stores whether this model has a password field or not.
302
-     * null until initialized by hasPasswordField()
303
-     */
304
-    protected $has_password_field;
305
-
306
-    /**
307
-     * @var EE_Password_Field|null Automatically set when calling getPasswordField()
308
-     */
309
-    protected $password_field;
310
-
311
-    /**
312
-     *    List of valid operators that can be used for querying.
313
-     * The keys are all operators we'll accept, the values are the real SQL
314
-     * operators used
315
-     *
316
-     * @var array
317
-     */
318
-    protected $_valid_operators = array(
319
-        '='           => '=',
320
-        '<='          => '<=',
321
-        '<'           => '<',
322
-        '>='          => '>=',
323
-        '>'           => '>',
324
-        '!='          => '!=',
325
-        'LIKE'        => 'LIKE',
326
-        'like'        => 'LIKE',
327
-        'NOT_LIKE'    => 'NOT LIKE',
328
-        'not_like'    => 'NOT LIKE',
329
-        'NOT LIKE'    => 'NOT LIKE',
330
-        'not like'    => 'NOT LIKE',
331
-        'IN'          => 'IN',
332
-        'in'          => 'IN',
333
-        'NOT_IN'      => 'NOT IN',
334
-        'not_in'      => 'NOT IN',
335
-        'NOT IN'      => 'NOT IN',
336
-        'not in'      => 'NOT IN',
337
-        'between'     => 'BETWEEN',
338
-        'BETWEEN'     => 'BETWEEN',
339
-        'IS_NOT_NULL' => 'IS NOT NULL',
340
-        'is_not_null' => 'IS NOT NULL',
341
-        'IS NOT NULL' => 'IS NOT NULL',
342
-        'is not null' => 'IS NOT NULL',
343
-        'IS_NULL'     => 'IS NULL',
344
-        'is_null'     => 'IS NULL',
345
-        'IS NULL'     => 'IS NULL',
346
-        'is null'     => 'IS NULL',
347
-        'REGEXP'      => 'REGEXP',
348
-        'regexp'      => 'REGEXP',
349
-        'NOT_REGEXP'  => 'NOT REGEXP',
350
-        'not_regexp'  => 'NOT REGEXP',
351
-        'NOT REGEXP'  => 'NOT REGEXP',
352
-        'not regexp'  => 'NOT REGEXP',
353
-    );
354
-
355
-    /**
356
-     * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
357
-     *
358
-     * @var array
359
-     */
360
-    protected $_in_style_operators = array('IN', 'NOT IN');
361
-
362
-    /**
363
-     * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
364
-     * '12-31-2012'"
365
-     *
366
-     * @var array
367
-     */
368
-    protected $_between_style_operators = array('BETWEEN');
369
-
370
-    /**
371
-     * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
372
-     * @var array
373
-     */
374
-    protected $_like_style_operators = array('LIKE', 'NOT LIKE');
375
-    /**
376
-     * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
377
-     * on a join table.
378
-     *
379
-     * @var array
380
-     */
381
-    protected $_null_style_operators = array('IS NOT NULL', 'IS NULL');
382
-
383
-    /**
384
-     * Allowed values for $query_params['order'] for ordering in queries
385
-     *
386
-     * @var array
387
-     */
388
-    protected $_allowed_order_values = array('asc', 'desc', 'ASC', 'DESC');
389
-
390
-    /**
391
-     * When these are keys in a WHERE or HAVING clause, they are handled much differently
392
-     * than regular field names. It is assumed that their values are an array of WHERE conditions
393
-     *
394
-     * @var array
395
-     */
396
-    private $_logic_query_param_keys = array('not', 'and', 'or', 'NOT', 'AND', 'OR');
397
-
398
-    /**
399
-     * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
400
-     * 'where', but 'where' clauses are so common that we thought we'd omit it
401
-     *
402
-     * @var array
403
-     */
404
-    private $_allowed_query_params = array(
405
-        0,
406
-        'limit',
407
-        'order_by',
408
-        'group_by',
409
-        'having',
410
-        'force_join',
411
-        'order',
412
-        'on_join_limit',
413
-        'default_where_conditions',
414
-        'caps',
415
-        'extra_selects',
416
-        'exclude_protected',
417
-    );
418
-
419
-    /**
420
-     * All the data types that can be used in $wpdb->prepare statements.
421
-     *
422
-     * @var array
423
-     */
424
-    private $_valid_wpdb_data_types = array('%d', '%s', '%f');
425
-
426
-    /**
427
-     * @var EE_Registry $EE
428
-     */
429
-    protected $EE = null;
430
-
431
-
432
-    /**
433
-     * Property which, when set, will have this model echo out the next X queries to the page for debugging.
434
-     *
435
-     * @var int
436
-     */
437
-    protected $_show_next_x_db_queries = 0;
438
-
439
-    /**
440
-     * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
441
-     * it gets saved on this property as an instance of CustomSelects so those selections can be used in
442
-     * WHERE, GROUP_BY, etc.
443
-     *
444
-     * @var CustomSelects
445
-     */
446
-    protected $_custom_selections = array();
447
-
448
-    /**
449
-     * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
450
-     * caches every model object we've fetched from the DB on this request
451
-     *
452
-     * @var array
453
-     */
454
-    protected $_entity_map;
455
-
456
-    /**
457
-     * @var LoaderInterface
458
-     */
459
-    protected static $loader;
460
-
461
-    /**
462
-     * @var Mirror
463
-     */
464
-    private static $mirror;
465
-
466
-
467
-
468
-    /**
469
-     * constant used to show EEM_Base has not yet verified the db on this http request
470
-     */
471
-    const db_verified_none = 0;
472
-
473
-    /**
474
-     * constant used to show EEM_Base has verified the EE core db on this http request,
475
-     * but not the addons' dbs
476
-     */
477
-    const db_verified_core = 1;
478
-
479
-    /**
480
-     * constant used to show EEM_Base has verified the addons' dbs (and implicitly
481
-     * the EE core db too)
482
-     */
483
-    const db_verified_addons = 2;
484
-
485
-    /**
486
-     * indicates whether an EEM_Base child has already re-verified the DB
487
-     * is ok (we don't want to do it repetitively). Should be set to one the constants
488
-     * looking like EEM_Base::db_verified_*
489
-     *
490
-     * @var int - 0 = none, 1 = core, 2 = addons
491
-     */
492
-    protected static $_db_verification_level = EEM_Base::db_verified_none;
493
-
494
-    /**
495
-     * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
496
-     *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
497
-     *        registrations for non-trashed tickets for non-trashed datetimes)
498
-     */
499
-    const default_where_conditions_all = 'all';
500
-
501
-    /**
502
-     * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
503
-     *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
504
-     *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
505
-     *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
506
-     *        models which share tables with other models, this can return data for the wrong model.
507
-     */
508
-    const default_where_conditions_this_only = 'this_model_only';
509
-
510
-    /**
511
-     * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
512
-     *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
513
-     *        return all registrations related to non-trashed tickets and non-trashed datetimes)
514
-     */
515
-    const default_where_conditions_others_only = 'other_models_only';
516
-
517
-    /**
518
-     * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
519
-     *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
520
-     *        their table with other models, like the Event and Venue models. For example, when querying for events
521
-     *        ordered by their venues' name, this will be sure to only return real events with associated real venues
522
-     *        (regardless of whether those events and venues are trashed)
523
-     *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
524
-     *        events.
525
-     */
526
-    const default_where_conditions_minimum_all = 'minimum';
527
-
528
-    /**
529
-     * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
530
-     *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
531
-     *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
532
-     *        not)
533
-     */
534
-    const default_where_conditions_minimum_others = 'full_this_minimum_others';
535
-
536
-    /**
537
-     * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
538
-     *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
539
-     *        it's possible it will return table entries for other models. You should use
540
-     *        EEM_Base::default_where_conditions_minimum_all instead.
541
-     */
542
-    const default_where_conditions_none = 'none';
543
-
544
-
545
-
546
-    /**
547
-     * About all child constructors:
548
-     * they should define the _tables, _fields and _model_relations arrays.
549
-     * Should ALWAYS be called after child constructor.
550
-     * In order to make the child constructors to be as simple as possible, this parent constructor
551
-     * finalizes constructing all the object's attributes.
552
-     * Generally, rather than requiring a child to code
553
-     * $this->_tables = array(
554
-     *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
555
-     *        ...);
556
-     *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
557
-     * each EE_Table has a function to set the table's alias after the constructor, using
558
-     * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
559
-     * do something similar.
560
-     *
561
-     * @param null $timezone
562
-     * @throws EE_Error
563
-     */
564
-    protected function __construct($timezone = null)
565
-    {
566
-        // check that the model has not been loaded too soon
567
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
568
-            throw new EE_Error(
569
-                sprintf(
570
-                    esc_html__(
571
-                        'The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
572
-                        'event_espresso'
573
-                    ),
574
-                    get_class($this)
575
-                )
576
-            );
577
-        }
578
-        /**
579
-         * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
580
-         */
581
-        if (empty(EEM_Base::$_model_query_blog_id)) {
582
-            EEM_Base::set_model_query_blog_id();
583
-        }
584
-        /**
585
-         * Filters the list of tables on a model. It is best to NOT use this directly and instead
586
-         * just use EE_Register_Model_Extension
587
-         *
588
-         * @var EE_Table_Base[] $_tables
589
-         */
590
-        $this->_tables = (array) apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
591
-        foreach ($this->_tables as $table_alias => $table_obj) {
592
-            /** @var $table_obj EE_Table_Base */
593
-            $table_obj->_construct_finalize_with_alias($table_alias);
594
-            if ($table_obj instanceof EE_Secondary_Table) {
595
-                /** @var $table_obj EE_Secondary_Table */
596
-                $table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
597
-            }
598
-        }
599
-        /**
600
-         * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
601
-         * EE_Register_Model_Extension
602
-         *
603
-         * @param EE_Model_Field_Base[] $_fields
604
-         */
605
-        $this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
606
-        $this->_invalidate_field_caches();
607
-        foreach ($this->_fields as $table_alias => $fields_for_table) {
608
-            if (! array_key_exists($table_alias, $this->_tables)) {
609
-                throw new EE_Error(sprintf(esc_html__(
610
-                    "Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
611
-                    'event_espresso'
612
-                ), $table_alias, implode(",", $this->_fields)));
613
-            }
614
-            foreach ($fields_for_table as $field_name => $field_obj) {
615
-                /** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
616
-                // primary key field base has a slightly different _construct_finalize
617
-                /** @var $field_obj EE_Model_Field_Base */
618
-                $field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
619
-            }
620
-        }
621
-        // everything is related to Extra_Meta
622
-        if (get_class($this) !== 'EEM_Extra_Meta') {
623
-            // make extra meta related to everything, but don't block deleting things just
624
-            // because they have related extra meta info. For now just orphan those extra meta
625
-            // in the future we should automatically delete them
626
-            $this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
627
-        }
628
-        // and change logs
629
-        if (get_class($this) !== 'EEM_Change_Log') {
630
-            $this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
631
-        }
632
-        /**
633
-         * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
634
-         * EE_Register_Model_Extension
635
-         *
636
-         * @param EE_Model_Relation_Base[] $_model_relations
637
-         */
638
-        $this->_model_relations = (array) apply_filters(
639
-            'FHEE__' . get_class($this) . '__construct__model_relations',
640
-            $this->_model_relations
641
-        );
642
-        foreach ($this->_model_relations as $model_name => $relation_obj) {
643
-            /** @var $relation_obj EE_Model_Relation_Base */
644
-            $relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
645
-        }
646
-        foreach ($this->_indexes as $index_name => $index_obj) {
647
-            $index_obj->_construct_finalize($index_name, $this->get_this_model_name());
648
-        }
649
-        $this->set_timezone($timezone);
650
-        // finalize default where condition strategy, or set default
651
-        if (! $this->_default_where_conditions_strategy) {
652
-            // nothing was set during child constructor, so set default
653
-            $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
654
-        }
655
-        $this->_default_where_conditions_strategy->_finalize_construct($this);
656
-        if (! $this->_minimum_where_conditions_strategy) {
657
-            // nothing was set during child constructor, so set default
658
-            $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
659
-        }
660
-        $this->_minimum_where_conditions_strategy->_finalize_construct($this);
661
-        // if the cap slug hasn't been set, and we haven't set it to false on purpose
662
-        // to indicate to NOT set it, set it to the logical default
663
-        if ($this->_caps_slug === null) {
664
-            $this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
665
-        }
666
-        // initialize the standard cap restriction generators if none were specified by the child constructor
667
-        if (is_array($this->_cap_restriction_generators)) {
668
-            foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
669
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
670
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
671
-                        'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
672
-                        new EE_Restriction_Generator_Protected(),
673
-                        $cap_context,
674
-                        $this
675
-                    );
676
-                }
677
-            }
678
-        }
679
-        // if there are cap restriction generators, use them to make the default cap restrictions
680
-        if (is_array($this->_cap_restriction_generators)) {
681
-            foreach ($this->_cap_restriction_generators as $context => $generator_object) {
682
-                if (! $generator_object) {
683
-                    continue;
684
-                }
685
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
686
-                    throw new EE_Error(
687
-                        sprintf(
688
-                            esc_html__(
689
-                                'Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
690
-                                'event_espresso'
691
-                            ),
692
-                            $context,
693
-                            $this->get_this_model_name()
694
-                        )
695
-                    );
696
-                }
697
-                $action = $this->cap_action_for_context($context);
698
-                if (! $generator_object->construction_finalized()) {
699
-                    $generator_object->_construct_finalize($this, $action);
700
-                }
701
-            }
702
-        }
703
-        do_action('AHEE__' . get_class($this) . '__construct__end');
704
-    }
705
-
706
-
707
-    /**
708
-     * @return LoaderInterface
709
-     * @throws InvalidArgumentException
710
-     * @throws InvalidDataTypeException
711
-     * @throws InvalidInterfaceException
712
-     */
713
-    protected static function getLoader(): LoaderInterface
714
-    {
715
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
716
-            EEM_Base::$loader = LoaderFactory::getLoader();
717
-        }
718
-        return EEM_Base::$loader;
719
-    }
720
-
721
-
722
-    /**
723
-     * @return Mirror
724
-     * @since   $VID:$
725
-     */
726
-    private static function getMirror(): Mirror
727
-    {
728
-        if (! EEM_Base::$mirror instanceof Mirror) {
729
-            EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
730
-        }
731
-        return EEM_Base::$mirror;
732
-    }
733
-
734
-
735
-    /**
736
-     * @param string $model_class_Name
737
-     * @param string $timezone
738
-     * @return array
739
-     * @throws ReflectionException
740
-     * @since   $VID:$
741
-     */
742
-    private static function getModelArguments(string $model_class_Name, string $timezone): array
743
-    {
744
-        $arguments = [$timezone];
745
-        $params    = EEM_Base::getMirror()->getParameters($model_class_Name);
746
-        if (count($params) > 1) {
747
-            if ($params[1]->getName() === 'model_field_factory') {
748
-                $arguments = [
749
-                    $timezone,
750
-                    EEM_Base::getLoader()->getShared(ModelFieldFactory::class)
751
-                ];
752
-            } elseif ($model_class_Name === 'EEM_Form_Section') {
753
-                $arguments = [
754
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
755
-                    $timezone
756
-                ];
757
-            } elseif ($model_class_Name === 'EEM_Form_Element') {
758
-                $arguments = [
759
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
760
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
761
-                    $timezone,
762
-                ];
763
-            }
764
-        }
765
-        return $arguments;
766
-    }
767
-
768
-
769
-    /**
770
-     * This function is a singleton method used to instantiate the Espresso_model object
771
-     *
772
-     * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
773
-     *                                (and any incoming timezone data that gets saved).
774
-     *                                Note this just sends the timezone info to the date time model field objects.
775
-     *                                Default is NULL
776
-     *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
777
-     * @return static (as in the concrete child class)
778
-     * @throws EE_Error
779
-     * @throws ReflectionException
780
-     */
781
-    public static function instance($timezone = null)
782
-    {
783
-        // check if instance of Espresso_model already exists
784
-        if (! static::$_instance instanceof static) {
785
-            $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
786
-            $model = new static(...$arguments);
787
-            EEM_Base::getLoader()->share(static::class, $model, $arguments);
788
-            static::$_instance = $model;
789
-        }
790
-        // we might have a timezone set, let set_timezone decide what to do with it
791
-        if ($timezone) {
792
-            static::$_instance->set_timezone($timezone);
793
-        }
794
-        // Espresso_model object
795
-        return static::$_instance;
796
-    }
797
-
798
-
799
-
800
-    /**
801
-     * resets the model and returns it
802
-     *
803
-     * @param string|null $timezone
804
-     * @return EEM_Base|null (if the model was already instantiated, returns it, with
805
-     * all its properties reset; if it wasn't instantiated, returns null)
806
-     * @throws EE_Error
807
-     * @throws ReflectionException
808
-     * @throws InvalidArgumentException
809
-     * @throws InvalidDataTypeException
810
-     * @throws InvalidInterfaceException
811
-     */
812
-    public static function reset($timezone = null)
813
-    {
814
-        if (! static::$_instance instanceof EEM_Base) {
815
-            return null;
816
-        }
817
-        // Let's NOT swap out the current instance for a new one
818
-        // because if someone has a reference to it, we can't remove their reference.
819
-        // It's best to keep using the same reference but change the original object instead,
820
-        // so reset all its properties to their original values as defined in the class.
821
-        $static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
822
-        foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
823
-            // don't set instance to null like it was originally,
824
-            // but it's static anyways, and we're ignoring static properties (for now at least)
825
-            if (! isset($static_properties[ $property ])) {
826
-                static::$_instance->{$property} = $value;
827
-            }
828
-        }
829
-        // and then directly call its constructor again, like we would if we were creating a new one
830
-        $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
831
-        static::$_instance->__construct(...$arguments);
832
-        return self::instance();
833
-    }
834
-
835
-
836
-    /**
837
-     * Used to set the $_model_query_blog_id static property.
838
-     *
839
-     * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
840
-     *                      value for get_current_blog_id() will be used.
841
-     */
842
-    public static function set_model_query_blog_id($blog_id = 0)
843
-    {
844
-        EEM_Base::$_model_query_blog_id = $blog_id > 0
845
-            ? (int) $blog_id
846
-            : get_current_blog_id();
847
-    }
848
-
849
-
850
-    /**
851
-     * Returns whatever is set as the internal $model_query_blog_id.
852
-     *
853
-     * @return int
854
-     */
855
-    public static function get_model_query_blog_id()
856
-    {
857
-        return EEM_Base::$_model_query_blog_id;
858
-    }
859
-
860
-
861
-
862
-    /**
863
-     * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
864
-     *
865
-     * @param  boolean $translated return localized strings or JUST the array.
866
-     * @return array
867
-     * @throws EE_Error
868
-     * @throws InvalidArgumentException
869
-     * @throws InvalidDataTypeException
870
-     * @throws InvalidInterfaceException
871
-     */
872
-    public function status_array($translated = false)
873
-    {
874
-        if (! array_key_exists('Status', $this->_model_relations)) {
875
-            return array();
876
-        }
877
-        $model_name = $this->get_this_model_name();
878
-        $status_type = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
879
-        $stati = EEM_Status::instance()->get_all(array(array('STS_type' => $status_type)));
880
-        $status_array = array();
881
-        foreach ($stati as $status) {
882
-            $status_array[ $status->ID() ] = $status->get('STS_code');
883
-        }
884
-        return $translated
885
-            ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
886
-            : $status_array;
887
-    }
888
-
889
-
890
-
891
-    /**
892
-     * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
893
-     *
894
-     * @param array $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
895
-     *                             or if you have the development copy of EE you can view this at the path:
896
-     *                             /docs/G--Model-System/model-query-params.md
897
-     * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
898
-     *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object IDs (if there is a primary key on the model.
899
-     *                                        if not, numerically indexed) Some full examples: get 10 transactions
900
-     *                                        which have Scottish attendees: EEM_Transaction::instance()->get_all(
901
-     *                                        array( array(
902
-     *                                        'OR'=>array(
903
-     *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
904
-     *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
905
-     *                                        )
906
-     *                                        ),
907
-     *                                        'limit'=>10,
908
-     *                                        'group_by'=>'TXN_ID'
909
-     *                                        ));
910
-     *                                        get all the answers to the question titled "shirt size" for event with id
911
-     *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
912
-     *                                        'Question.QST_display_text'=>'shirt size',
913
-     *                                        'Registration.Event.EVT_ID'=>12
914
-     *                                        ),
915
-     *                                        'order_by'=>array('ANS_value'=>'ASC')
916
-     *                                        ));
917
-     * @throws EE_Error
918
-     */
919
-    public function get_all($query_params = array())
920
-    {
921
-        if (
922
-            isset($query_params['limit'])
923
-            && ! isset($query_params['group_by'])
924
-        ) {
925
-            $query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
926
-        }
927
-        return $this->_create_objects($this->_get_all_wpdb_results($query_params));
928
-    }
929
-
930
-
931
-
932
-    /**
933
-     * Modifies the query parameters so we only get back model objects
934
-     * that "belong" to the current user
935
-     *
936
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
937
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
938
-     */
939
-    public function alter_query_params_to_only_include_mine($query_params = array())
940
-    {
941
-        $wp_user_field_name = $this->wp_user_field_name();
942
-        if ($wp_user_field_name) {
943
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
944
-        }
945
-        return $query_params;
946
-    }
947
-
948
-
949
-
950
-    /**
951
-     * Returns the name of the field's name that points to the WP_User table
952
-     *  on this model (or follows the _model_chain_to_wp_user and uses that model's
953
-     * foreign key to the WP_User table)
954
-     *
955
-     * @return string|boolean string on success, boolean false when there is no
956
-     * foreign key to the WP_User table
957
-     */
958
-    public function wp_user_field_name()
959
-    {
960
-        try {
961
-            if (! empty($this->_model_chain_to_wp_user)) {
962
-                $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
963
-                $last_model_name = end($models_to_follow_to_wp_users);
964
-                $model_with_fk_to_wp_users = EE_Registry::instance()->load_model($last_model_name);
965
-                $model_chain_to_wp_user = $this->_model_chain_to_wp_user . '.';
966
-            } else {
967
-                $model_with_fk_to_wp_users = $this;
968
-                $model_chain_to_wp_user = '';
969
-            }
970
-            $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
971
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
972
-        } catch (EE_Error $e) {
973
-            return false;
974
-        }
975
-    }
976
-
977
-
978
-
979
-    /**
980
-     * Returns the _model_chain_to_wp_user string, which indicates which related model
981
-     * (or transiently-related model) has a foreign key to the wp_users table;
982
-     * useful for finding if model objects of this type are 'owned' by the current user.
983
-     * This is an empty string when the foreign key is on this model and when it isn't,
984
-     * but is only non-empty when this model's ownership is indicated by a RELATED model
985
-     * (or transiently-related model)
986
-     *
987
-     * @return string
988
-     */
989
-    public function model_chain_to_wp_user()
990
-    {
991
-        return $this->_model_chain_to_wp_user;
992
-    }
993
-
994
-
995
-
996
-    /**
997
-     * Whether this model is 'owned' by a specific wordpress user (even indirectly,
998
-     * like how registrations don't have a foreign key to wp_users, but the
999
-     * events they are for are), or is unrelated to wp users.
1000
-     * generally available
1001
-     *
1002
-     * @return boolean
1003
-     */
1004
-    public function is_owned()
1005
-    {
1006
-        if ($this->model_chain_to_wp_user()) {
1007
-            return true;
1008
-        }
1009
-        try {
1010
-            $this->get_foreign_key_to('WP_User');
1011
-            return true;
1012
-        } catch (EE_Error $e) {
1013
-            return false;
1014
-        }
1015
-    }
1016
-
1017
-
1018
-    /**
1019
-     * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1020
-     * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1021
-     * the model)
1022
-     *
1023
-     * @param array  $query_params      @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1024
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1025
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1026
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1027
-     *                                  override this and set the select to "*", or a specific column name, like
1028
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1029
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1030
-     *                                  the aliases used to refer to this selection, and values are to be
1031
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1032
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1033
-     * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1034
-     * @throws EE_Error
1035
-     * @throws InvalidArgumentException
1036
-     */
1037
-    protected function _get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1038
-    {
1039
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1040
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1041
-        $select_expressions = $columns_to_select === null
1042
-            ? $this->_construct_default_select_sql($model_query_info)
1043
-            : '';
1044
-        if ($this->_custom_selections instanceof CustomSelects) {
1045
-            $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1046
-            $select_expressions .= $select_expressions
1047
-                ? ', ' . $custom_expressions
1048
-                : $custom_expressions;
1049
-        }
1050
-
1051
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1052
-        return $this->_do_wpdb_query('get_results', array($SQL, $output));
1053
-    }
1054
-
1055
-
1056
-    /**
1057
-     * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1058
-     * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1059
-     * method of including extra select information.
1060
-     *
1061
-     * @param array             $query_params
1062
-     * @param null|array|string $columns_to_select
1063
-     * @return null|CustomSelects
1064
-     * @throws InvalidArgumentException
1065
-     */
1066
-    protected function getCustomSelection(array $query_params, $columns_to_select = null)
1067
-    {
1068
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1069
-            return null;
1070
-        }
1071
-        $selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1072
-        $selects = is_string($selects) ? explode(',', $selects) : $selects;
1073
-        return new CustomSelects($selects);
1074
-    }
1075
-
1076
-
1077
-
1078
-    /**
1079
-     * Gets an array of rows from the database just like $wpdb->get_results would,
1080
-     * but you can use the model query params to more easily
1081
-     * take care of joins, field preparation etc.
1082
-     *
1083
-     * @param array  $query_params      @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1084
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1085
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1086
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1087
-     *                                  override this and set the select to "*", or a specific column name, like
1088
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1089
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1090
-     *                                  the aliases used to refer to this selection, and values are to be
1091
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1092
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1093
-     * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1094
-     * @throws EE_Error
1095
-     */
1096
-    public function get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1097
-    {
1098
-        return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1099
-    }
1100
-
1101
-
1102
-
1103
-    /**
1104
-     * For creating a custom select statement
1105
-     *
1106
-     * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1107
-     *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1108
-     *                                 SQL, and 1=>is the datatype
1109
-     * @throws EE_Error
1110
-     * @return string
1111
-     */
1112
-    private function _construct_select_from_input($columns_to_select)
1113
-    {
1114
-        if (is_array($columns_to_select)) {
1115
-            $select_sql_array = array();
1116
-            foreach ($columns_to_select as $alias => $selection_and_datatype) {
1117
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1118
-                    throw new EE_Error(
1119
-                        sprintf(
1120
-                            esc_html__(
1121
-                                "Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1122
-                                'event_espresso'
1123
-                            ),
1124
-                            $selection_and_datatype,
1125
-                            $alias
1126
-                        )
1127
-                    );
1128
-                }
1129
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1130
-                    throw new EE_Error(
1131
-                        sprintf(
1132
-                            esc_html__(
1133
-                                "Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1134
-                                'event_espresso'
1135
-                            ),
1136
-                            $selection_and_datatype[1],
1137
-                            $selection_and_datatype[0],
1138
-                            $alias,
1139
-                            implode(', ', $this->_valid_wpdb_data_types)
1140
-                        )
1141
-                    );
1142
-                }
1143
-                $select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1144
-            }
1145
-            $columns_to_select_string = implode(', ', $select_sql_array);
1146
-        } else {
1147
-            $columns_to_select_string = $columns_to_select;
1148
-        }
1149
-        return $columns_to_select_string;
1150
-    }
1151
-
1152
-
1153
-
1154
-    /**
1155
-     * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1156
-     *
1157
-     * @return string
1158
-     * @throws EE_Error
1159
-     */
1160
-    public function primary_key_name()
1161
-    {
1162
-        return $this->get_primary_key_field()->get_name();
1163
-    }
1164
-
1165
-
1166
-    /**
1167
-     * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1168
-     * If there is no primary key on this model, $id is treated as primary key string
1169
-     *
1170
-     * @param mixed $id int or string, depending on the type of the model's primary key
1171
-     * @return EE_Base_Class|mixed|null
1172
-     * @throws EE_Error
1173
-     */
1174
-    public function get_one_by_ID($id)
1175
-    {
1176
-        if ($this->get_from_entity_map($id)) {
1177
-            return $this->get_from_entity_map($id);
1178
-        }
1179
-        $model_object = $this->get_one(
1180
-            $this->alter_query_params_to_restrict_by_ID(
1181
-                $id,
1182
-                array('default_where_conditions' => EEM_Base::default_where_conditions_minimum_all)
1183
-            )
1184
-        );
1185
-        $className = $this->_get_class_name();
1186
-        if ($model_object instanceof $className) {
1187
-            // make sure valid objects get added to the entity map
1188
-            // so that the next call to this method doesn't trigger another trip to the db
1189
-            $this->add_to_entity_map($model_object);
1190
-        }
1191
-        return $model_object;
1192
-    }
1193
-
1194
-
1195
-
1196
-    /**
1197
-     * Alters query parameters to only get items with this ID are returned.
1198
-     * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1199
-     * or could just be a simple primary key ID
1200
-     *
1201
-     * @param int   $id
1202
-     * @param array $query_params
1203
-     * @return array of normal query params, @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1204
-     * @throws EE_Error
1205
-     */
1206
-    public function alter_query_params_to_restrict_by_ID($id, $query_params = array())
1207
-    {
1208
-        if (! isset($query_params[0])) {
1209
-            $query_params[0] = array();
1210
-        }
1211
-        $conditions_from_id = $this->parse_index_primary_key_string($id);
1212
-        if ($conditions_from_id === null) {
1213
-            $query_params[0][ $this->primary_key_name() ] = $id;
1214
-        } else {
1215
-            // no primary key, so the $id must be from the get_index_primary_key_string()
1216
-            $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1217
-        }
1218
-        return $query_params;
1219
-    }
1220
-
1221
-
1222
-
1223
-    /**
1224
-     * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1225
-     * array. If no item is found, null is returned.
1226
-     *
1227
-     * @param array $query_params like EEM_Base's $query_params variable.
1228
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1229
-     * @throws EE_Error
1230
-     */
1231
-    public function get_one($query_params = array())
1232
-    {
1233
-        if (! is_array($query_params)) {
1234
-            EE_Error::doing_it_wrong(
1235
-                'EEM_Base::get_one',
1236
-                sprintf(
1237
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1238
-                    gettype($query_params)
1239
-                ),
1240
-                '4.6.0'
1241
-            );
1242
-            $query_params = array();
1243
-        }
1244
-        $query_params['limit'] = 1;
1245
-        $items = $this->get_all($query_params);
1246
-        if (empty($items)) {
1247
-            return null;
1248
-        }
1249
-        return array_shift($items);
1250
-    }
1251
-
1252
-
1253
-
1254
-    /**
1255
-     * Returns the next x number of items in sequence from the given value as
1256
-     * found in the database matching the given query conditions.
1257
-     *
1258
-     * @param mixed $current_field_value    Value used for the reference point.
1259
-     * @param null  $field_to_order_by      What field is used for the
1260
-     *                                      reference point.
1261
-     * @param int   $limit                  How many to return.
1262
-     * @param array $query_params           Extra conditions on the query.
1263
-     * @param null  $columns_to_select      If left null, then an array of
1264
-     *                                      EE_Base_Class objects is returned,
1265
-     *                                      otherwise you can indicate just the
1266
-     *                                      columns you want returned.
1267
-     * @return EE_Base_Class[]|array
1268
-     * @throws EE_Error
1269
-     */
1270
-    public function next_x(
1271
-        $current_field_value,
1272
-        $field_to_order_by = null,
1273
-        $limit = 1,
1274
-        $query_params = array(),
1275
-        $columns_to_select = null
1276
-    ) {
1277
-        return $this->_get_consecutive(
1278
-            $current_field_value,
1279
-            '>',
1280
-            $field_to_order_by,
1281
-            $limit,
1282
-            $query_params,
1283
-            $columns_to_select
1284
-        );
1285
-    }
1286
-
1287
-
1288
-
1289
-    /**
1290
-     * Returns the previous x number of items in sequence from the given value
1291
-     * as found in the database matching the given query conditions.
1292
-     *
1293
-     * @param mixed $current_field_value    Value used for the reference point.
1294
-     * @param null  $field_to_order_by      What field is used for the
1295
-     *                                      reference point.
1296
-     * @param int   $limit                  How many to return.
1297
-     * @param array $query_params           Extra conditions on the query.
1298
-     * @param null  $columns_to_select      If left null, then an array of
1299
-     *                                      EE_Base_Class objects is returned,
1300
-     *                                      otherwise you can indicate just the
1301
-     *                                      columns you want returned.
1302
-     * @return EE_Base_Class[]|array
1303
-     * @throws EE_Error
1304
-     */
1305
-    public function previous_x(
1306
-        $current_field_value,
1307
-        $field_to_order_by = null,
1308
-        $limit = 1,
1309
-        $query_params = array(),
1310
-        $columns_to_select = null
1311
-    ) {
1312
-        return $this->_get_consecutive(
1313
-            $current_field_value,
1314
-            '<',
1315
-            $field_to_order_by,
1316
-            $limit,
1317
-            $query_params,
1318
-            $columns_to_select
1319
-        );
1320
-    }
1321
-
1322
-
1323
-
1324
-    /**
1325
-     * Returns the next item in sequence from the given value as found in the
1326
-     * database matching the given query conditions.
1327
-     *
1328
-     * @param mixed $current_field_value    Value used for the reference point.
1329
-     * @param null  $field_to_order_by      What field is used for the
1330
-     *                                      reference point.
1331
-     * @param array $query_params           Extra conditions on the query.
1332
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1333
-     *                                      object is returned, otherwise you
1334
-     *                                      can indicate just the columns you
1335
-     *                                      want and a single array indexed by
1336
-     *                                      the columns will be returned.
1337
-     * @return EE_Base_Class|null|array()
1338
-     * @throws EE_Error
1339
-     */
1340
-    public function next(
1341
-        $current_field_value,
1342
-        $field_to_order_by = null,
1343
-        $query_params = array(),
1344
-        $columns_to_select = null
1345
-    ) {
1346
-        $results = $this->_get_consecutive(
1347
-            $current_field_value,
1348
-            '>',
1349
-            $field_to_order_by,
1350
-            1,
1351
-            $query_params,
1352
-            $columns_to_select
1353
-        );
1354
-        return empty($results) ? null : reset($results);
1355
-    }
1356
-
1357
-
1358
-
1359
-    /**
1360
-     * Returns the previous item in sequence from the given value as found in
1361
-     * the database matching the given query conditions.
1362
-     *
1363
-     * @param mixed $current_field_value    Value used for the reference point.
1364
-     * @param null  $field_to_order_by      What field is used for the
1365
-     *                                      reference point.
1366
-     * @param array $query_params           Extra conditions on the query.
1367
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1368
-     *                                      object is returned, otherwise you
1369
-     *                                      can indicate just the columns you
1370
-     *                                      want and a single array indexed by
1371
-     *                                      the columns will be returned.
1372
-     * @return EE_Base_Class|null|array()
1373
-     * @throws EE_Error
1374
-     */
1375
-    public function previous(
1376
-        $current_field_value,
1377
-        $field_to_order_by = null,
1378
-        $query_params = array(),
1379
-        $columns_to_select = null
1380
-    ) {
1381
-        $results = $this->_get_consecutive(
1382
-            $current_field_value,
1383
-            '<',
1384
-            $field_to_order_by,
1385
-            1,
1386
-            $query_params,
1387
-            $columns_to_select
1388
-        );
1389
-        return empty($results) ? null : reset($results);
1390
-    }
1391
-
1392
-
1393
-
1394
-    /**
1395
-     * Returns the a consecutive number of items in sequence from the given
1396
-     * value as found in the database matching the given query conditions.
1397
-     *
1398
-     * @param mixed  $current_field_value   Value used for the reference point.
1399
-     * @param string $operand               What operand is used for the sequence.
1400
-     * @param string $field_to_order_by     What field is used for the reference point.
1401
-     * @param int    $limit                 How many to return.
1402
-     * @param array  $query_params          Extra conditions on the query.
1403
-     * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1404
-     *                                      otherwise you can indicate just the columns you want returned.
1405
-     * @return EE_Base_Class[]|array
1406
-     * @throws EE_Error
1407
-     */
1408
-    protected function _get_consecutive(
1409
-        $current_field_value,
1410
-        $operand = '>',
1411
-        $field_to_order_by = null,
1412
-        $limit = 1,
1413
-        $query_params = array(),
1414
-        $columns_to_select = null
1415
-    ) {
1416
-        // if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1417
-        if (empty($field_to_order_by)) {
1418
-            if ($this->has_primary_key_field()) {
1419
-                $field_to_order_by = $this->get_primary_key_field()->get_name();
1420
-            } else {
1421
-                if (WP_DEBUG) {
1422
-                    throw new EE_Error(esc_html__(
1423
-                        'EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1424
-                        'event_espresso'
1425
-                    ));
1426
-                }
1427
-                EE_Error::add_error(esc_html__('There was an error with the query.', 'event_espresso'));
1428
-                return array();
1429
-            }
1430
-        }
1431
-        if (! is_array($query_params)) {
1432
-            EE_Error::doing_it_wrong(
1433
-                'EEM_Base::_get_consecutive',
1434
-                sprintf(
1435
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1436
-                    gettype($query_params)
1437
-                ),
1438
-                '4.6.0'
1439
-            );
1440
-            $query_params = array();
1441
-        }
1442
-        // let's add the where query param for consecutive look up.
1443
-        $query_params[0][ $field_to_order_by ] = array($operand, $current_field_value);
1444
-        $query_params['limit'] = $limit;
1445
-        // set direction
1446
-        $incoming_orderby = isset($query_params['order_by']) ? (array) $query_params['order_by'] : array();
1447
-        $query_params['order_by'] = $operand === '>'
1448
-            ? array($field_to_order_by => 'ASC') + $incoming_orderby
1449
-            : array($field_to_order_by => 'DESC') + $incoming_orderby;
1450
-        // if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1451
-        if (empty($columns_to_select)) {
1452
-            return $this->get_all($query_params);
1453
-        }
1454
-        // getting just the fields
1455
-        return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1456
-    }
1457
-
1458
-
1459
-
1460
-    /**
1461
-     * This sets the _timezone property after model object has been instantiated.
1462
-     *
1463
-     * @param null | string $timezone valid PHP DateTimeZone timezone string
1464
-     */
1465
-    public function set_timezone($timezone)
1466
-    {
1467
-        if ($timezone !== null) {
1468
-            $this->_timezone = $timezone;
1469
-        }
1470
-        // note we need to loop through relations and set the timezone on those objects as well.
1471
-        foreach ($this->_model_relations as $relation) {
1472
-            $relation->set_timezone($timezone);
1473
-        }
1474
-        // and finally we do the same for any datetime fields
1475
-        foreach ($this->_fields as $field) {
1476
-            if ($field instanceof EE_Datetime_Field) {
1477
-                $field->set_timezone($timezone);
1478
-            }
1479
-        }
1480
-    }
1481
-
1482
-
1483
-
1484
-    /**
1485
-     * This just returns whatever is set for the current timezone.
1486
-     *
1487
-     * @access public
1488
-     * @return string
1489
-     */
1490
-    public function get_timezone()
1491
-    {
1492
-        // first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1493
-        if (empty($this->_timezone)) {
1494
-            foreach ($this->_fields as $field) {
1495
-                if ($field instanceof EE_Datetime_Field) {
1496
-                    $this->set_timezone($field->get_timezone());
1497
-                    break;
1498
-                }
1499
-            }
1500
-        }
1501
-        // if timezone STILL empty then return the default timezone for the site.
1502
-        if (empty($this->_timezone)) {
1503
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1504
-        }
1505
-        return $this->_timezone;
1506
-    }
1507
-
1508
-
1509
-
1510
-    /**
1511
-     * This returns the date formats set for the given field name and also ensures that
1512
-     * $this->_timezone property is set correctly.
1513
-     *
1514
-     * @since 4.6.x
1515
-     * @param string $field_name The name of the field the formats are being retrieved for.
1516
-     * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1517
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1518
-     * @return array formats in an array with the date format first, and the time format last.
1519
-     */
1520
-    public function get_formats_for($field_name, $pretty = false)
1521
-    {
1522
-        $field_settings = $this->field_settings_for($field_name);
1523
-        // if not a valid EE_Datetime_Field then throw error
1524
-        if (! $field_settings instanceof EE_Datetime_Field) {
1525
-            throw new EE_Error(sprintf(esc_html__(
1526
-                'The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1527
-                'event_espresso'
1528
-            ), $field_name));
1529
-        }
1530
-        // while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1531
-        // the field.
1532
-        $this->_timezone = $field_settings->get_timezone();
1533
-        return array($field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty));
1534
-    }
1535
-
1536
-
1537
-
1538
-    /**
1539
-     * This returns the current time in a format setup for a query on this model.
1540
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1541
-     * it will return:
1542
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1543
-     *  NOW
1544
-     *  - or a unix timestamp (equivalent to time())
1545
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1546
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1547
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1548
-     * @since 4.6.x
1549
-     * @param string $field_name       The field the current time is needed for.
1550
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1551
-     *                                 formatted string matching the set format for the field in the set timezone will
1552
-     *                                 be returned.
1553
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1554
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1555
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1556
-     *                                 exception is triggered.
1557
-     */
1558
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1559
-    {
1560
-        $formats = $this->get_formats_for($field_name);
1561
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1562
-        if ($timestamp) {
1563
-            return $DateTime->format('U');
1564
-        }
1565
-        // not returning timestamp, so return formatted string in timezone.
1566
-        switch ($what) {
1567
-            case 'time':
1568
-                return $DateTime->format($formats[1]);
1569
-                break;
1570
-            case 'date':
1571
-                return $DateTime->format($formats[0]);
1572
-                break;
1573
-            default:
1574
-                return $DateTime->format(implode(' ', $formats));
1575
-                break;
1576
-        }
1577
-    }
1578
-
1579
-
1580
-
1581
-    /**
1582
-     * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1583
-     * for the model are.  Returns a DateTime object.
1584
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1585
-     * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1586
-     * ignored.
1587
-     *
1588
-     * @param string $field_name      The field being setup.
1589
-     * @param string $timestring      The date time string being used.
1590
-     * @param string $incoming_format The format for the time string.
1591
-     * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1592
-     *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1593
-     *                                format is
1594
-     *                                'U', this is ignored.
1595
-     * @return DateTime
1596
-     * @throws EE_Error
1597
-     */
1598
-    public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1599
-    {
1600
-        // just using this to ensure the timezone is set correctly internally
1601
-        $this->get_formats_for($field_name);
1602
-        // load EEH_DTT_Helper
1603
-        $set_timezone = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1604
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1605
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1606
-        return \EventEspresso\core\domain\entities\DbSafeDateTime::createFromDateTime($incomingDateTime);
1607
-    }
1608
-
1609
-
1610
-
1611
-    /**
1612
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1613
-     *
1614
-     * @return EE_Table_Base[]
1615
-     */
1616
-    public function get_tables()
1617
-    {
1618
-        return $this->_tables;
1619
-    }
1620
-
1621
-
1622
-
1623
-    /**
1624
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1625
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1626
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1627
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1628
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1629
-     * model object with EVT_ID = 1
1630
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1631
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1632
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1633
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1634
-     * are not specified)
1635
-     *
1636
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1637
-     *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1638
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1639
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1640
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1641
-     *                                         ID=34, we'd use this method as follows:
1642
-     *                                         EEM_Transaction::instance()->update(
1643
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1644
-     *                                         array(array('TXN_ID'=>34)));
1645
-     * @param array   $query_params            @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1646
-     *                                         Eg, consider updating Question's QST_admin_label field is of type
1647
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1648
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1649
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1650
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1651
-     *                                         TRUE, it is assumed that you've already called
1652
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1653
-     *                                         malicious javascript. However, if
1654
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1655
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1656
-     *                                         and every other field, before insertion. We provide this parameter
1657
-     *                                         because model objects perform their prepare_for_set function on all
1658
-     *                                         their values, and so don't need to be called again (and in many cases,
1659
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1660
-     *                                         prepare_for_set method...)
1661
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1662
-     *                                         in this model's entity map according to $fields_n_values that match
1663
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1664
-     *                                         by setting this to FALSE, but be aware that model objects being used
1665
-     *                                         could get out-of-sync with the database
1666
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1667
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1668
-     *                                         bad)
1669
-     * @throws EE_Error
1670
-     */
1671
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1672
-    {
1673
-        if (! is_array($query_params)) {
1674
-            EE_Error::doing_it_wrong(
1675
-                'EEM_Base::update',
1676
-                sprintf(
1677
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1678
-                    gettype($query_params)
1679
-                ),
1680
-                '4.6.0'
1681
-            );
1682
-            $query_params = array();
1683
-        }
1684
-        /**
1685
-         * Action called before a model update call has been made.
1686
-         *
1687
-         * @param EEM_Base $model
1688
-         * @param array    $fields_n_values the updated fields and their new values
1689
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1690
-         */
1691
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1692
-        /**
1693
-         * Filters the fields about to be updated given the query parameters. You can provide the
1694
-         * $query_params to $this->get_all() to find exactly which records will be updated
1695
-         *
1696
-         * @param array    $fields_n_values fields and their new values
1697
-         * @param EEM_Base $model           the model being queried
1698
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1699
-         */
1700
-        $fields_n_values = (array) apply_filters(
1701
-            'FHEE__EEM_Base__update__fields_n_values',
1702
-            $fields_n_values,
1703
-            $this,
1704
-            $query_params
1705
-        );
1706
-        // need to verify that, for any entry we want to update, there are entries in each secondary table.
1707
-        // to do that, for each table, verify that it's PK isn't null.
1708
-        $tables = $this->get_tables();
1709
-        // and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1710
-        // NOTE: we should make this code more efficient by NOT querying twice
1711
-        // before the real update, but that needs to first go through ALPHA testing
1712
-        // as it's dangerous. says Mike August 8 2014
1713
-        // we want to make sure the default_where strategy is ignored
1714
-        $this->_ignore_where_strategy = true;
1715
-        $wpdb_select_results = $this->_get_all_wpdb_results($query_params);
1716
-        foreach ($wpdb_select_results as $wpdb_result) {
1717
-            // type cast stdClass as array
1718
-            $wpdb_result = (array) $wpdb_result;
1719
-            // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1720
-            if ($this->has_primary_key_field()) {
1721
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1722
-            } else {
1723
-                // if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1724
-                $main_table_pk_value = null;
1725
-            }
1726
-            // if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1727
-            // and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1728
-            if (count($tables) > 1) {
1729
-                // foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1730
-                // in that table, and so we'll want to insert one
1731
-                foreach ($tables as $table_obj) {
1732
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1733
-                    // if there is no private key for this table on the results, it means there's no entry
1734
-                    // in this table, right? so insert a row in the current table, using any fields available
1735
-                    if (
1736
-                        ! (array_key_exists($this_table_pk_column, $wpdb_result)
1737
-                           && $wpdb_result[ $this_table_pk_column ])
1738
-                    ) {
1739
-                        $success = $this->_insert_into_specific_table(
1740
-                            $table_obj,
1741
-                            $fields_n_values,
1742
-                            $main_table_pk_value
1743
-                        );
1744
-                        // if we died here, report the error
1745
-                        if (! $success) {
1746
-                            return false;
1747
-                        }
1748
-                    }
1749
-                }
1750
-            }
1751
-            //              //and now check that if we have cached any models by that ID on the model, that
1752
-            //              //they also get updated properly
1753
-            //              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1754
-            //              if( $model_object ){
1755
-            //                  foreach( $fields_n_values as $field => $value ){
1756
-            //                      $model_object->set($field, $value);
1757
-            // let's make sure default_where strategy is followed now
1758
-            $this->_ignore_where_strategy = false;
1759
-        }
1760
-        // if we want to keep model objects in sync, AND
1761
-        // if this wasn't called from a model object (to update itself)
1762
-        // then we want to make sure we keep all the existing
1763
-        // model objects in sync with the db
1764
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1765
-            if ($this->has_primary_key_field()) {
1766
-                $model_objs_affected_ids = $this->get_col($query_params);
1767
-            } else {
1768
-                // we need to select a bunch of columns and then combine them into the the "index primary key string"s
1769
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1770
-                $model_objs_affected_ids = array();
1771
-                foreach ($models_affected_key_columns as $row) {
1772
-                    $combined_index_key = $this->get_index_primary_key_string($row);
1773
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1774
-                }
1775
-            }
1776
-            if (! $model_objs_affected_ids) {
1777
-                // wait wait wait- if nothing was affected let's stop here
1778
-                return 0;
1779
-            }
1780
-            foreach ($model_objs_affected_ids as $id) {
1781
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1782
-                if ($model_obj_in_entity_map) {
1783
-                    foreach ($fields_n_values as $field => $new_value) {
1784
-                        $model_obj_in_entity_map->set($field, $new_value);
1785
-                    }
1786
-                }
1787
-            }
1788
-            // if there is a primary key on this model, we can now do a slight optimization
1789
-            if ($this->has_primary_key_field()) {
1790
-                // we already know what we want to update. So let's make the query simpler so it's a little more efficient
1791
-                $query_params = array(
1792
-                    array($this->primary_key_name() => array('IN', $model_objs_affected_ids)),
1793
-                    'limit'                    => count($model_objs_affected_ids),
1794
-                    'default_where_conditions' => EEM_Base::default_where_conditions_none,
1795
-                );
1796
-            }
1797
-        }
1798
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1799
-        $SQL = "UPDATE "
1800
-               . $model_query_info->get_full_join_sql()
1801
-               . " SET "
1802
-               . $this->_construct_update_sql($fields_n_values)
1803
-               . $model_query_info->get_where_sql();// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1804
-        $rows_affected = $this->_do_wpdb_query('query', array($SQL));
1805
-        /**
1806
-         * Action called after a model update call has been made.
1807
-         *
1808
-         * @param EEM_Base $model
1809
-         * @param array    $fields_n_values the updated fields and their new values
1810
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1811
-         * @param int      $rows_affected
1812
-         */
1813
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1814
-        return $rows_affected;// how many supposedly got updated
1815
-    }
1816
-
1817
-
1818
-
1819
-    /**
1820
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1821
-     * are teh values of the field specified (or by default the primary key field)
1822
-     * that matched the query params. Note that you should pass the name of the
1823
-     * model FIELD, not the database table's column name.
1824
-     *
1825
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1826
-     * @param string $field_to_select
1827
-     * @return array just like $wpdb->get_col()
1828
-     * @throws EE_Error
1829
-     */
1830
-    public function get_col($query_params = array(), $field_to_select = null)
1831
-    {
1832
-        if ($field_to_select) {
1833
-            $field = $this->field_settings_for($field_to_select);
1834
-        } elseif ($this->has_primary_key_field()) {
1835
-            $field = $this->get_primary_key_field();
1836
-        } else {
1837
-            // no primary key, just grab the first column
1838
-            $field_settings = $this->field_settings();
1839
-            $field = reset($field_settings);
1840
-        }
1841
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1842
-        $select_expressions = $field->get_qualified_column();
1843
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1844
-        return $this->_do_wpdb_query('get_col', array($SQL));
1845
-    }
1846
-
1847
-
1848
-
1849
-    /**
1850
-     * Returns a single column value for a single row from the database
1851
-     *
1852
-     * @param array  $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1853
-     * @param string $field_to_select @see EEM_Base::get_col()
1854
-     * @return string
1855
-     * @throws EE_Error
1856
-     */
1857
-    public function get_var($query_params = array(), $field_to_select = null)
1858
-    {
1859
-        $query_params['limit'] = 1;
1860
-        $col = $this->get_col($query_params, $field_to_select);
1861
-        if (! empty($col)) {
1862
-            return reset($col);
1863
-        }
1864
-        return null;
1865
-    }
1866
-
1867
-
1868
-
1869
-    /**
1870
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1871
-     * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1872
-     * injection, but currently no further filtering is done
1873
-     *
1874
-     * @global      $wpdb
1875
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1876
-     *                               be updated to in the DB
1877
-     * @return string of SQL
1878
-     * @throws EE_Error
1879
-     */
1880
-    public function _construct_update_sql($fields_n_values)
1881
-    {
1882
-        /** @type WPDB $wpdb */
1883
-        global $wpdb;
1884
-        $cols_n_values = array();
1885
-        foreach ($fields_n_values as $field_name => $value) {
1886
-            $field_obj = $this->field_settings_for($field_name);
1887
-            // if the value is NULL, we want to assign the value to that.
1888
-            // wpdb->prepare doesn't really handle that properly
1889
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1890
-            $value_sql = $prepared_value === null ? 'NULL'
1891
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1892
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1893
-        }
1894
-        return implode(",", $cols_n_values);
1895
-    }
1896
-
1897
-
1898
-
1899
-    /**
1900
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1901
-     * Performs a HARD delete, meaning the database row should always be removed,
1902
-     * not just have a flag field on it switched
1903
-     * Wrapper for EEM_Base::delete_permanently()
1904
-     *
1905
-     * @param mixed $id
1906
-     * @param boolean $allow_blocking
1907
-     * @return int the number of rows deleted
1908
-     * @throws EE_Error
1909
-     */
1910
-    public function delete_permanently_by_ID($id, $allow_blocking = true)
1911
-    {
1912
-        return $this->delete_permanently(
1913
-            array(
1914
-                array($this->get_primary_key_field()->get_name() => $id),
1915
-                'limit' => 1,
1916
-            ),
1917
-            $allow_blocking
1918
-        );
1919
-    }
1920
-
1921
-
1922
-
1923
-    /**
1924
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1925
-     * Wrapper for EEM_Base::delete()
1926
-     *
1927
-     * @param mixed $id
1928
-     * @param boolean $allow_blocking
1929
-     * @return int the number of rows deleted
1930
-     * @throws EE_Error
1931
-     */
1932
-    public function delete_by_ID($id, $allow_blocking = true)
1933
-    {
1934
-        return $this->delete(
1935
-            array(
1936
-                array($this->get_primary_key_field()->get_name() => $id),
1937
-                'limit' => 1,
1938
-            ),
1939
-            $allow_blocking
1940
-        );
1941
-    }
1942
-
1943
-
1944
-
1945
-    /**
1946
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1947
-     * meaning if the model has a field that indicates its been "trashed" or
1948
-     * "soft deleted", we will just set that instead of actually deleting the rows.
1949
-     *
1950
-     * @see EEM_Base::delete_permanently
1951
-     * @param array   $query_params
1952
-     * @param boolean $allow_blocking
1953
-     * @return int how many rows got deleted
1954
-     * @throws EE_Error
1955
-     */
1956
-    public function delete($query_params, $allow_blocking = true)
1957
-    {
1958
-        return $this->delete_permanently($query_params, $allow_blocking);
1959
-    }
1960
-
1961
-
1962
-
1963
-    /**
1964
-     * Deletes the model objects that meet the query params. Note: this method is overridden
1965
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1966
-     * as archived, not actually deleted
1967
-     *
1968
-     * @param array   $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1969
-     * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1970
-     *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1971
-     *                                deletes regardless of other objects which may depend on it. Its generally
1972
-     *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1973
-     *                                DB
1974
-     * @return int how many rows got deleted
1975
-     * @throws EE_Error
1976
-     */
1977
-    public function delete_permanently($query_params, $allow_blocking = true)
1978
-    {
1979
-        /**
1980
-         * Action called just before performing a real deletion query. You can use the
1981
-         * model and its $query_params to find exactly which items will be deleted
1982
-         *
1983
-         * @param EEM_Base $model
1984
-         * @param array    $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1985
-         * @param boolean  $allow_blocking whether or not to allow related model objects
1986
-         *                                 to block (prevent) this deletion
1987
-         */
1988
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1989
-        // some MySQL databases may be running safe mode, which may restrict
1990
-        // deletion if there is no KEY column used in the WHERE statement of a deletion.
1991
-        // to get around this, we first do a SELECT, get all the IDs, and then run another query
1992
-        // to delete them
1993
-        $items_for_deletion = $this->_get_all_wpdb_results($query_params);
1994
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1995
-        $deletion_where_query_part = $this->_build_query_part_for_deleting_from_columns_and_values(
1996
-            $columns_and_ids_for_deleting
1997
-        );
1998
-        /**
1999
-         * Allows client code to act on the items being deleted before the query is actually executed.
2000
-         *
2001
-         * @param EEM_Base $this  The model instance being acted on.
2002
-         * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
2003
-         * @param bool     $allow_blocking @see param description in method phpdoc block.
2004
-         * @param array $columns_and_ids_for_deleting       An array indicating what entities will get removed as
2005
-         *                                                  derived from the incoming query parameters.
2006
-         *                                                  @see details on the structure of this array in the phpdocs
2007
-         *                                                  for the `_get_ids_for_delete_method`
2008
-         *
2009
-         */
2010
-        do_action(
2011
-            'AHEE__EEM_Base__delete__before_query',
2012
-            $this,
2013
-            $query_params,
2014
-            $allow_blocking,
2015
-            $columns_and_ids_for_deleting
2016
-        );
2017
-        if ($deletion_where_query_part) {
2018
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
2019
-            $table_aliases = array_keys($this->_tables);
2020
-            $SQL = "DELETE "
2021
-                   . implode(", ", $table_aliases)
2022
-                   . " FROM "
2023
-                   . $model_query_info->get_full_join_sql()
2024
-                   . " WHERE "
2025
-                   . $deletion_where_query_part;
2026
-            $rows_deleted = $this->_do_wpdb_query('query', array($SQL));
2027
-        } else {
2028
-            $rows_deleted = 0;
2029
-        }
2030
-
2031
-        // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2032
-        // there was no error with the delete query.
2033
-        if (
2034
-            $this->has_primary_key_field()
2035
-            && $rows_deleted !== false
2036
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2037
-        ) {
2038
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2039
-            foreach ($ids_for_removal as $id) {
2040
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2041
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2042
-                }
2043
-            }
2044
-
2045
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2046
-            // `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2047
-            // unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2048
-            // (although it is possible).
2049
-            // Note this can be skipped by using the provided filter and returning false.
2050
-            if (
2051
-                apply_filters(
2052
-                    'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2053
-                    ! $this instanceof EEM_Extra_Meta,
2054
-                    $this
2055
-                )
2056
-            ) {
2057
-                EEM_Extra_Meta::instance()->delete_permanently(array(
2058
-                    0 => array(
2059
-                        'EXM_type' => $this->get_this_model_name(),
2060
-                        'OBJ_ID'   => array(
2061
-                            'IN',
2062
-                            $ids_for_removal
2063
-                        )
2064
-                    )
2065
-                ));
2066
-            }
2067
-        }
2068
-
2069
-        /**
2070
-         * Action called just after performing a real deletion query. Although at this point the
2071
-         * items should have been deleted
2072
-         *
2073
-         * @param EEM_Base $model
2074
-         * @param array    $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2075
-         * @param int      $rows_deleted
2076
-         */
2077
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2078
-        return $rows_deleted;// how many supposedly got deleted
2079
-    }
2080
-
2081
-
2082
-
2083
-    /**
2084
-     * Checks all the relations that throw error messages when there are blocking related objects
2085
-     * for related model objects. If there are any related model objects on those relations,
2086
-     * adds an EE_Error, and return true
2087
-     *
2088
-     * @param EE_Base_Class|int $this_model_obj_or_id
2089
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2090
-     *                                                 should be ignored when determining whether there are related
2091
-     *                                                 model objects which block this model object's deletion. Useful
2092
-     *                                                 if you know A is related to B and are considering deleting A,
2093
-     *                                                 but want to see if A has any other objects blocking its deletion
2094
-     *                                                 before removing the relation between A and B
2095
-     * @return boolean
2096
-     * @throws EE_Error
2097
-     */
2098
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2099
-    {
2100
-        // first, if $ignore_this_model_obj was supplied, get its model
2101
-        if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2102
-            $ignored_model = $ignore_this_model_obj->get_model();
2103
-        } else {
2104
-            $ignored_model = null;
2105
-        }
2106
-        // now check all the relations of $this_model_obj_or_id and see if there
2107
-        // are any related model objects blocking it?
2108
-        $is_blocked = false;
2109
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2110
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2111
-                // if $ignore_this_model_obj was supplied, then for the query
2112
-                // on that model needs to be told to ignore $ignore_this_model_obj
2113
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2114
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id, array(
2115
-                        array(
2116
-                            $ignored_model->get_primary_key_field()->get_name() => array(
2117
-                                '!=',
2118
-                                $ignore_this_model_obj->ID(),
2119
-                            ),
2120
-                        ),
2121
-                    ));
2122
-                } else {
2123
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2124
-                }
2125
-                if ($related_model_objects) {
2126
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2127
-                    $is_blocked = true;
2128
-                }
2129
-            }
2130
-        }
2131
-        return $is_blocked;
2132
-    }
2133
-
2134
-
2135
-    /**
2136
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2137
-     * @param array $row_results_for_deleting
2138
-     * @param bool  $allow_blocking
2139
-     * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2140
-     *                 model DOES have a primary_key_field, then the array will be a simple single dimension array where
2141
-     *                 the key is the fully qualified primary key column and the value is an array of ids that will be
2142
-     *                 deleted. Example:
2143
-     *                      array('Event.EVT_ID' => array( 1,2,3))
2144
-     *                 If the model DOES NOT have a primary_key_field, then the array will be a two dimensional array
2145
-     *                 where each element is a group of columns and values that get deleted. Example:
2146
-     *                      array(
2147
-     *                          0 => array(
2148
-     *                              'Term_Relationship.object_id' => 1
2149
-     *                              'Term_Relationship.term_taxonomy_id' => 5
2150
-     *                          ),
2151
-     *                          1 => array(
2152
-     *                              'Term_Relationship.object_id' => 1
2153
-     *                              'Term_Relationship.term_taxonomy_id' => 6
2154
-     *                          )
2155
-     *                      )
2156
-     * @throws EE_Error
2157
-     */
2158
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2159
-    {
2160
-        $ids_to_delete_indexed_by_column = array();
2161
-        if ($this->has_primary_key_field()) {
2162
-            $primary_table = $this->_get_main_table();
2163
-            $primary_table_pk_field = $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2164
-            $other_tables = $this->_get_other_tables();
2165
-            $ids_to_delete_indexed_by_column = $query = array();
2166
-            foreach ($row_results_for_deleting as $item_to_delete) {
2167
-                // before we mark this item for deletion,
2168
-                // make sure there's no related entities blocking its deletion (if we're checking)
2169
-                if (
2170
-                    $allow_blocking
2171
-                    && $this->delete_is_blocked_by_related_models(
2172
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2173
-                    )
2174
-                ) {
2175
-                    continue;
2176
-                }
2177
-                // primary table deletes
2178
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2179
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2180
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2181
-                }
2182
-            }
2183
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2184
-            $fields = $this->get_combined_primary_key_fields();
2185
-            foreach ($row_results_for_deleting as $item_to_delete) {
2186
-                $ids_to_delete_indexed_by_column_for_row = array();
2187
-                foreach ($fields as $cpk_field) {
2188
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2189
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2190
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2191
-                    }
2192
-                }
2193
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2194
-            }
2195
-        } else {
2196
-            // so there's no primary key and no combined key...
2197
-            // sorry, can't help you
2198
-            throw new EE_Error(
2199
-                sprintf(
2200
-                    esc_html__(
2201
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2202
-                        "event_espresso"
2203
-                    ),
2204
-                    get_class($this)
2205
-                )
2206
-            );
2207
-        }
2208
-        return $ids_to_delete_indexed_by_column;
2209
-    }
2210
-
2211
-
2212
-    /**
2213
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2214
-     * the corresponding query_part for the query performing the delete.
2215
-     *
2216
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2217
-     * @return string
2218
-     * @throws EE_Error
2219
-     */
2220
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2221
-    {
2222
-        $query_part = '';
2223
-        if (empty($ids_to_delete_indexed_by_column)) {
2224
-            return $query_part;
2225
-        } elseif ($this->has_primary_key_field()) {
2226
-            $query = array();
2227
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2228
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2229
-            }
2230
-            $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2231
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2232
-            $ways_to_identify_a_row = array();
2233
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2234
-                $values_for_each_combined_primary_key_for_a_row = array();
2235
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2236
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2237
-                }
2238
-                $ways_to_identify_a_row[] = '('
2239
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2240
-                                            . ')';
2241
-            }
2242
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2243
-        }
2244
-        return $query_part;
2245
-    }
2246
-
2247
-
2248
-
2249
-    /**
2250
-     * Gets the model field by the fully qualified name
2251
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2252
-     * @return EE_Model_Field_Base
2253
-     */
2254
-    public function get_field_by_column($qualified_column_name)
2255
-    {
2256
-        foreach ($this->field_settings(true) as $field_name => $field_obj) {
2257
-            if ($field_obj->get_qualified_column() === $qualified_column_name) {
2258
-                return $field_obj;
2259
-            }
2260
-        }
2261
-        throw new EE_Error(
2262
-            sprintf(
2263
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2264
-                $this->get_this_model_name(),
2265
-                $qualified_column_name
2266
-            )
2267
-        );
2268
-    }
2269
-
2270
-
2271
-
2272
-    /**
2273
-     * Count all the rows that match criteria the model query params.
2274
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2275
-     * column
2276
-     *
2277
-     * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2278
-     * @param string $field_to_count field on model to count by (not column name)
2279
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2280
-     *                               that by the setting $distinct to TRUE;
2281
-     * @return int
2282
-     * @throws EE_Error
2283
-     */
2284
-    public function count($query_params = array(), $field_to_count = null, $distinct = false)
2285
-    {
2286
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2287
-        if ($field_to_count) {
2288
-            $field_obj = $this->field_settings_for($field_to_count);
2289
-            $column_to_count = $field_obj->get_qualified_column();
2290
-        } elseif ($this->has_primary_key_field()) {
2291
-            $pk_field_obj = $this->get_primary_key_field();
2292
-            $column_to_count = $pk_field_obj->get_qualified_column();
2293
-        } else {
2294
-            // there's no primary key
2295
-            // if we're counting distinct items, and there's no primary key,
2296
-            // we need to list out the columns for distinction;
2297
-            // otherwise we can just use star
2298
-            if ($distinct) {
2299
-                $columns_to_use = array();
2300
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2301
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2302
-                }
2303
-                $column_to_count = implode(',', $columns_to_use);
2304
-            } else {
2305
-                $column_to_count = '*';
2306
-            }
2307
-        }
2308
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2309
-        $SQL = "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2310
-        return (int) $this->_do_wpdb_query('get_var', array($SQL));
2311
-    }
2312
-
2313
-
2314
-
2315
-    /**
2316
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2317
-     *
2318
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2319
-     * @param string $field_to_sum name of field (array key in $_fields array)
2320
-     * @return float
2321
-     * @throws EE_Error
2322
-     */
2323
-    public function sum($query_params, $field_to_sum = null)
2324
-    {
2325
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2326
-        if ($field_to_sum) {
2327
-            $field_obj = $this->field_settings_for($field_to_sum);
2328
-        } else {
2329
-            $field_obj = $this->get_primary_key_field();
2330
-        }
2331
-        $column_to_count = $field_obj->get_qualified_column();
2332
-        $SQL = "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2333
-        $return_value = $this->_do_wpdb_query('get_var', array($SQL));
2334
-        $data_type = $field_obj->get_wpdb_data_type();
2335
-        if ($data_type === '%d' || $data_type === '%s') {
2336
-            return (float) $return_value;
2337
-        }
2338
-        // must be %f
2339
-        return (float) $return_value;
2340
-    }
2341
-
2342
-
2343
-
2344
-    /**
2345
-     * Just calls the specified method on $wpdb with the given arguments
2346
-     * Consolidates a little extra error handling code
2347
-     *
2348
-     * @param string $wpdb_method
2349
-     * @param array  $arguments_to_provide
2350
-     * @throws EE_Error
2351
-     * @global wpdb  $wpdb
2352
-     * @return mixed
2353
-     */
2354
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2355
-    {
2356
-        // if we're in maintenance mode level 2, DON'T run any queries
2357
-        // because level 2 indicates the database needs updating and
2358
-        // is probably out of sync with the code
2359
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2360
-            throw new EE_Error(sprintf(esc_html__(
2361
-                "Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2362
-                "event_espresso"
2363
-            )));
2364
-        }
2365
-        /** @type WPDB $wpdb */
2366
-        global $wpdb;
2367
-        if (! method_exists($wpdb, $wpdb_method)) {
2368
-            throw new EE_Error(sprintf(esc_html__(
2369
-                'There is no method named "%s" on Wordpress\' $wpdb object',
2370
-                'event_espresso'
2371
-            ), $wpdb_method));
2372
-        }
2373
-        if (WP_DEBUG) {
2374
-            $old_show_errors_value = $wpdb->show_errors;
2375
-            $wpdb->show_errors(false);
2376
-        }
2377
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2378
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2379
-        if (WP_DEBUG) {
2380
-            $wpdb->show_errors($old_show_errors_value);
2381
-            if (! empty($wpdb->last_error)) {
2382
-                throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2383
-            }
2384
-            if ($result === false) {
2385
-                throw new EE_Error(sprintf(esc_html__(
2386
-                    'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2387
-                    'event_espresso'
2388
-                ), $wpdb_method, var_export($arguments_to_provide, true)));
2389
-            }
2390
-        } elseif ($result === false) {
2391
-            EE_Error::add_error(
2392
-                sprintf(
2393
-                    esc_html__(
2394
-                        'A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2395
-                        'event_espresso'
2396
-                    ),
2397
-                    $wpdb_method,
2398
-                    var_export($arguments_to_provide, true),
2399
-                    $wpdb->last_error
2400
-                ),
2401
-                __FILE__,
2402
-                __FUNCTION__,
2403
-                __LINE__
2404
-            );
2405
-        }
2406
-        return $result;
2407
-    }
2408
-
2409
-
2410
-
2411
-    /**
2412
-     * Attempts to run the indicated WPDB method with the provided arguments,
2413
-     * and if there's an error tries to verify the DB is correct. Uses
2414
-     * the static property EEM_Base::$_db_verification_level to determine whether
2415
-     * we should try to fix the EE core db, the addons, or just give up
2416
-     *
2417
-     * @param string $wpdb_method
2418
-     * @param array  $arguments_to_provide
2419
-     * @return mixed
2420
-     */
2421
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2422
-    {
2423
-        /** @type WPDB $wpdb */
2424
-        global $wpdb;
2425
-        $wpdb->last_error = null;
2426
-        $result = call_user_func_array(array($wpdb, $wpdb_method), $arguments_to_provide);
2427
-        // was there an error running the query? but we don't care on new activations
2428
-        // (we're going to setup the DB anyway on new activations)
2429
-        if (
2430
-            ($result === false || ! empty($wpdb->last_error))
2431
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2432
-        ) {
2433
-            switch (EEM_Base::$_db_verification_level) {
2434
-                case EEM_Base::db_verified_none:
2435
-                    // let's double-check core's DB
2436
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2437
-                    break;
2438
-                case EEM_Base::db_verified_core:
2439
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2440
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2441
-                    break;
2442
-                case EEM_Base::db_verified_addons:
2443
-                    // ummmm... you in trouble
2444
-                    return $result;
2445
-                    break;
2446
-            }
2447
-            if (! empty($error_message)) {
2448
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2449
-                trigger_error($error_message);
2450
-            }
2451
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2452
-        }
2453
-        return $result;
2454
-    }
2455
-
2456
-
2457
-
2458
-    /**
2459
-     * Verifies the EE core database is up-to-date and records that we've done it on
2460
-     * EEM_Base::$_db_verification_level
2461
-     *
2462
-     * @param string $wpdb_method
2463
-     * @param array  $arguments_to_provide
2464
-     * @return string
2465
-     */
2466
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2467
-    {
2468
-        /** @type WPDB $wpdb */
2469
-        global $wpdb;
2470
-        // ok remember that we've already attempted fixing the core db, in case the problem persists
2471
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2472
-        $error_message = sprintf(
2473
-            esc_html__(
2474
-                'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2475
-                'event_espresso'
2476
-            ),
2477
-            $wpdb->last_error,
2478
-            $wpdb_method,
2479
-            wp_json_encode($arguments_to_provide)
2480
-        );
2481
-        EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2482
-        return $error_message;
2483
-    }
2484
-
2485
-
2486
-
2487
-    /**
2488
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2489
-     * EEM_Base::$_db_verification_level
2490
-     *
2491
-     * @param $wpdb_method
2492
-     * @param $arguments_to_provide
2493
-     * @return string
2494
-     */
2495
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2496
-    {
2497
-        /** @type WPDB $wpdb */
2498
-        global $wpdb;
2499
-        // ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2500
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2501
-        $error_message = sprintf(
2502
-            esc_html__(
2503
-                'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2504
-                'event_espresso'
2505
-            ),
2506
-            $wpdb->last_error,
2507
-            $wpdb_method,
2508
-            wp_json_encode($arguments_to_provide)
2509
-        );
2510
-        EE_System::instance()->initialize_addons();
2511
-        return $error_message;
2512
-    }
2513
-
2514
-
2515
-
2516
-    /**
2517
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2518
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2519
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2520
-     * ..."
2521
-     *
2522
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2523
-     * @return string
2524
-     */
2525
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2526
-    {
2527
-        return " FROM " . $model_query_info->get_full_join_sql() .
2528
-               $model_query_info->get_where_sql() .
2529
-               $model_query_info->get_group_by_sql() .
2530
-               $model_query_info->get_having_sql() .
2531
-               $model_query_info->get_order_by_sql() .
2532
-               $model_query_info->get_limit_sql();
2533
-    }
2534
-
2535
-
2536
-
2537
-    /**
2538
-     * Set to easily debug the next X queries ran from this model.
2539
-     *
2540
-     * @param int $count
2541
-     */
2542
-    public function show_next_x_db_queries($count = 1)
2543
-    {
2544
-        $this->_show_next_x_db_queries = $count;
2545
-    }
2546
-
2547
-
2548
-
2549
-    /**
2550
-     * @param $sql_query
2551
-     */
2552
-    public function show_db_query_if_previously_requested($sql_query)
2553
-    {
2554
-        if ($this->_show_next_x_db_queries > 0) {
2555
-            echo esc_html($sql_query);
2556
-            $this->_show_next_x_db_queries--;
2557
-        }
2558
-    }
2559
-
2560
-
2561
-
2562
-    /**
2563
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2564
-     * There are the 3 cases:
2565
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2566
-     * $otherModelObject has no ID, it is first saved.
2567
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2568
-     * has no ID, it is first saved.
2569
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2570
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2571
-     * join table
2572
-     *
2573
-     * @param        EE_Base_Class                     /int $thisModelObject
2574
-     * @param        EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2575
-     * @param string $relationName                     , key in EEM_Base::_relations
2576
-     *                                                 an attendee to a group, you also want to specify which role they
2577
-     *                                                 will have in that group. So you would use this parameter to
2578
-     *                                                 specify array('role-column-name'=>'role-id')
2579
-     * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2580
-     *                                                 to for relation to methods that allow you to further specify
2581
-     *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2582
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2583
-     *                                                 because these will be inserted in any new rows created as well.
2584
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2585
-     * @throws EE_Error
2586
-     */
2587
-    public function add_relationship_to(
2588
-        $id_or_obj,
2589
-        $other_model_id_or_obj,
2590
-        $relationName,
2591
-        $extra_join_model_fields_n_values = array()
2592
-    ) {
2593
-        $relation_obj = $this->related_settings_for($relationName);
2594
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2595
-    }
2596
-
2597
-
2598
-
2599
-    /**
2600
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2601
-     * There are the 3 cases:
2602
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2603
-     * error
2604
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2605
-     * an error
2606
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2607
-     *
2608
-     * @param        EE_Base_Class /int $id_or_obj
2609
-     * @param        EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2610
-     * @param string $relationName key in EEM_Base::_relations
2611
-     * @return boolean of success
2612
-     * @throws EE_Error
2613
-     * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2614
-     *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2615
-     *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2616
-     *                             because these will be inserted in any new rows created as well.
2617
-     */
2618
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = array())
2619
-    {
2620
-        $relation_obj = $this->related_settings_for($relationName);
2621
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2622
-    }
2623
-
2624
-
2625
-
2626
-    /**
2627
-     * @param mixed           $id_or_obj
2628
-     * @param string          $relationName
2629
-     * @param array           $where_query_params
2630
-     * @param EE_Base_Class[] objects to which relations were removed
2631
-     * @return \EE_Base_Class[]
2632
-     * @throws EE_Error
2633
-     */
2634
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = array())
2635
-    {
2636
-        $relation_obj = $this->related_settings_for($relationName);
2637
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2638
-    }
2639
-
2640
-
2641
-
2642
-    /**
2643
-     * Gets all the related items of the specified $model_name, using $query_params.
2644
-     * Note: by default, we remove the "default query params"
2645
-     * because we want to get even deleted items etc.
2646
-     *
2647
-     * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2648
-     * @param string $model_name   like 'Event', 'Registration', etc. always singular
2649
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2650
-     * @return EE_Base_Class[]
2651
-     * @throws EE_Error
2652
-     */
2653
-    public function get_all_related($id_or_obj, $model_name, $query_params = null)
2654
-    {
2655
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2656
-        $relation_settings = $this->related_settings_for($model_name);
2657
-        return $relation_settings->get_all_related($model_obj, $query_params);
2658
-    }
2659
-
2660
-
2661
-
2662
-    /**
2663
-     * Deletes all the model objects across the relation indicated by $model_name
2664
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2665
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2666
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2667
-     *
2668
-     * @param EE_Base_Class|int|string $id_or_obj
2669
-     * @param string                   $model_name
2670
-     * @param array                    $query_params
2671
-     * @return int how many deleted
2672
-     * @throws EE_Error
2673
-     */
2674
-    public function delete_related($id_or_obj, $model_name, $query_params = array())
2675
-    {
2676
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2677
-        $relation_settings = $this->related_settings_for($model_name);
2678
-        return $relation_settings->delete_all_related($model_obj, $query_params);
2679
-    }
2680
-
2681
-
2682
-
2683
-    /**
2684
-     * Hard deletes all the model objects across the relation indicated by $model_name
2685
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2686
-     * the model objects can't be hard deleted because of blocking related model objects,
2687
-     * just does a soft-delete on them instead.
2688
-     *
2689
-     * @param EE_Base_Class|int|string $id_or_obj
2690
-     * @param string                   $model_name
2691
-     * @param array                    $query_params
2692
-     * @return int how many deleted
2693
-     * @throws EE_Error
2694
-     */
2695
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = array())
2696
-    {
2697
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2698
-        $relation_settings = $this->related_settings_for($model_name);
2699
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2700
-    }
2701
-
2702
-
2703
-
2704
-    /**
2705
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2706
-     * unless otherwise specified in the $query_params
2707
-     *
2708
-     * @param        int             /EE_Base_Class $id_or_obj
2709
-     * @param string $model_name     like 'Event', or 'Registration'
2710
-     * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2711
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2712
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2713
-     *                               that by the setting $distinct to TRUE;
2714
-     * @return int
2715
-     * @throws EE_Error
2716
-     */
2717
-    public function count_related(
2718
-        $id_or_obj,
2719
-        $model_name,
2720
-        $query_params = array(),
2721
-        $field_to_count = null,
2722
-        $distinct = false
2723
-    ) {
2724
-        $related_model = $this->get_related_model_obj($model_name);
2725
-        // we're just going to use the query params on the related model's normal get_all query,
2726
-        // except add a condition to say to match the current mod
2727
-        if (! isset($query_params['default_where_conditions'])) {
2728
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2729
-        }
2730
-        $this_model_name = $this->get_this_model_name();
2731
-        $this_pk_field_name = $this->get_primary_key_field()->get_name();
2732
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2733
-        return $related_model->count($query_params, $field_to_count, $distinct);
2734
-    }
2735
-
2736
-
2737
-
2738
-    /**
2739
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2740
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2741
-     *
2742
-     * @param        int           /EE_Base_Class $id_or_obj
2743
-     * @param string $model_name   like 'Event', or 'Registration'
2744
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2745
-     * @param string $field_to_sum name of field to count by. By default, uses primary key
2746
-     * @return float
2747
-     * @throws EE_Error
2748
-     */
2749
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2750
-    {
2751
-        $related_model = $this->get_related_model_obj($model_name);
2752
-        if (! is_array($query_params)) {
2753
-            EE_Error::doing_it_wrong(
2754
-                'EEM_Base::sum_related',
2755
-                sprintf(
2756
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2757
-                    gettype($query_params)
2758
-                ),
2759
-                '4.6.0'
2760
-            );
2761
-            $query_params = array();
2762
-        }
2763
-        // we're just going to use the query params on the related model's normal get_all query,
2764
-        // except add a condition to say to match the current mod
2765
-        if (! isset($query_params['default_where_conditions'])) {
2766
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2767
-        }
2768
-        $this_model_name = $this->get_this_model_name();
2769
-        $this_pk_field_name = $this->get_primary_key_field()->get_name();
2770
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2771
-        return $related_model->sum($query_params, $field_to_sum);
2772
-    }
2773
-
2774
-
2775
-
2776
-    /**
2777
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2778
-     * $modelObject
2779
-     *
2780
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2781
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2782
-     * @param array               $query_params     @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2783
-     * @return EE_Base_Class
2784
-     * @throws EE_Error
2785
-     */
2786
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2787
-    {
2788
-        $query_params['limit'] = 1;
2789
-        $results = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2790
-        if ($results) {
2791
-            return array_shift($results);
2792
-        }
2793
-        return null;
2794
-    }
2795
-
2796
-
2797
-
2798
-    /**
2799
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2800
-     *
2801
-     * @return string
2802
-     */
2803
-    public function get_this_model_name()
2804
-    {
2805
-        return str_replace("EEM_", "", get_class($this));
2806
-    }
2807
-
2808
-
2809
-
2810
-    /**
2811
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2812
-     *
2813
-     * @return EE_Any_Foreign_Model_Name_Field
2814
-     * @throws EE_Error
2815
-     */
2816
-    public function get_field_containing_related_model_name()
2817
-    {
2818
-        foreach ($this->field_settings(true) as $field) {
2819
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2820
-                $field_with_model_name = $field;
2821
-            }
2822
-        }
2823
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2824
-            throw new EE_Error(sprintf(
2825
-                esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2826
-                $this->get_this_model_name()
2827
-            ));
2828
-        }
2829
-        return $field_with_model_name;
2830
-    }
2831
-
2832
-
2833
-
2834
-    /**
2835
-     * Inserts a new entry into the database, for each table.
2836
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2837
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2838
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2839
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2840
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2841
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2842
-     *
2843
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2844
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2845
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2846
-     *                              of EEM_Base)
2847
-     * @return int|string new primary key on main table that got inserted
2848
-     * @throws EE_Error
2849
-     */
2850
-    public function insert($field_n_values)
2851
-    {
2852
-        /**
2853
-         * Filters the fields and their values before inserting an item using the models
2854
-         *
2855
-         * @param array    $fields_n_values keys are the fields and values are their new values
2856
-         * @param EEM_Base $model           the model used
2857
-         */
2858
-        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2859
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2860
-            $main_table = $this->_get_main_table();
2861
-            $new_id = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2862
-            if ($new_id !== false) {
2863
-                foreach ($this->_get_other_tables() as $other_table) {
2864
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2865
-                }
2866
-            }
2867
-            /**
2868
-             * Done just after attempting to insert a new model object
2869
-             *
2870
-             * @param EEM_Base   $model           used
2871
-             * @param array      $fields_n_values fields and their values
2872
-             * @param int|string the              ID of the newly-inserted model object
2873
-             */
2874
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2875
-            return $new_id;
2876
-        }
2877
-        return false;
2878
-    }
2879
-
2880
-
2881
-
2882
-    /**
2883
-     * Checks that the result would satisfy the unique indexes on this model
2884
-     *
2885
-     * @param array  $field_n_values
2886
-     * @param string $action
2887
-     * @return boolean
2888
-     * @throws EE_Error
2889
-     */
2890
-    protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2891
-    {
2892
-        foreach ($this->unique_indexes() as $index_name => $index) {
2893
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2894
-            if ($this->exists(array($uniqueness_where_params))) {
2895
-                EE_Error::add_error(
2896
-                    sprintf(
2897
-                        esc_html__(
2898
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2899
-                            "event_espresso"
2900
-                        ),
2901
-                        $action,
2902
-                        $this->_get_class_name(),
2903
-                        $index_name,
2904
-                        implode(",", $index->field_names()),
2905
-                        http_build_query($uniqueness_where_params)
2906
-                    ),
2907
-                    __FILE__,
2908
-                    __FUNCTION__,
2909
-                    __LINE__
2910
-                );
2911
-                return false;
2912
-            }
2913
-        }
2914
-        return true;
2915
-    }
2916
-
2917
-
2918
-
2919
-    /**
2920
-     * Checks the database for an item that conflicts (ie, if this item were
2921
-     * saved to the DB would break some uniqueness requirement, like a primary key
2922
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
2923
-     * can be either an EE_Base_Class or an array of fields n values
2924
-     *
2925
-     * @param EE_Base_Class|array $obj_or_fields_array
2926
-     * @param boolean             $include_primary_key whether to use the model object's primary key
2927
-     *                                                 when looking for conflicts
2928
-     *                                                 (ie, if false, we ignore the model object's primary key
2929
-     *                                                 when finding "conflicts". If true, it's also considered).
2930
-     *                                                 Only works for INT primary key,
2931
-     *                                                 STRING primary keys cannot be ignored
2932
-     * @throws EE_Error
2933
-     * @return EE_Base_Class|array
2934
-     */
2935
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2936
-    {
2937
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
2938
-            $fields_n_values = $obj_or_fields_array->model_field_array();
2939
-        } elseif (is_array($obj_or_fields_array)) {
2940
-            $fields_n_values = $obj_or_fields_array;
2941
-        } else {
2942
-            throw new EE_Error(
2943
-                sprintf(
2944
-                    esc_html__(
2945
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2946
-                        "event_espresso"
2947
-                    ),
2948
-                    get_class($this),
2949
-                    $obj_or_fields_array
2950
-                )
2951
-            );
2952
-        }
2953
-        $query_params = array();
2954
-        if (
2955
-            $this->has_primary_key_field()
2956
-            && ($include_primary_key
2957
-                || $this->get_primary_key_field()
2958
-                   instanceof
2959
-                   EE_Primary_Key_String_Field)
2960
-            && isset($fields_n_values[ $this->primary_key_name() ])
2961
-        ) {
2962
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2963
-        }
2964
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2965
-            $uniqueness_where_params = array_intersect_key($fields_n_values, $unique_index->fields());
2966
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2967
-        }
2968
-        // if there is nothing to base this search on, then we shouldn't find anything
2969
-        if (empty($query_params)) {
2970
-            return array();
2971
-        }
2972
-        return $this->get_one($query_params);
2973
-    }
2974
-
2975
-
2976
-
2977
-    /**
2978
-     * Like count, but is optimized and returns a boolean instead of an int
2979
-     *
2980
-     * @param array $query_params
2981
-     * @return boolean
2982
-     * @throws EE_Error
2983
-     */
2984
-    public function exists($query_params)
2985
-    {
2986
-        $query_params['limit'] = 1;
2987
-        return $this->count($query_params) > 0;
2988
-    }
2989
-
2990
-
2991
-
2992
-    /**
2993
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
2994
-     *
2995
-     * @param int|string $id
2996
-     * @return boolean
2997
-     * @throws EE_Error
2998
-     */
2999
-    public function exists_by_ID($id)
3000
-    {
3001
-        return $this->exists(
3002
-            array(
3003
-                'default_where_conditions' => EEM_Base::default_where_conditions_none,
3004
-                array(
3005
-                    $this->primary_key_name() => $id,
3006
-                ),
3007
-            )
3008
-        );
3009
-    }
3010
-
3011
-
3012
-
3013
-    /**
3014
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3015
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3016
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3017
-     * on the main table)
3018
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
3019
-     * cases where we want to call it directly rather than via insert().
3020
-     *
3021
-     * @access   protected
3022
-     * @param EE_Table_Base $table
3023
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3024
-     *                                       float
3025
-     * @param int           $new_id          for now we assume only int keys
3026
-     * @throws EE_Error
3027
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3028
-     * @return int ID of new row inserted, or FALSE on failure
3029
-     */
3030
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3031
-    {
3032
-        global $wpdb;
3033
-        $insertion_col_n_values = array();
3034
-        $format_for_insertion = array();
3035
-        $fields_on_table = $this->_get_fields_for_table($table->get_table_alias());
3036
-        foreach ($fields_on_table as $field_name => $field_obj) {
3037
-            // check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3038
-            if ($field_obj->is_auto_increment()) {
3039
-                continue;
3040
-            }
3041
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3042
-            // if the value we want to assign it to is NULL, just don't mention it for the insertion
3043
-            if ($prepared_value !== null) {
3044
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3045
-                $format_for_insertion[] = $field_obj->get_wpdb_data_type();
3046
-            }
3047
-        }
3048
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3049
-            // its not the main table, so we should have already saved the main table's PK which we just inserted
3050
-            // so add the fk to the main table as a column
3051
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3052
-            $format_for_insertion[] = '%d';// yes right now we're only allowing these foreign keys to be INTs
3053
-        }
3054
-        // insert the new entry
3055
-        $result = $this->_do_wpdb_query(
3056
-            'insert',
3057
-            array($table->get_table_name(), $insertion_col_n_values, $format_for_insertion)
3058
-        );
3059
-        if ($result === false) {
3060
-            return false;
3061
-        }
3062
-        // ok, now what do we return for the ID of the newly-inserted thing?
3063
-        if ($this->has_primary_key_field()) {
3064
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3065
-                return $wpdb->insert_id;
3066
-            }
3067
-            // it's not an auto-increment primary key, so
3068
-            // it must have been supplied
3069
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3070
-        }
3071
-        // we can't return a  primary key because there is none. instead return
3072
-        // a unique string indicating this model
3073
-        return $this->get_index_primary_key_string($fields_n_values);
3074
-    }
3075
-
3076
-
3077
-
3078
-    /**
3079
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3080
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3081
-     * and there is no default, we pass it along. WPDB will take care of it)
3082
-     *
3083
-     * @param EE_Model_Field_Base $field_obj
3084
-     * @param array               $fields_n_values
3085
-     * @return mixed string|int|float depending on what the table column will be expecting
3086
-     * @throws EE_Error
3087
-     */
3088
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3089
-    {
3090
-        // if this field doesn't allow nullable, don't allow it
3091
-        if (
3092
-            ! $field_obj->is_nullable()
3093
-            && (
3094
-                ! isset($fields_n_values[ $field_obj->get_name() ])
3095
-                || $fields_n_values[ $field_obj->get_name() ] === null
3096
-            )
3097
-        ) {
3098
-            $fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3099
-        }
3100
-        $unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3101
-            ? $fields_n_values[ $field_obj->get_name() ]
3102
-            : null;
3103
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3104
-    }
3105
-
3106
-
3107
-
3108
-    /**
3109
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3110
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3111
-     * the field's prepare_for_set() method.
3112
-     *
3113
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3114
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3115
-     *                                   top of file)
3116
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3117
-     *                                   $value is a custom selection
3118
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3119
-     */
3120
-    private function _prepare_value_for_use_in_db($value, $field)
3121
-    {
3122
-        if ($field && $field instanceof EE_Model_Field_Base) {
3123
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3124
-            switch ($this->_values_already_prepared_by_model_object) {
3125
-                /** @noinspection PhpMissingBreakStatementInspection */
3126
-                case self::not_prepared_by_model_object:
3127
-                    $value = $field->prepare_for_set($value);
3128
-                // purposefully left out "return"
3129
-                // no break
3130
-                case self::prepared_by_model_object:
3131
-                    /** @noinspection SuspiciousAssignmentsInspection */
3132
-                    $value = $field->prepare_for_use_in_db($value);
3133
-                    // no break
3134
-                case self::prepared_for_use_in_db:
3135
-                    // leave the value alone
3136
-            }
3137
-            return $value;
3138
-            // phpcs:enable
3139
-        }
3140
-        return $value;
3141
-    }
3142
-
3143
-
3144
-
3145
-    /**
3146
-     * Returns the main table on this model
3147
-     *
3148
-     * @return EE_Primary_Table
3149
-     * @throws EE_Error
3150
-     */
3151
-    protected function _get_main_table()
3152
-    {
3153
-        foreach ($this->_tables as $table) {
3154
-            if ($table instanceof EE_Primary_Table) {
3155
-                return $table;
3156
-            }
3157
-        }
3158
-        throw new EE_Error(sprintf(esc_html__(
3159
-            'There are no main tables on %s. They should be added to _tables array in the constructor',
3160
-            'event_espresso'
3161
-        ), get_class($this)));
3162
-    }
3163
-
3164
-
3165
-
3166
-    /**
3167
-     * table
3168
-     * returns EE_Primary_Table table name
3169
-     *
3170
-     * @return string
3171
-     * @throws EE_Error
3172
-     */
3173
-    public function table()
3174
-    {
3175
-        return $this->_get_main_table()->get_table_name();
3176
-    }
3177
-
3178
-
3179
-
3180
-    /**
3181
-     * table
3182
-     * returns first EE_Secondary_Table table name
3183
-     *
3184
-     * @return string
3185
-     */
3186
-    public function second_table()
3187
-    {
3188
-        // grab second table from tables array
3189
-        $second_table = end($this->_tables);
3190
-        return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3191
-    }
3192
-
3193
-
3194
-
3195
-    /**
3196
-     * get_table_obj_by_alias
3197
-     * returns table name given it's alias
3198
-     *
3199
-     * @param string $table_alias
3200
-     * @return EE_Primary_Table | EE_Secondary_Table
3201
-     */
3202
-    public function get_table_obj_by_alias($table_alias = '')
3203
-    {
3204
-        return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3205
-    }
3206
-
3207
-
3208
-
3209
-    /**
3210
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3211
-     *
3212
-     * @return EE_Secondary_Table[]
3213
-     */
3214
-    protected function _get_other_tables()
3215
-    {
3216
-        $other_tables = array();
3217
-        foreach ($this->_tables as $table_alias => $table) {
3218
-            if ($table instanceof EE_Secondary_Table) {
3219
-                $other_tables[ $table_alias ] = $table;
3220
-            }
3221
-        }
3222
-        return $other_tables;
3223
-    }
3224
-
3225
-
3226
-
3227
-    /**
3228
-     * Finds all the fields that correspond to the given table
3229
-     *
3230
-     * @param string $table_alias , array key in EEM_Base::_tables
3231
-     * @return EE_Model_Field_Base[]
3232
-     */
3233
-    public function _get_fields_for_table($table_alias)
3234
-    {
3235
-        return $this->_fields[ $table_alias ];
3236
-    }
3237
-
3238
-
3239
-
3240
-    /**
3241
-     * Recurses through all the where parameters, and finds all the related models we'll need
3242
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3243
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3244
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3245
-     * related Registration, Transaction, and Payment models.
3246
-     *
3247
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3248
-     * @return EE_Model_Query_Info_Carrier
3249
-     * @throws EE_Error
3250
-     */
3251
-    public function _extract_related_models_from_query($query_params)
3252
-    {
3253
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3254
-        if (array_key_exists(0, $query_params)) {
3255
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3256
-        }
3257
-        if (array_key_exists('group_by', $query_params)) {
3258
-            if (is_array($query_params['group_by'])) {
3259
-                $this->_extract_related_models_from_sub_params_array_values(
3260
-                    $query_params['group_by'],
3261
-                    $query_info_carrier,
3262
-                    'group_by'
3263
-                );
3264
-            } elseif (! empty($query_params['group_by'])) {
3265
-                $this->_extract_related_model_info_from_query_param(
3266
-                    $query_params['group_by'],
3267
-                    $query_info_carrier,
3268
-                    'group_by'
3269
-                );
3270
-            }
3271
-        }
3272
-        if (array_key_exists('having', $query_params)) {
3273
-            $this->_extract_related_models_from_sub_params_array_keys(
3274
-                $query_params[0],
3275
-                $query_info_carrier,
3276
-                'having'
3277
-            );
3278
-        }
3279
-        if (array_key_exists('order_by', $query_params)) {
3280
-            if (is_array($query_params['order_by'])) {
3281
-                $this->_extract_related_models_from_sub_params_array_keys(
3282
-                    $query_params['order_by'],
3283
-                    $query_info_carrier,
3284
-                    'order_by'
3285
-                );
3286
-            } elseif (! empty($query_params['order_by'])) {
3287
-                $this->_extract_related_model_info_from_query_param(
3288
-                    $query_params['order_by'],
3289
-                    $query_info_carrier,
3290
-                    'order_by'
3291
-                );
3292
-            }
3293
-        }
3294
-        if (array_key_exists('force_join', $query_params)) {
3295
-            $this->_extract_related_models_from_sub_params_array_values(
3296
-                $query_params['force_join'],
3297
-                $query_info_carrier,
3298
-                'force_join'
3299
-            );
3300
-        }
3301
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3302
-        return $query_info_carrier;
3303
-    }
3304
-
3305
-
3306
-
3307
-    /**
3308
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3309
-     *
3310
-     * @param array                       $sub_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3311
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3312
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3313
-     * @throws EE_Error
3314
-     * @return \EE_Model_Query_Info_Carrier
3315
-     */
3316
-    private function _extract_related_models_from_sub_params_array_keys(
3317
-        $sub_query_params,
3318
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3319
-        $query_param_type
3320
-    ) {
3321
-        if (! empty($sub_query_params)) {
3322
-            $sub_query_params = (array) $sub_query_params;
3323
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3324
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3325
-                $this->_extract_related_model_info_from_query_param(
3326
-                    $param,
3327
-                    $model_query_info_carrier,
3328
-                    $query_param_type
3329
-                );
3330
-                // if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3331
-                // indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3332
-                // extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3333
-                // of array('Registration.TXN_ID'=>23)
3334
-                $query_param_sans_stars = $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3335
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3336
-                    if (! is_array($possibly_array_of_params)) {
3337
-                        throw new EE_Error(sprintf(
3338
-                            esc_html__(
3339
-                                "You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3340
-                                "event_espresso"
3341
-                            ),
3342
-                            $param,
3343
-                            $possibly_array_of_params
3344
-                        ));
3345
-                    }
3346
-                    $this->_extract_related_models_from_sub_params_array_keys(
3347
-                        $possibly_array_of_params,
3348
-                        $model_query_info_carrier,
3349
-                        $query_param_type
3350
-                    );
3351
-                } elseif (
3352
-                    $query_param_type === 0 // ie WHERE
3353
-                          && is_array($possibly_array_of_params)
3354
-                          && isset($possibly_array_of_params[2])
3355
-                          && $possibly_array_of_params[2] == true
3356
-                ) {
3357
-                    // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3358
-                    // indicating that $possible_array_of_params[1] is actually a field name,
3359
-                    // from which we should extract query parameters!
3360
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3361
-                        throw new EE_Error(sprintf(esc_html__(
3362
-                            "Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3363
-                            "event_espresso"
3364
-                        ), $query_param_type, implode(",", $possibly_array_of_params)));
3365
-                    }
3366
-                    $this->_extract_related_model_info_from_query_param(
3367
-                        $possibly_array_of_params[1],
3368
-                        $model_query_info_carrier,
3369
-                        $query_param_type
3370
-                    );
3371
-                }
3372
-            }
3373
-        }
3374
-        return $model_query_info_carrier;
3375
-    }
3376
-
3377
-
3378
-
3379
-    /**
3380
-     * For extracting related models from forced_joins, where the array values contain the info about what
3381
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3382
-     *
3383
-     * @param array                       $sub_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3384
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3385
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3386
-     * @throws EE_Error
3387
-     * @return \EE_Model_Query_Info_Carrier
3388
-     */
3389
-    private function _extract_related_models_from_sub_params_array_values(
3390
-        $sub_query_params,
3391
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3392
-        $query_param_type
3393
-    ) {
3394
-        if (! empty($sub_query_params)) {
3395
-            if (! is_array($sub_query_params)) {
3396
-                throw new EE_Error(sprintf(
3397
-                    esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3398
-                    $sub_query_params
3399
-                ));
3400
-            }
3401
-            foreach ($sub_query_params as $param) {
3402
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3403
-                $this->_extract_related_model_info_from_query_param(
3404
-                    $param,
3405
-                    $model_query_info_carrier,
3406
-                    $query_param_type
3407
-                );
3408
-            }
3409
-        }
3410
-        return $model_query_info_carrier;
3411
-    }
3412
-
3413
-
3414
-    /**
3415
-     * Extract all the query parts from  model query params
3416
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3417
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3418
-     * but use them in a different order. Eg, we need to know what models we are querying
3419
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3420
-     * other models before we can finalize the where clause SQL.
3421
-     *
3422
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3423
-     * @throws EE_Error
3424
-     * @return EE_Model_Query_Info_Carrier
3425
-     * @throws ModelConfigurationException
3426
-     */
3427
-    public function _create_model_query_info_carrier($query_params)
3428
-    {
3429
-        if (! is_array($query_params)) {
3430
-            EE_Error::doing_it_wrong(
3431
-                'EEM_Base::_create_model_query_info_carrier',
3432
-                sprintf(
3433
-                    esc_html__(
3434
-                        '$query_params should be an array, you passed a variable of type %s',
3435
-                        'event_espresso'
3436
-                    ),
3437
-                    gettype($query_params)
3438
-                ),
3439
-                '4.6.0'
3440
-            );
3441
-            $query_params = array();
3442
-        }
3443
-        $query_params[0] = isset($query_params[0]) ? $query_params[0] : array();
3444
-        // first check if we should alter the query to account for caps or not
3445
-        // because the caps might require us to do extra joins
3446
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3447
-            $query_params[0] = array_replace_recursive(
3448
-                $query_params[0],
3449
-                $this->caps_where_conditions($query_params['caps'])
3450
-            );
3451
-        }
3452
-
3453
-        // check if we should alter the query to remove data related to protected
3454
-        // custom post types
3455
-        if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3456
-            $where_param_key_for_password = $this->modelChainAndPassword();
3457
-            // only include if related to a cpt where no password has been set
3458
-            $query_params[0]['OR*nopassword'] = array(
3459
-                $where_param_key_for_password => '',
3460
-                $where_param_key_for_password . '*' => array('IS_NULL')
3461
-            );
3462
-        }
3463
-        $query_object = $this->_extract_related_models_from_query($query_params);
3464
-        // verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3465
-        foreach ($query_params[0] as $key => $value) {
3466
-            if (is_int($key)) {
3467
-                throw new EE_Error(
3468
-                    sprintf(
3469
-                        esc_html__(
3470
-                            "WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3471
-                            "event_espresso"
3472
-                        ),
3473
-                        $key,
3474
-                        var_export($value, true),
3475
-                        var_export($query_params, true),
3476
-                        get_class($this)
3477
-                    )
3478
-                );
3479
-            }
3480
-        }
3481
-        if (
3482
-            array_key_exists('default_where_conditions', $query_params)
3483
-            && ! empty($query_params['default_where_conditions'])
3484
-        ) {
3485
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3486
-        } else {
3487
-            $use_default_where_conditions = EEM_Base::default_where_conditions_all;
3488
-        }
3489
-        $query_params[0] = array_merge(
3490
-            $this->_get_default_where_conditions_for_models_in_query(
3491
-                $query_object,
3492
-                $use_default_where_conditions,
3493
-                $query_params[0]
3494
-            ),
3495
-            $query_params[0]
3496
-        );
3497
-        $query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3498
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3499
-        // So we need to setup a subquery and use that for the main join.
3500
-        // Note for now this only works on the primary table for the model.
3501
-        // So for instance, you could set the limit array like this:
3502
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3503
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3504
-            $query_object->set_main_model_join_sql(
3505
-                $this->_construct_limit_join_select(
3506
-                    $query_params['on_join_limit'][0],
3507
-                    $query_params['on_join_limit'][1]
3508
-                )
3509
-            );
3510
-        }
3511
-        // set limit
3512
-        if (array_key_exists('limit', $query_params)) {
3513
-            if (is_array($query_params['limit'])) {
3514
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3515
-                    $e = sprintf(
3516
-                        esc_html__(
3517
-                            "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3518
-                            "event_espresso"
3519
-                        ),
3520
-                        http_build_query($query_params['limit'])
3521
-                    );
3522
-                    throw new EE_Error($e . "|" . $e);
3523
-                }
3524
-                // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3525
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3526
-            } elseif (! empty($query_params['limit'])) {
3527
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3528
-            }
3529
-        }
3530
-        // set order by
3531
-        if (array_key_exists('order_by', $query_params)) {
3532
-            if (is_array($query_params['order_by'])) {
3533
-                // if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3534
-                // specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3535
-                // including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3536
-                if (array_key_exists('order', $query_params)) {
3537
-                    throw new EE_Error(
3538
-                        sprintf(
3539
-                            esc_html__(
3540
-                                "In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3541
-                                "event_espresso"
3542
-                            ),
3543
-                            get_class($this),
3544
-                            implode(", ", array_keys($query_params['order_by'])),
3545
-                            implode(", ", $query_params['order_by']),
3546
-                            $query_params['order']
3547
-                        )
3548
-                    );
3549
-                }
3550
-                $this->_extract_related_models_from_sub_params_array_keys(
3551
-                    $query_params['order_by'],
3552
-                    $query_object,
3553
-                    'order_by'
3554
-                );
3555
-                // assume it's an array of fields to order by
3556
-                $order_array = array();
3557
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3558
-                    $order = $this->_extract_order($order);
3559
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3560
-                }
3561
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3562
-            } elseif (! empty($query_params['order_by'])) {
3563
-                $this->_extract_related_model_info_from_query_param(
3564
-                    $query_params['order_by'],
3565
-                    $query_object,
3566
-                    'order',
3567
-                    $query_params['order_by']
3568
-                );
3569
-                $order = isset($query_params['order'])
3570
-                    ? $this->_extract_order($query_params['order'])
3571
-                    : 'DESC';
3572
-                $query_object->set_order_by_sql(
3573
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3574
-                );
3575
-            }
3576
-        }
3577
-        // if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3578
-        if (
3579
-            ! array_key_exists('order_by', $query_params)
3580
-            && array_key_exists('order', $query_params)
3581
-            && ! empty($query_params['order'])
3582
-        ) {
3583
-            $pk_field = $this->get_primary_key_field();
3584
-            $order = $this->_extract_order($query_params['order']);
3585
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3586
-        }
3587
-        // set group by
3588
-        if (array_key_exists('group_by', $query_params)) {
3589
-            if (is_array($query_params['group_by'])) {
3590
-                // it's an array, so assume we'll be grouping by a bunch of stuff
3591
-                $group_by_array = array();
3592
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3593
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3594
-                }
3595
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3596
-            } elseif (! empty($query_params['group_by'])) {
3597
-                $query_object->set_group_by_sql(
3598
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3599
-                );
3600
-            }
3601
-        }
3602
-        // set having
3603
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3604
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3605
-        }
3606
-        // now, just verify they didn't pass anything wack
3607
-        foreach ($query_params as $query_key => $query_value) {
3608
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3609
-                throw new EE_Error(
3610
-                    sprintf(
3611
-                        esc_html__(
3612
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3613
-                            'event_espresso'
3614
-                        ),
3615
-                        $query_key,
3616
-                        get_class($this),
3617
-                        //                      print_r( $this->_allowed_query_params, TRUE )
3618
-                        implode(',', $this->_allowed_query_params)
3619
-                    )
3620
-                );
3621
-            }
3622
-        }
3623
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3624
-        if (empty($main_model_join_sql)) {
3625
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3626
-        }
3627
-        return $query_object;
3628
-    }
3629
-
3630
-
3631
-
3632
-    /**
3633
-     * Gets the where conditions that should be imposed on the query based on the
3634
-     * context (eg reading frontend, backend, edit or delete).
3635
-     *
3636
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3637
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3638
-     * @throws EE_Error
3639
-     */
3640
-    public function caps_where_conditions($context = self::caps_read)
3641
-    {
3642
-        EEM_Base::verify_is_valid_cap_context($context);
3643
-        $cap_where_conditions = array();
3644
-        $cap_restrictions = $this->caps_missing($context);
3645
-        /**
3646
-         * @var $cap_restrictions EE_Default_Where_Conditions[]
3647
-         */
3648
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3649
-            $cap_where_conditions = array_replace_recursive(
3650
-                $cap_where_conditions,
3651
-                $restriction_if_no_cap->get_default_where_conditions()
3652
-            );
3653
-        }
3654
-        return apply_filters(
3655
-            'FHEE__EEM_Base__caps_where_conditions__return',
3656
-            $cap_where_conditions,
3657
-            $this,
3658
-            $context,
3659
-            $cap_restrictions
3660
-        );
3661
-    }
3662
-
3663
-
3664
-
3665
-    /**
3666
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3667
-     * otherwise throws an exception
3668
-     *
3669
-     * @param string $should_be_order_string
3670
-     * @return string either ASC, asc, DESC or desc
3671
-     * @throws EE_Error
3672
-     */
3673
-    private function _extract_order($should_be_order_string)
3674
-    {
3675
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3676
-            return $should_be_order_string;
3677
-        }
3678
-        throw new EE_Error(
3679
-            sprintf(
3680
-                esc_html__(
3681
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3682
-                    "event_espresso"
3683
-                ),
3684
-                get_class($this),
3685
-                $should_be_order_string
3686
-            )
3687
-        );
3688
-    }
3689
-
3690
-
3691
-
3692
-    /**
3693
-     * Looks at all the models which are included in this query, and asks each
3694
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3695
-     * so they can be merged
3696
-     *
3697
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3698
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3699
-     *                                                                  'none' means NO default where conditions will
3700
-     *                                                                  be used AT ALL during this query.
3701
-     *                                                                  'other_models_only' means default where
3702
-     *                                                                  conditions from other models will be used, but
3703
-     *                                                                  not for this primary model. 'all', the default,
3704
-     *                                                                  means default where conditions will apply as
3705
-     *                                                                  normal
3706
-     * @param array                       $where_query_params           @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3707
-     * @throws EE_Error
3708
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3709
-     */
3710
-    private function _get_default_where_conditions_for_models_in_query(
3711
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3712
-        $use_default_where_conditions = EEM_Base::default_where_conditions_all,
3713
-        $where_query_params = array()
3714
-    ) {
3715
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3716
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3717
-            throw new EE_Error(sprintf(
3718
-                esc_html__(
3719
-                    "You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3720
-                    "event_espresso"
3721
-                ),
3722
-                $use_default_where_conditions,
3723
-                implode(", ", $allowed_used_default_where_conditions_values)
3724
-            ));
3725
-        }
3726
-        $universal_query_params = array();
3727
-        if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3728
-            $universal_query_params = $this->_get_default_where_conditions();
3729
-        } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3730
-            $universal_query_params = $this->_get_minimum_where_conditions();
3731
-        }
3732
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3733
-            $related_model = $this->get_related_model_obj($model_name);
3734
-            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3735
-                $related_model_universal_where_params = $related_model->_get_default_where_conditions($model_relation_path);
3736
-            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3737
-                $related_model_universal_where_params = $related_model->_get_minimum_where_conditions($model_relation_path);
3738
-            } else {
3739
-                // we don't want to add full or even minimum default where conditions from this model, so just continue
3740
-                continue;
3741
-            }
3742
-            $overrides = $this->_override_defaults_or_make_null_friendly(
3743
-                $related_model_universal_where_params,
3744
-                $where_query_params,
3745
-                $related_model,
3746
-                $model_relation_path
3747
-            );
3748
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3749
-                $universal_query_params,
3750
-                $overrides
3751
-            );
3752
-        }
3753
-        return $universal_query_params;
3754
-    }
3755
-
3756
-
3757
-
3758
-    /**
3759
-     * Determines whether or not we should use default where conditions for the model in question
3760
-     * (this model, or other related models).
3761
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3762
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3763
-     * We should use default where conditions on related models when they requested to use default where conditions
3764
-     * on all models, or specifically just on other related models
3765
-     * @param      $default_where_conditions_value
3766
-     * @param bool $for_this_model false means this is for OTHER related models
3767
-     * @return bool
3768
-     */
3769
-    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3770
-    {
3771
-        return (
3772
-                   $for_this_model
3773
-                   && in_array(
3774
-                       $default_where_conditions_value,
3775
-                       array(
3776
-                           EEM_Base::default_where_conditions_all,
3777
-                           EEM_Base::default_where_conditions_this_only,
3778
-                           EEM_Base::default_where_conditions_minimum_others,
3779
-                       ),
3780
-                       true
3781
-                   )
3782
-               )
3783
-               || (
3784
-                   ! $for_this_model
3785
-                   && in_array(
3786
-                       $default_where_conditions_value,
3787
-                       array(
3788
-                           EEM_Base::default_where_conditions_all,
3789
-                           EEM_Base::default_where_conditions_others_only,
3790
-                       ),
3791
-                       true
3792
-                   )
3793
-               );
3794
-    }
3795
-
3796
-    /**
3797
-     * Determines whether or not we should use default minimum conditions for the model in question
3798
-     * (this model, or other related models).
3799
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3800
-     * where conditions.
3801
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3802
-     * on this model or others
3803
-     * @param      $default_where_conditions_value
3804
-     * @param bool $for_this_model false means this is for OTHER related models
3805
-     * @return bool
3806
-     */
3807
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3808
-    {
3809
-        return (
3810
-                   $for_this_model
3811
-                   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3812
-               )
3813
-               || (
3814
-                   ! $for_this_model
3815
-                   && in_array(
3816
-                       $default_where_conditions_value,
3817
-                       array(
3818
-                           EEM_Base::default_where_conditions_minimum_others,
3819
-                           EEM_Base::default_where_conditions_minimum_all,
3820
-                       ),
3821
-                       true
3822
-                   )
3823
-               );
3824
-    }
3825
-
3826
-
3827
-    /**
3828
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3829
-     * then we also add a special where condition which allows for that model's primary key
3830
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3831
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3832
-     *
3833
-     * @param array    $default_where_conditions
3834
-     * @param array    $provided_where_conditions
3835
-     * @param EEM_Base $model
3836
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3837
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3838
-     * @throws EE_Error
3839
-     */
3840
-    private function _override_defaults_or_make_null_friendly(
3841
-        $default_where_conditions,
3842
-        $provided_where_conditions,
3843
-        $model,
3844
-        $model_relation_path
3845
-    ) {
3846
-        $null_friendly_where_conditions = array();
3847
-        $none_overridden = true;
3848
-        $or_condition_key_for_defaults = 'OR*' . get_class($model);
3849
-        foreach ($default_where_conditions as $key => $val) {
3850
-            if (isset($provided_where_conditions[ $key ])) {
3851
-                $none_overridden = false;
3852
-            } else {
3853
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3854
-            }
3855
-        }
3856
-        if ($none_overridden && $default_where_conditions) {
3857
-            if ($model->has_primary_key_field()) {
3858
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3859
-                                                                                . "."
3860
-                                                                                . $model->primary_key_name() ] = array('IS NULL');
3861
-            }/*else{
42
+	/**
43
+	 * Flag to indicate whether the values provided to EEM_Base have already been prepared
44
+	 * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
45
+	 * They almost always WILL NOT, but it's not necessarily a requirement.
46
+	 * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
47
+	 *
48
+	 * @var boolean
49
+	 */
50
+	private $_values_already_prepared_by_model_object = 0;
51
+
52
+	/**
53
+	 * when $_values_already_prepared_by_model_object equals this, we assume
54
+	 * the data is just like form input that needs to have the model fields'
55
+	 * prepare_for_set and prepare_for_use_in_db called on it
56
+	 */
57
+	const not_prepared_by_model_object = 0;
58
+
59
+	/**
60
+	 * when $_values_already_prepared_by_model_object equals this, we
61
+	 * assume this value is coming from a model object and doesn't need to have
62
+	 * prepare_for_set called on it, just prepare_for_use_in_db is used
63
+	 */
64
+	const prepared_by_model_object = 1;
65
+
66
+	/**
67
+	 * when $_values_already_prepared_by_model_object equals this, we assume
68
+	 * the values are already to be used in the database (ie no processing is done
69
+	 * on them by the model's fields)
70
+	 */
71
+	const prepared_for_use_in_db = 2;
72
+
73
+
74
+	protected $singular_item = 'Item';
75
+
76
+	protected $plural_item   = 'Items';
77
+
78
+	/**
79
+	 * @type EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
80
+	 */
81
+	protected $_tables;
82
+
83
+	/**
84
+	 * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
85
+	 * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
86
+	 * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
87
+	 *
88
+	 * @var EE_Model_Field_Base[][] $_fields
89
+	 */
90
+	protected $_fields;
91
+
92
+	/**
93
+	 * array of different kinds of relations
94
+	 *
95
+	 * @var EE_Model_Relation_Base[] $_model_relations
96
+	 */
97
+	protected $_model_relations = [];
98
+
99
+	/**
100
+	 * @var EE_Index[] $_indexes
101
+	 */
102
+	protected $_indexes = array();
103
+
104
+	/**
105
+	 * Default strategy for getting where conditions on this model. This strategy is used to get default
106
+	 * where conditions which are added to get_all, update, and delete queries. They can be overridden
107
+	 * by setting the same columns as used in these queries in the query yourself.
108
+	 *
109
+	 * @var EE_Default_Where_Conditions
110
+	 */
111
+	protected $_default_where_conditions_strategy;
112
+
113
+	/**
114
+	 * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
115
+	 * This is particularly useful when you want something between 'none' and 'default'
116
+	 *
117
+	 * @var EE_Default_Where_Conditions
118
+	 */
119
+	protected $_minimum_where_conditions_strategy;
120
+
121
+	/**
122
+	 * String describing how to find the "owner" of this model's objects.
123
+	 * When there is a foreign key on this model to the wp_users table, this isn't needed.
124
+	 * But when there isn't, this indicates which related model, or transiently-related model,
125
+	 * has the foreign key to the wp_users table.
126
+	 * Eg, for EEM_Registration this would be 'Event' because registrations are directly
127
+	 * related to events, and events have a foreign key to wp_users.
128
+	 * On EEM_Transaction, this would be 'Transaction.Event'
129
+	 *
130
+	 * @var string
131
+	 */
132
+	protected $_model_chain_to_wp_user = '';
133
+
134
+	/**
135
+	 * String describing how to find the model with a password controlling access to this model. This property has the
136
+	 * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
137
+	 * This value is the path of models to follow to arrive at the model with the password field.
138
+	 * If it is an empty string, it means this model has the password field. If it is null, it means there is no
139
+	 * model with a password that should affect reading this on the front-end.
140
+	 * Eg this is an empty string for the Event model because it has a password.
141
+	 * This is null for the Registration model, because its event's password has no bearing on whether
142
+	 * you can read the registration or not on the front-end (it just depends on your capabilities.)
143
+	 * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
144
+	 * should hide tickets for datetimes for events that have a password set.
145
+	 * @var string |null
146
+	 */
147
+	protected $model_chain_to_password = null;
148
+
149
+	/**
150
+	 * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
151
+	 * don't need it (particularly CPT models)
152
+	 *
153
+	 * @var bool
154
+	 */
155
+	protected $_ignore_where_strategy = false;
156
+
157
+	/**
158
+	 * String used in caps relating to this model. Eg, if the caps relating to this
159
+	 * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
160
+	 *
161
+	 * @var string. If null it hasn't been initialized yet. If false then we
162
+	 * have indicated capabilities don't apply to this
163
+	 */
164
+	protected $_caps_slug = null;
165
+
166
+	/**
167
+	 * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
168
+	 * and next-level keys are capability names, and each's value is a
169
+	 * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
170
+	 * they specify which context to use (ie, frontend, backend, edit or delete)
171
+	 * and then each capability in the corresponding sub-array that they're missing
172
+	 * adds the where conditions onto the query.
173
+	 *
174
+	 * @var array
175
+	 */
176
+	protected $_cap_restrictions = array(
177
+		self::caps_read       => array(),
178
+		self::caps_read_admin => array(),
179
+		self::caps_edit       => array(),
180
+		self::caps_delete     => array(),
181
+	);
182
+
183
+	/**
184
+	 * Array defining which cap restriction generators to use to create default
185
+	 * cap restrictions to put in EEM_Base::_cap_restrictions.
186
+	 * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
187
+	 * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
188
+	 * automatically set this to false (not just null).
189
+	 *
190
+	 * @var EE_Restriction_Generator_Base[]
191
+	 */
192
+	protected $_cap_restriction_generators = array();
193
+
194
+	/**
195
+	 * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
196
+	 */
197
+	const caps_read       = 'read';
198
+
199
+	const caps_read_admin = 'read_admin';
200
+
201
+	const caps_edit       = 'edit';
202
+
203
+	const caps_delete     = 'delete';
204
+
205
+	/**
206
+	 * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
207
+	 * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
208
+	 * maps to 'read' because when looking for relevant permissions we're going to use
209
+	 * 'read' in teh capabilities names like 'ee_read_events' etc.
210
+	 *
211
+	 * @var array
212
+	 */
213
+	protected $_cap_contexts_to_cap_action_map = array(
214
+		self::caps_read       => 'read',
215
+		self::caps_read_admin => 'read',
216
+		self::caps_edit       => 'edit',
217
+		self::caps_delete     => 'delete',
218
+	);
219
+
220
+	/**
221
+	 * Timezone
222
+	 * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
223
+	 * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
224
+	 * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
225
+	 * EE_Datetime_Field data type will have access to it.
226
+	 *
227
+	 * @var string
228
+	 */
229
+	protected $_timezone;
230
+
231
+
232
+	/**
233
+	 * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
234
+	 * multisite.
235
+	 *
236
+	 * @var int
237
+	 */
238
+	protected static $_model_query_blog_id;
239
+
240
+	/**
241
+	 * A copy of _fields, except the array keys are the model names pointed to by
242
+	 * the field
243
+	 *
244
+	 * @var EE_Model_Field_Base[]
245
+	 */
246
+	private $_cache_foreign_key_to_fields = array();
247
+
248
+	/**
249
+	 * Cached list of all the fields on the model, indexed by their name
250
+	 *
251
+	 * @var EE_Model_Field_Base[]
252
+	 */
253
+	private $_cached_fields = null;
254
+
255
+	/**
256
+	 * Cached list of all the fields on the model, except those that are
257
+	 * marked as only pertinent to the database
258
+	 *
259
+	 * @var EE_Model_Field_Base[]
260
+	 */
261
+	private $_cached_fields_non_db_only = null;
262
+
263
+	/**
264
+	 * A cached reference to the primary key for quick lookup
265
+	 *
266
+	 * @var EE_Model_Field_Base
267
+	 */
268
+	private $_primary_key_field = null;
269
+
270
+	/**
271
+	 * Flag indicating whether this model has a primary key or not
272
+	 *
273
+	 * @var boolean
274
+	 */
275
+	protected $_has_primary_key_field = null;
276
+
277
+	/**
278
+	 * array in the format:  [ FK alias => full PK ]
279
+	 * where keys are local column name aliases for foreign keys
280
+	 * and values are the fully qualified column name for the primary key they represent
281
+	 *  ex:
282
+	 *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
283
+	 *
284
+	 * @var array $foreign_key_aliases
285
+	 */
286
+	protected $foreign_key_aliases = [];
287
+
288
+	/**
289
+	 * Whether or not this model is based off a table in WP core only (CPTs should set
290
+	 * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
291
+	 * This should be true for models that deal with data that should exist independent of EE.
292
+	 * For example, if the model can read and insert data that isn't used by EE, this should be true.
293
+	 * It would be false, however, if you could guarantee the model would only interact with EE data,
294
+	 * even if it uses a WP core table (eg event and venue models set this to false for that reason:
295
+	 * they can only read and insert events and venues custom post types, not arbitrary post types)
296
+	 * @var boolean
297
+	 */
298
+	protected $_wp_core_model = false;
299
+
300
+	/**
301
+	 * @var bool stores whether this model has a password field or not.
302
+	 * null until initialized by hasPasswordField()
303
+	 */
304
+	protected $has_password_field;
305
+
306
+	/**
307
+	 * @var EE_Password_Field|null Automatically set when calling getPasswordField()
308
+	 */
309
+	protected $password_field;
310
+
311
+	/**
312
+	 *    List of valid operators that can be used for querying.
313
+	 * The keys are all operators we'll accept, the values are the real SQL
314
+	 * operators used
315
+	 *
316
+	 * @var array
317
+	 */
318
+	protected $_valid_operators = array(
319
+		'='           => '=',
320
+		'<='          => '<=',
321
+		'<'           => '<',
322
+		'>='          => '>=',
323
+		'>'           => '>',
324
+		'!='          => '!=',
325
+		'LIKE'        => 'LIKE',
326
+		'like'        => 'LIKE',
327
+		'NOT_LIKE'    => 'NOT LIKE',
328
+		'not_like'    => 'NOT LIKE',
329
+		'NOT LIKE'    => 'NOT LIKE',
330
+		'not like'    => 'NOT LIKE',
331
+		'IN'          => 'IN',
332
+		'in'          => 'IN',
333
+		'NOT_IN'      => 'NOT IN',
334
+		'not_in'      => 'NOT IN',
335
+		'NOT IN'      => 'NOT IN',
336
+		'not in'      => 'NOT IN',
337
+		'between'     => 'BETWEEN',
338
+		'BETWEEN'     => 'BETWEEN',
339
+		'IS_NOT_NULL' => 'IS NOT NULL',
340
+		'is_not_null' => 'IS NOT NULL',
341
+		'IS NOT NULL' => 'IS NOT NULL',
342
+		'is not null' => 'IS NOT NULL',
343
+		'IS_NULL'     => 'IS NULL',
344
+		'is_null'     => 'IS NULL',
345
+		'IS NULL'     => 'IS NULL',
346
+		'is null'     => 'IS NULL',
347
+		'REGEXP'      => 'REGEXP',
348
+		'regexp'      => 'REGEXP',
349
+		'NOT_REGEXP'  => 'NOT REGEXP',
350
+		'not_regexp'  => 'NOT REGEXP',
351
+		'NOT REGEXP'  => 'NOT REGEXP',
352
+		'not regexp'  => 'NOT REGEXP',
353
+	);
354
+
355
+	/**
356
+	 * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
357
+	 *
358
+	 * @var array
359
+	 */
360
+	protected $_in_style_operators = array('IN', 'NOT IN');
361
+
362
+	/**
363
+	 * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
364
+	 * '12-31-2012'"
365
+	 *
366
+	 * @var array
367
+	 */
368
+	protected $_between_style_operators = array('BETWEEN');
369
+
370
+	/**
371
+	 * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
372
+	 * @var array
373
+	 */
374
+	protected $_like_style_operators = array('LIKE', 'NOT LIKE');
375
+	/**
376
+	 * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
377
+	 * on a join table.
378
+	 *
379
+	 * @var array
380
+	 */
381
+	protected $_null_style_operators = array('IS NOT NULL', 'IS NULL');
382
+
383
+	/**
384
+	 * Allowed values for $query_params['order'] for ordering in queries
385
+	 *
386
+	 * @var array
387
+	 */
388
+	protected $_allowed_order_values = array('asc', 'desc', 'ASC', 'DESC');
389
+
390
+	/**
391
+	 * When these are keys in a WHERE or HAVING clause, they are handled much differently
392
+	 * than regular field names. It is assumed that their values are an array of WHERE conditions
393
+	 *
394
+	 * @var array
395
+	 */
396
+	private $_logic_query_param_keys = array('not', 'and', 'or', 'NOT', 'AND', 'OR');
397
+
398
+	/**
399
+	 * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
400
+	 * 'where', but 'where' clauses are so common that we thought we'd omit it
401
+	 *
402
+	 * @var array
403
+	 */
404
+	private $_allowed_query_params = array(
405
+		0,
406
+		'limit',
407
+		'order_by',
408
+		'group_by',
409
+		'having',
410
+		'force_join',
411
+		'order',
412
+		'on_join_limit',
413
+		'default_where_conditions',
414
+		'caps',
415
+		'extra_selects',
416
+		'exclude_protected',
417
+	);
418
+
419
+	/**
420
+	 * All the data types that can be used in $wpdb->prepare statements.
421
+	 *
422
+	 * @var array
423
+	 */
424
+	private $_valid_wpdb_data_types = array('%d', '%s', '%f');
425
+
426
+	/**
427
+	 * @var EE_Registry $EE
428
+	 */
429
+	protected $EE = null;
430
+
431
+
432
+	/**
433
+	 * Property which, when set, will have this model echo out the next X queries to the page for debugging.
434
+	 *
435
+	 * @var int
436
+	 */
437
+	protected $_show_next_x_db_queries = 0;
438
+
439
+	/**
440
+	 * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
441
+	 * it gets saved on this property as an instance of CustomSelects so those selections can be used in
442
+	 * WHERE, GROUP_BY, etc.
443
+	 *
444
+	 * @var CustomSelects
445
+	 */
446
+	protected $_custom_selections = array();
447
+
448
+	/**
449
+	 * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
450
+	 * caches every model object we've fetched from the DB on this request
451
+	 *
452
+	 * @var array
453
+	 */
454
+	protected $_entity_map;
455
+
456
+	/**
457
+	 * @var LoaderInterface
458
+	 */
459
+	protected static $loader;
460
+
461
+	/**
462
+	 * @var Mirror
463
+	 */
464
+	private static $mirror;
465
+
466
+
467
+
468
+	/**
469
+	 * constant used to show EEM_Base has not yet verified the db on this http request
470
+	 */
471
+	const db_verified_none = 0;
472
+
473
+	/**
474
+	 * constant used to show EEM_Base has verified the EE core db on this http request,
475
+	 * but not the addons' dbs
476
+	 */
477
+	const db_verified_core = 1;
478
+
479
+	/**
480
+	 * constant used to show EEM_Base has verified the addons' dbs (and implicitly
481
+	 * the EE core db too)
482
+	 */
483
+	const db_verified_addons = 2;
484
+
485
+	/**
486
+	 * indicates whether an EEM_Base child has already re-verified the DB
487
+	 * is ok (we don't want to do it repetitively). Should be set to one the constants
488
+	 * looking like EEM_Base::db_verified_*
489
+	 *
490
+	 * @var int - 0 = none, 1 = core, 2 = addons
491
+	 */
492
+	protected static $_db_verification_level = EEM_Base::db_verified_none;
493
+
494
+	/**
495
+	 * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
496
+	 *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
497
+	 *        registrations for non-trashed tickets for non-trashed datetimes)
498
+	 */
499
+	const default_where_conditions_all = 'all';
500
+
501
+	/**
502
+	 * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
503
+	 *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
504
+	 *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
505
+	 *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
506
+	 *        models which share tables with other models, this can return data for the wrong model.
507
+	 */
508
+	const default_where_conditions_this_only = 'this_model_only';
509
+
510
+	/**
511
+	 * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
512
+	 *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
513
+	 *        return all registrations related to non-trashed tickets and non-trashed datetimes)
514
+	 */
515
+	const default_where_conditions_others_only = 'other_models_only';
516
+
517
+	/**
518
+	 * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
519
+	 *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
520
+	 *        their table with other models, like the Event and Venue models. For example, when querying for events
521
+	 *        ordered by their venues' name, this will be sure to only return real events with associated real venues
522
+	 *        (regardless of whether those events and venues are trashed)
523
+	 *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
524
+	 *        events.
525
+	 */
526
+	const default_where_conditions_minimum_all = 'minimum';
527
+
528
+	/**
529
+	 * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
530
+	 *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
531
+	 *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
532
+	 *        not)
533
+	 */
534
+	const default_where_conditions_minimum_others = 'full_this_minimum_others';
535
+
536
+	/**
537
+	 * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
538
+	 *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
539
+	 *        it's possible it will return table entries for other models. You should use
540
+	 *        EEM_Base::default_where_conditions_minimum_all instead.
541
+	 */
542
+	const default_where_conditions_none = 'none';
543
+
544
+
545
+
546
+	/**
547
+	 * About all child constructors:
548
+	 * they should define the _tables, _fields and _model_relations arrays.
549
+	 * Should ALWAYS be called after child constructor.
550
+	 * In order to make the child constructors to be as simple as possible, this parent constructor
551
+	 * finalizes constructing all the object's attributes.
552
+	 * Generally, rather than requiring a child to code
553
+	 * $this->_tables = array(
554
+	 *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
555
+	 *        ...);
556
+	 *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
557
+	 * each EE_Table has a function to set the table's alias after the constructor, using
558
+	 * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
559
+	 * do something similar.
560
+	 *
561
+	 * @param null $timezone
562
+	 * @throws EE_Error
563
+	 */
564
+	protected function __construct($timezone = null)
565
+	{
566
+		// check that the model has not been loaded too soon
567
+		if (! did_action('AHEE__EE_System__load_espresso_addons')) {
568
+			throw new EE_Error(
569
+				sprintf(
570
+					esc_html__(
571
+						'The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
572
+						'event_espresso'
573
+					),
574
+					get_class($this)
575
+				)
576
+			);
577
+		}
578
+		/**
579
+		 * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
580
+		 */
581
+		if (empty(EEM_Base::$_model_query_blog_id)) {
582
+			EEM_Base::set_model_query_blog_id();
583
+		}
584
+		/**
585
+		 * Filters the list of tables on a model. It is best to NOT use this directly and instead
586
+		 * just use EE_Register_Model_Extension
587
+		 *
588
+		 * @var EE_Table_Base[] $_tables
589
+		 */
590
+		$this->_tables = (array) apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
591
+		foreach ($this->_tables as $table_alias => $table_obj) {
592
+			/** @var $table_obj EE_Table_Base */
593
+			$table_obj->_construct_finalize_with_alias($table_alias);
594
+			if ($table_obj instanceof EE_Secondary_Table) {
595
+				/** @var $table_obj EE_Secondary_Table */
596
+				$table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
597
+			}
598
+		}
599
+		/**
600
+		 * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
601
+		 * EE_Register_Model_Extension
602
+		 *
603
+		 * @param EE_Model_Field_Base[] $_fields
604
+		 */
605
+		$this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
606
+		$this->_invalidate_field_caches();
607
+		foreach ($this->_fields as $table_alias => $fields_for_table) {
608
+			if (! array_key_exists($table_alias, $this->_tables)) {
609
+				throw new EE_Error(sprintf(esc_html__(
610
+					"Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
611
+					'event_espresso'
612
+				), $table_alias, implode(",", $this->_fields)));
613
+			}
614
+			foreach ($fields_for_table as $field_name => $field_obj) {
615
+				/** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
616
+				// primary key field base has a slightly different _construct_finalize
617
+				/** @var $field_obj EE_Model_Field_Base */
618
+				$field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
619
+			}
620
+		}
621
+		// everything is related to Extra_Meta
622
+		if (get_class($this) !== 'EEM_Extra_Meta') {
623
+			// make extra meta related to everything, but don't block deleting things just
624
+			// because they have related extra meta info. For now just orphan those extra meta
625
+			// in the future we should automatically delete them
626
+			$this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
627
+		}
628
+		// and change logs
629
+		if (get_class($this) !== 'EEM_Change_Log') {
630
+			$this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
631
+		}
632
+		/**
633
+		 * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
634
+		 * EE_Register_Model_Extension
635
+		 *
636
+		 * @param EE_Model_Relation_Base[] $_model_relations
637
+		 */
638
+		$this->_model_relations = (array) apply_filters(
639
+			'FHEE__' . get_class($this) . '__construct__model_relations',
640
+			$this->_model_relations
641
+		);
642
+		foreach ($this->_model_relations as $model_name => $relation_obj) {
643
+			/** @var $relation_obj EE_Model_Relation_Base */
644
+			$relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
645
+		}
646
+		foreach ($this->_indexes as $index_name => $index_obj) {
647
+			$index_obj->_construct_finalize($index_name, $this->get_this_model_name());
648
+		}
649
+		$this->set_timezone($timezone);
650
+		// finalize default where condition strategy, or set default
651
+		if (! $this->_default_where_conditions_strategy) {
652
+			// nothing was set during child constructor, so set default
653
+			$this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
654
+		}
655
+		$this->_default_where_conditions_strategy->_finalize_construct($this);
656
+		if (! $this->_minimum_where_conditions_strategy) {
657
+			// nothing was set during child constructor, so set default
658
+			$this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
659
+		}
660
+		$this->_minimum_where_conditions_strategy->_finalize_construct($this);
661
+		// if the cap slug hasn't been set, and we haven't set it to false on purpose
662
+		// to indicate to NOT set it, set it to the logical default
663
+		if ($this->_caps_slug === null) {
664
+			$this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
665
+		}
666
+		// initialize the standard cap restriction generators if none were specified by the child constructor
667
+		if (is_array($this->_cap_restriction_generators)) {
668
+			foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
669
+				if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
670
+					$this->_cap_restriction_generators[ $cap_context ] = apply_filters(
671
+						'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
672
+						new EE_Restriction_Generator_Protected(),
673
+						$cap_context,
674
+						$this
675
+					);
676
+				}
677
+			}
678
+		}
679
+		// if there are cap restriction generators, use them to make the default cap restrictions
680
+		if (is_array($this->_cap_restriction_generators)) {
681
+			foreach ($this->_cap_restriction_generators as $context => $generator_object) {
682
+				if (! $generator_object) {
683
+					continue;
684
+				}
685
+				if (! $generator_object instanceof EE_Restriction_Generator_Base) {
686
+					throw new EE_Error(
687
+						sprintf(
688
+							esc_html__(
689
+								'Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
690
+								'event_espresso'
691
+							),
692
+							$context,
693
+							$this->get_this_model_name()
694
+						)
695
+					);
696
+				}
697
+				$action = $this->cap_action_for_context($context);
698
+				if (! $generator_object->construction_finalized()) {
699
+					$generator_object->_construct_finalize($this, $action);
700
+				}
701
+			}
702
+		}
703
+		do_action('AHEE__' . get_class($this) . '__construct__end');
704
+	}
705
+
706
+
707
+	/**
708
+	 * @return LoaderInterface
709
+	 * @throws InvalidArgumentException
710
+	 * @throws InvalidDataTypeException
711
+	 * @throws InvalidInterfaceException
712
+	 */
713
+	protected static function getLoader(): LoaderInterface
714
+	{
715
+		if (! EEM_Base::$loader instanceof LoaderInterface) {
716
+			EEM_Base::$loader = LoaderFactory::getLoader();
717
+		}
718
+		return EEM_Base::$loader;
719
+	}
720
+
721
+
722
+	/**
723
+	 * @return Mirror
724
+	 * @since   $VID:$
725
+	 */
726
+	private static function getMirror(): Mirror
727
+	{
728
+		if (! EEM_Base::$mirror instanceof Mirror) {
729
+			EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
730
+		}
731
+		return EEM_Base::$mirror;
732
+	}
733
+
734
+
735
+	/**
736
+	 * @param string $model_class_Name
737
+	 * @param string $timezone
738
+	 * @return array
739
+	 * @throws ReflectionException
740
+	 * @since   $VID:$
741
+	 */
742
+	private static function getModelArguments(string $model_class_Name, string $timezone): array
743
+	{
744
+		$arguments = [$timezone];
745
+		$params    = EEM_Base::getMirror()->getParameters($model_class_Name);
746
+		if (count($params) > 1) {
747
+			if ($params[1]->getName() === 'model_field_factory') {
748
+				$arguments = [
749
+					$timezone,
750
+					EEM_Base::getLoader()->getShared(ModelFieldFactory::class)
751
+				];
752
+			} elseif ($model_class_Name === 'EEM_Form_Section') {
753
+				$arguments = [
754
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
755
+					$timezone
756
+				];
757
+			} elseif ($model_class_Name === 'EEM_Form_Element') {
758
+				$arguments = [
759
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
760
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
761
+					$timezone,
762
+				];
763
+			}
764
+		}
765
+		return $arguments;
766
+	}
767
+
768
+
769
+	/**
770
+	 * This function is a singleton method used to instantiate the Espresso_model object
771
+	 *
772
+	 * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
773
+	 *                                (and any incoming timezone data that gets saved).
774
+	 *                                Note this just sends the timezone info to the date time model field objects.
775
+	 *                                Default is NULL
776
+	 *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
777
+	 * @return static (as in the concrete child class)
778
+	 * @throws EE_Error
779
+	 * @throws ReflectionException
780
+	 */
781
+	public static function instance($timezone = null)
782
+	{
783
+		// check if instance of Espresso_model already exists
784
+		if (! static::$_instance instanceof static) {
785
+			$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
786
+			$model = new static(...$arguments);
787
+			EEM_Base::getLoader()->share(static::class, $model, $arguments);
788
+			static::$_instance = $model;
789
+		}
790
+		// we might have a timezone set, let set_timezone decide what to do with it
791
+		if ($timezone) {
792
+			static::$_instance->set_timezone($timezone);
793
+		}
794
+		// Espresso_model object
795
+		return static::$_instance;
796
+	}
797
+
798
+
799
+
800
+	/**
801
+	 * resets the model and returns it
802
+	 *
803
+	 * @param string|null $timezone
804
+	 * @return EEM_Base|null (if the model was already instantiated, returns it, with
805
+	 * all its properties reset; if it wasn't instantiated, returns null)
806
+	 * @throws EE_Error
807
+	 * @throws ReflectionException
808
+	 * @throws InvalidArgumentException
809
+	 * @throws InvalidDataTypeException
810
+	 * @throws InvalidInterfaceException
811
+	 */
812
+	public static function reset($timezone = null)
813
+	{
814
+		if (! static::$_instance instanceof EEM_Base) {
815
+			return null;
816
+		}
817
+		// Let's NOT swap out the current instance for a new one
818
+		// because if someone has a reference to it, we can't remove their reference.
819
+		// It's best to keep using the same reference but change the original object instead,
820
+		// so reset all its properties to their original values as defined in the class.
821
+		$static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
822
+		foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
823
+			// don't set instance to null like it was originally,
824
+			// but it's static anyways, and we're ignoring static properties (for now at least)
825
+			if (! isset($static_properties[ $property ])) {
826
+				static::$_instance->{$property} = $value;
827
+			}
828
+		}
829
+		// and then directly call its constructor again, like we would if we were creating a new one
830
+		$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
831
+		static::$_instance->__construct(...$arguments);
832
+		return self::instance();
833
+	}
834
+
835
+
836
+	/**
837
+	 * Used to set the $_model_query_blog_id static property.
838
+	 *
839
+	 * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
840
+	 *                      value for get_current_blog_id() will be used.
841
+	 */
842
+	public static function set_model_query_blog_id($blog_id = 0)
843
+	{
844
+		EEM_Base::$_model_query_blog_id = $blog_id > 0
845
+			? (int) $blog_id
846
+			: get_current_blog_id();
847
+	}
848
+
849
+
850
+	/**
851
+	 * Returns whatever is set as the internal $model_query_blog_id.
852
+	 *
853
+	 * @return int
854
+	 */
855
+	public static function get_model_query_blog_id()
856
+	{
857
+		return EEM_Base::$_model_query_blog_id;
858
+	}
859
+
860
+
861
+
862
+	/**
863
+	 * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
864
+	 *
865
+	 * @param  boolean $translated return localized strings or JUST the array.
866
+	 * @return array
867
+	 * @throws EE_Error
868
+	 * @throws InvalidArgumentException
869
+	 * @throws InvalidDataTypeException
870
+	 * @throws InvalidInterfaceException
871
+	 */
872
+	public function status_array($translated = false)
873
+	{
874
+		if (! array_key_exists('Status', $this->_model_relations)) {
875
+			return array();
876
+		}
877
+		$model_name = $this->get_this_model_name();
878
+		$status_type = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
879
+		$stati = EEM_Status::instance()->get_all(array(array('STS_type' => $status_type)));
880
+		$status_array = array();
881
+		foreach ($stati as $status) {
882
+			$status_array[ $status->ID() ] = $status->get('STS_code');
883
+		}
884
+		return $translated
885
+			? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
886
+			: $status_array;
887
+	}
888
+
889
+
890
+
891
+	/**
892
+	 * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
893
+	 *
894
+	 * @param array $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
895
+	 *                             or if you have the development copy of EE you can view this at the path:
896
+	 *                             /docs/G--Model-System/model-query-params.md
897
+	 * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
898
+	 *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object IDs (if there is a primary key on the model.
899
+	 *                                        if not, numerically indexed) Some full examples: get 10 transactions
900
+	 *                                        which have Scottish attendees: EEM_Transaction::instance()->get_all(
901
+	 *                                        array( array(
902
+	 *                                        'OR'=>array(
903
+	 *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
904
+	 *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
905
+	 *                                        )
906
+	 *                                        ),
907
+	 *                                        'limit'=>10,
908
+	 *                                        'group_by'=>'TXN_ID'
909
+	 *                                        ));
910
+	 *                                        get all the answers to the question titled "shirt size" for event with id
911
+	 *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
912
+	 *                                        'Question.QST_display_text'=>'shirt size',
913
+	 *                                        'Registration.Event.EVT_ID'=>12
914
+	 *                                        ),
915
+	 *                                        'order_by'=>array('ANS_value'=>'ASC')
916
+	 *                                        ));
917
+	 * @throws EE_Error
918
+	 */
919
+	public function get_all($query_params = array())
920
+	{
921
+		if (
922
+			isset($query_params['limit'])
923
+			&& ! isset($query_params['group_by'])
924
+		) {
925
+			$query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
926
+		}
927
+		return $this->_create_objects($this->_get_all_wpdb_results($query_params));
928
+	}
929
+
930
+
931
+
932
+	/**
933
+	 * Modifies the query parameters so we only get back model objects
934
+	 * that "belong" to the current user
935
+	 *
936
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
937
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
938
+	 */
939
+	public function alter_query_params_to_only_include_mine($query_params = array())
940
+	{
941
+		$wp_user_field_name = $this->wp_user_field_name();
942
+		if ($wp_user_field_name) {
943
+			$query_params[0][ $wp_user_field_name ] = get_current_user_id();
944
+		}
945
+		return $query_params;
946
+	}
947
+
948
+
949
+
950
+	/**
951
+	 * Returns the name of the field's name that points to the WP_User table
952
+	 *  on this model (or follows the _model_chain_to_wp_user and uses that model's
953
+	 * foreign key to the WP_User table)
954
+	 *
955
+	 * @return string|boolean string on success, boolean false when there is no
956
+	 * foreign key to the WP_User table
957
+	 */
958
+	public function wp_user_field_name()
959
+	{
960
+		try {
961
+			if (! empty($this->_model_chain_to_wp_user)) {
962
+				$models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
963
+				$last_model_name = end($models_to_follow_to_wp_users);
964
+				$model_with_fk_to_wp_users = EE_Registry::instance()->load_model($last_model_name);
965
+				$model_chain_to_wp_user = $this->_model_chain_to_wp_user . '.';
966
+			} else {
967
+				$model_with_fk_to_wp_users = $this;
968
+				$model_chain_to_wp_user = '';
969
+			}
970
+			$wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
971
+			return $model_chain_to_wp_user . $wp_user_field->get_name();
972
+		} catch (EE_Error $e) {
973
+			return false;
974
+		}
975
+	}
976
+
977
+
978
+
979
+	/**
980
+	 * Returns the _model_chain_to_wp_user string, which indicates which related model
981
+	 * (or transiently-related model) has a foreign key to the wp_users table;
982
+	 * useful for finding if model objects of this type are 'owned' by the current user.
983
+	 * This is an empty string when the foreign key is on this model and when it isn't,
984
+	 * but is only non-empty when this model's ownership is indicated by a RELATED model
985
+	 * (or transiently-related model)
986
+	 *
987
+	 * @return string
988
+	 */
989
+	public function model_chain_to_wp_user()
990
+	{
991
+		return $this->_model_chain_to_wp_user;
992
+	}
993
+
994
+
995
+
996
+	/**
997
+	 * Whether this model is 'owned' by a specific wordpress user (even indirectly,
998
+	 * like how registrations don't have a foreign key to wp_users, but the
999
+	 * events they are for are), or is unrelated to wp users.
1000
+	 * generally available
1001
+	 *
1002
+	 * @return boolean
1003
+	 */
1004
+	public function is_owned()
1005
+	{
1006
+		if ($this->model_chain_to_wp_user()) {
1007
+			return true;
1008
+		}
1009
+		try {
1010
+			$this->get_foreign_key_to('WP_User');
1011
+			return true;
1012
+		} catch (EE_Error $e) {
1013
+			return false;
1014
+		}
1015
+	}
1016
+
1017
+
1018
+	/**
1019
+	 * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1020
+	 * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1021
+	 * the model)
1022
+	 *
1023
+	 * @param array  $query_params      @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1024
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1025
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1026
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1027
+	 *                                  override this and set the select to "*", or a specific column name, like
1028
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1029
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1030
+	 *                                  the aliases used to refer to this selection, and values are to be
1031
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1032
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1033
+	 * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1034
+	 * @throws EE_Error
1035
+	 * @throws InvalidArgumentException
1036
+	 */
1037
+	protected function _get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1038
+	{
1039
+		$this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1040
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1041
+		$select_expressions = $columns_to_select === null
1042
+			? $this->_construct_default_select_sql($model_query_info)
1043
+			: '';
1044
+		if ($this->_custom_selections instanceof CustomSelects) {
1045
+			$custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1046
+			$select_expressions .= $select_expressions
1047
+				? ', ' . $custom_expressions
1048
+				: $custom_expressions;
1049
+		}
1050
+
1051
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1052
+		return $this->_do_wpdb_query('get_results', array($SQL, $output));
1053
+	}
1054
+
1055
+
1056
+	/**
1057
+	 * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1058
+	 * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1059
+	 * method of including extra select information.
1060
+	 *
1061
+	 * @param array             $query_params
1062
+	 * @param null|array|string $columns_to_select
1063
+	 * @return null|CustomSelects
1064
+	 * @throws InvalidArgumentException
1065
+	 */
1066
+	protected function getCustomSelection(array $query_params, $columns_to_select = null)
1067
+	{
1068
+		if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1069
+			return null;
1070
+		}
1071
+		$selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1072
+		$selects = is_string($selects) ? explode(',', $selects) : $selects;
1073
+		return new CustomSelects($selects);
1074
+	}
1075
+
1076
+
1077
+
1078
+	/**
1079
+	 * Gets an array of rows from the database just like $wpdb->get_results would,
1080
+	 * but you can use the model query params to more easily
1081
+	 * take care of joins, field preparation etc.
1082
+	 *
1083
+	 * @param array  $query_params      @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1084
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1085
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1086
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1087
+	 *                                  override this and set the select to "*", or a specific column name, like
1088
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1089
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1090
+	 *                                  the aliases used to refer to this selection, and values are to be
1091
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1092
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1093
+	 * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1094
+	 * @throws EE_Error
1095
+	 */
1096
+	public function get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1097
+	{
1098
+		return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1099
+	}
1100
+
1101
+
1102
+
1103
+	/**
1104
+	 * For creating a custom select statement
1105
+	 *
1106
+	 * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1107
+	 *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1108
+	 *                                 SQL, and 1=>is the datatype
1109
+	 * @throws EE_Error
1110
+	 * @return string
1111
+	 */
1112
+	private function _construct_select_from_input($columns_to_select)
1113
+	{
1114
+		if (is_array($columns_to_select)) {
1115
+			$select_sql_array = array();
1116
+			foreach ($columns_to_select as $alias => $selection_and_datatype) {
1117
+				if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1118
+					throw new EE_Error(
1119
+						sprintf(
1120
+							esc_html__(
1121
+								"Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1122
+								'event_espresso'
1123
+							),
1124
+							$selection_and_datatype,
1125
+							$alias
1126
+						)
1127
+					);
1128
+				}
1129
+				if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1130
+					throw new EE_Error(
1131
+						sprintf(
1132
+							esc_html__(
1133
+								"Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1134
+								'event_espresso'
1135
+							),
1136
+							$selection_and_datatype[1],
1137
+							$selection_and_datatype[0],
1138
+							$alias,
1139
+							implode(', ', $this->_valid_wpdb_data_types)
1140
+						)
1141
+					);
1142
+				}
1143
+				$select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1144
+			}
1145
+			$columns_to_select_string = implode(', ', $select_sql_array);
1146
+		} else {
1147
+			$columns_to_select_string = $columns_to_select;
1148
+		}
1149
+		return $columns_to_select_string;
1150
+	}
1151
+
1152
+
1153
+
1154
+	/**
1155
+	 * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1156
+	 *
1157
+	 * @return string
1158
+	 * @throws EE_Error
1159
+	 */
1160
+	public function primary_key_name()
1161
+	{
1162
+		return $this->get_primary_key_field()->get_name();
1163
+	}
1164
+
1165
+
1166
+	/**
1167
+	 * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1168
+	 * If there is no primary key on this model, $id is treated as primary key string
1169
+	 *
1170
+	 * @param mixed $id int or string, depending on the type of the model's primary key
1171
+	 * @return EE_Base_Class|mixed|null
1172
+	 * @throws EE_Error
1173
+	 */
1174
+	public function get_one_by_ID($id)
1175
+	{
1176
+		if ($this->get_from_entity_map($id)) {
1177
+			return $this->get_from_entity_map($id);
1178
+		}
1179
+		$model_object = $this->get_one(
1180
+			$this->alter_query_params_to_restrict_by_ID(
1181
+				$id,
1182
+				array('default_where_conditions' => EEM_Base::default_where_conditions_minimum_all)
1183
+			)
1184
+		);
1185
+		$className = $this->_get_class_name();
1186
+		if ($model_object instanceof $className) {
1187
+			// make sure valid objects get added to the entity map
1188
+			// so that the next call to this method doesn't trigger another trip to the db
1189
+			$this->add_to_entity_map($model_object);
1190
+		}
1191
+		return $model_object;
1192
+	}
1193
+
1194
+
1195
+
1196
+	/**
1197
+	 * Alters query parameters to only get items with this ID are returned.
1198
+	 * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1199
+	 * or could just be a simple primary key ID
1200
+	 *
1201
+	 * @param int   $id
1202
+	 * @param array $query_params
1203
+	 * @return array of normal query params, @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1204
+	 * @throws EE_Error
1205
+	 */
1206
+	public function alter_query_params_to_restrict_by_ID($id, $query_params = array())
1207
+	{
1208
+		if (! isset($query_params[0])) {
1209
+			$query_params[0] = array();
1210
+		}
1211
+		$conditions_from_id = $this->parse_index_primary_key_string($id);
1212
+		if ($conditions_from_id === null) {
1213
+			$query_params[0][ $this->primary_key_name() ] = $id;
1214
+		} else {
1215
+			// no primary key, so the $id must be from the get_index_primary_key_string()
1216
+			$query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1217
+		}
1218
+		return $query_params;
1219
+	}
1220
+
1221
+
1222
+
1223
+	/**
1224
+	 * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1225
+	 * array. If no item is found, null is returned.
1226
+	 *
1227
+	 * @param array $query_params like EEM_Base's $query_params variable.
1228
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1229
+	 * @throws EE_Error
1230
+	 */
1231
+	public function get_one($query_params = array())
1232
+	{
1233
+		if (! is_array($query_params)) {
1234
+			EE_Error::doing_it_wrong(
1235
+				'EEM_Base::get_one',
1236
+				sprintf(
1237
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1238
+					gettype($query_params)
1239
+				),
1240
+				'4.6.0'
1241
+			);
1242
+			$query_params = array();
1243
+		}
1244
+		$query_params['limit'] = 1;
1245
+		$items = $this->get_all($query_params);
1246
+		if (empty($items)) {
1247
+			return null;
1248
+		}
1249
+		return array_shift($items);
1250
+	}
1251
+
1252
+
1253
+
1254
+	/**
1255
+	 * Returns the next x number of items in sequence from the given value as
1256
+	 * found in the database matching the given query conditions.
1257
+	 *
1258
+	 * @param mixed $current_field_value    Value used for the reference point.
1259
+	 * @param null  $field_to_order_by      What field is used for the
1260
+	 *                                      reference point.
1261
+	 * @param int   $limit                  How many to return.
1262
+	 * @param array $query_params           Extra conditions on the query.
1263
+	 * @param null  $columns_to_select      If left null, then an array of
1264
+	 *                                      EE_Base_Class objects is returned,
1265
+	 *                                      otherwise you can indicate just the
1266
+	 *                                      columns you want returned.
1267
+	 * @return EE_Base_Class[]|array
1268
+	 * @throws EE_Error
1269
+	 */
1270
+	public function next_x(
1271
+		$current_field_value,
1272
+		$field_to_order_by = null,
1273
+		$limit = 1,
1274
+		$query_params = array(),
1275
+		$columns_to_select = null
1276
+	) {
1277
+		return $this->_get_consecutive(
1278
+			$current_field_value,
1279
+			'>',
1280
+			$field_to_order_by,
1281
+			$limit,
1282
+			$query_params,
1283
+			$columns_to_select
1284
+		);
1285
+	}
1286
+
1287
+
1288
+
1289
+	/**
1290
+	 * Returns the previous x number of items in sequence from the given value
1291
+	 * as found in the database matching the given query conditions.
1292
+	 *
1293
+	 * @param mixed $current_field_value    Value used for the reference point.
1294
+	 * @param null  $field_to_order_by      What field is used for the
1295
+	 *                                      reference point.
1296
+	 * @param int   $limit                  How many to return.
1297
+	 * @param array $query_params           Extra conditions on the query.
1298
+	 * @param null  $columns_to_select      If left null, then an array of
1299
+	 *                                      EE_Base_Class objects is returned,
1300
+	 *                                      otherwise you can indicate just the
1301
+	 *                                      columns you want returned.
1302
+	 * @return EE_Base_Class[]|array
1303
+	 * @throws EE_Error
1304
+	 */
1305
+	public function previous_x(
1306
+		$current_field_value,
1307
+		$field_to_order_by = null,
1308
+		$limit = 1,
1309
+		$query_params = array(),
1310
+		$columns_to_select = null
1311
+	) {
1312
+		return $this->_get_consecutive(
1313
+			$current_field_value,
1314
+			'<',
1315
+			$field_to_order_by,
1316
+			$limit,
1317
+			$query_params,
1318
+			$columns_to_select
1319
+		);
1320
+	}
1321
+
1322
+
1323
+
1324
+	/**
1325
+	 * Returns the next item in sequence from the given value as found in the
1326
+	 * database matching the given query conditions.
1327
+	 *
1328
+	 * @param mixed $current_field_value    Value used for the reference point.
1329
+	 * @param null  $field_to_order_by      What field is used for the
1330
+	 *                                      reference point.
1331
+	 * @param array $query_params           Extra conditions on the query.
1332
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1333
+	 *                                      object is returned, otherwise you
1334
+	 *                                      can indicate just the columns you
1335
+	 *                                      want and a single array indexed by
1336
+	 *                                      the columns will be returned.
1337
+	 * @return EE_Base_Class|null|array()
1338
+	 * @throws EE_Error
1339
+	 */
1340
+	public function next(
1341
+		$current_field_value,
1342
+		$field_to_order_by = null,
1343
+		$query_params = array(),
1344
+		$columns_to_select = null
1345
+	) {
1346
+		$results = $this->_get_consecutive(
1347
+			$current_field_value,
1348
+			'>',
1349
+			$field_to_order_by,
1350
+			1,
1351
+			$query_params,
1352
+			$columns_to_select
1353
+		);
1354
+		return empty($results) ? null : reset($results);
1355
+	}
1356
+
1357
+
1358
+
1359
+	/**
1360
+	 * Returns the previous item in sequence from the given value as found in
1361
+	 * the database matching the given query conditions.
1362
+	 *
1363
+	 * @param mixed $current_field_value    Value used for the reference point.
1364
+	 * @param null  $field_to_order_by      What field is used for the
1365
+	 *                                      reference point.
1366
+	 * @param array $query_params           Extra conditions on the query.
1367
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1368
+	 *                                      object is returned, otherwise you
1369
+	 *                                      can indicate just the columns you
1370
+	 *                                      want and a single array indexed by
1371
+	 *                                      the columns will be returned.
1372
+	 * @return EE_Base_Class|null|array()
1373
+	 * @throws EE_Error
1374
+	 */
1375
+	public function previous(
1376
+		$current_field_value,
1377
+		$field_to_order_by = null,
1378
+		$query_params = array(),
1379
+		$columns_to_select = null
1380
+	) {
1381
+		$results = $this->_get_consecutive(
1382
+			$current_field_value,
1383
+			'<',
1384
+			$field_to_order_by,
1385
+			1,
1386
+			$query_params,
1387
+			$columns_to_select
1388
+		);
1389
+		return empty($results) ? null : reset($results);
1390
+	}
1391
+
1392
+
1393
+
1394
+	/**
1395
+	 * Returns the a consecutive number of items in sequence from the given
1396
+	 * value as found in the database matching the given query conditions.
1397
+	 *
1398
+	 * @param mixed  $current_field_value   Value used for the reference point.
1399
+	 * @param string $operand               What operand is used for the sequence.
1400
+	 * @param string $field_to_order_by     What field is used for the reference point.
1401
+	 * @param int    $limit                 How many to return.
1402
+	 * @param array  $query_params          Extra conditions on the query.
1403
+	 * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1404
+	 *                                      otherwise you can indicate just the columns you want returned.
1405
+	 * @return EE_Base_Class[]|array
1406
+	 * @throws EE_Error
1407
+	 */
1408
+	protected function _get_consecutive(
1409
+		$current_field_value,
1410
+		$operand = '>',
1411
+		$field_to_order_by = null,
1412
+		$limit = 1,
1413
+		$query_params = array(),
1414
+		$columns_to_select = null
1415
+	) {
1416
+		// if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1417
+		if (empty($field_to_order_by)) {
1418
+			if ($this->has_primary_key_field()) {
1419
+				$field_to_order_by = $this->get_primary_key_field()->get_name();
1420
+			} else {
1421
+				if (WP_DEBUG) {
1422
+					throw new EE_Error(esc_html__(
1423
+						'EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1424
+						'event_espresso'
1425
+					));
1426
+				}
1427
+				EE_Error::add_error(esc_html__('There was an error with the query.', 'event_espresso'));
1428
+				return array();
1429
+			}
1430
+		}
1431
+		if (! is_array($query_params)) {
1432
+			EE_Error::doing_it_wrong(
1433
+				'EEM_Base::_get_consecutive',
1434
+				sprintf(
1435
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1436
+					gettype($query_params)
1437
+				),
1438
+				'4.6.0'
1439
+			);
1440
+			$query_params = array();
1441
+		}
1442
+		// let's add the where query param for consecutive look up.
1443
+		$query_params[0][ $field_to_order_by ] = array($operand, $current_field_value);
1444
+		$query_params['limit'] = $limit;
1445
+		// set direction
1446
+		$incoming_orderby = isset($query_params['order_by']) ? (array) $query_params['order_by'] : array();
1447
+		$query_params['order_by'] = $operand === '>'
1448
+			? array($field_to_order_by => 'ASC') + $incoming_orderby
1449
+			: array($field_to_order_by => 'DESC') + $incoming_orderby;
1450
+		// if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1451
+		if (empty($columns_to_select)) {
1452
+			return $this->get_all($query_params);
1453
+		}
1454
+		// getting just the fields
1455
+		return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1456
+	}
1457
+
1458
+
1459
+
1460
+	/**
1461
+	 * This sets the _timezone property after model object has been instantiated.
1462
+	 *
1463
+	 * @param null | string $timezone valid PHP DateTimeZone timezone string
1464
+	 */
1465
+	public function set_timezone($timezone)
1466
+	{
1467
+		if ($timezone !== null) {
1468
+			$this->_timezone = $timezone;
1469
+		}
1470
+		// note we need to loop through relations and set the timezone on those objects as well.
1471
+		foreach ($this->_model_relations as $relation) {
1472
+			$relation->set_timezone($timezone);
1473
+		}
1474
+		// and finally we do the same for any datetime fields
1475
+		foreach ($this->_fields as $field) {
1476
+			if ($field instanceof EE_Datetime_Field) {
1477
+				$field->set_timezone($timezone);
1478
+			}
1479
+		}
1480
+	}
1481
+
1482
+
1483
+
1484
+	/**
1485
+	 * This just returns whatever is set for the current timezone.
1486
+	 *
1487
+	 * @access public
1488
+	 * @return string
1489
+	 */
1490
+	public function get_timezone()
1491
+	{
1492
+		// first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1493
+		if (empty($this->_timezone)) {
1494
+			foreach ($this->_fields as $field) {
1495
+				if ($field instanceof EE_Datetime_Field) {
1496
+					$this->set_timezone($field->get_timezone());
1497
+					break;
1498
+				}
1499
+			}
1500
+		}
1501
+		// if timezone STILL empty then return the default timezone for the site.
1502
+		if (empty($this->_timezone)) {
1503
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1504
+		}
1505
+		return $this->_timezone;
1506
+	}
1507
+
1508
+
1509
+
1510
+	/**
1511
+	 * This returns the date formats set for the given field name and also ensures that
1512
+	 * $this->_timezone property is set correctly.
1513
+	 *
1514
+	 * @since 4.6.x
1515
+	 * @param string $field_name The name of the field the formats are being retrieved for.
1516
+	 * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1517
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1518
+	 * @return array formats in an array with the date format first, and the time format last.
1519
+	 */
1520
+	public function get_formats_for($field_name, $pretty = false)
1521
+	{
1522
+		$field_settings = $this->field_settings_for($field_name);
1523
+		// if not a valid EE_Datetime_Field then throw error
1524
+		if (! $field_settings instanceof EE_Datetime_Field) {
1525
+			throw new EE_Error(sprintf(esc_html__(
1526
+				'The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1527
+				'event_espresso'
1528
+			), $field_name));
1529
+		}
1530
+		// while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1531
+		// the field.
1532
+		$this->_timezone = $field_settings->get_timezone();
1533
+		return array($field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty));
1534
+	}
1535
+
1536
+
1537
+
1538
+	/**
1539
+	 * This returns the current time in a format setup for a query on this model.
1540
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1541
+	 * it will return:
1542
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1543
+	 *  NOW
1544
+	 *  - or a unix timestamp (equivalent to time())
1545
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1546
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1547
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1548
+	 * @since 4.6.x
1549
+	 * @param string $field_name       The field the current time is needed for.
1550
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1551
+	 *                                 formatted string matching the set format for the field in the set timezone will
1552
+	 *                                 be returned.
1553
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1554
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1555
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1556
+	 *                                 exception is triggered.
1557
+	 */
1558
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1559
+	{
1560
+		$formats = $this->get_formats_for($field_name);
1561
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1562
+		if ($timestamp) {
1563
+			return $DateTime->format('U');
1564
+		}
1565
+		// not returning timestamp, so return formatted string in timezone.
1566
+		switch ($what) {
1567
+			case 'time':
1568
+				return $DateTime->format($formats[1]);
1569
+				break;
1570
+			case 'date':
1571
+				return $DateTime->format($formats[0]);
1572
+				break;
1573
+			default:
1574
+				return $DateTime->format(implode(' ', $formats));
1575
+				break;
1576
+		}
1577
+	}
1578
+
1579
+
1580
+
1581
+	/**
1582
+	 * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1583
+	 * for the model are.  Returns a DateTime object.
1584
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1585
+	 * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1586
+	 * ignored.
1587
+	 *
1588
+	 * @param string $field_name      The field being setup.
1589
+	 * @param string $timestring      The date time string being used.
1590
+	 * @param string $incoming_format The format for the time string.
1591
+	 * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1592
+	 *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1593
+	 *                                format is
1594
+	 *                                'U', this is ignored.
1595
+	 * @return DateTime
1596
+	 * @throws EE_Error
1597
+	 */
1598
+	public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1599
+	{
1600
+		// just using this to ensure the timezone is set correctly internally
1601
+		$this->get_formats_for($field_name);
1602
+		// load EEH_DTT_Helper
1603
+		$set_timezone = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1604
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1605
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1606
+		return \EventEspresso\core\domain\entities\DbSafeDateTime::createFromDateTime($incomingDateTime);
1607
+	}
1608
+
1609
+
1610
+
1611
+	/**
1612
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1613
+	 *
1614
+	 * @return EE_Table_Base[]
1615
+	 */
1616
+	public function get_tables()
1617
+	{
1618
+		return $this->_tables;
1619
+	}
1620
+
1621
+
1622
+
1623
+	/**
1624
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1625
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1626
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1627
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1628
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1629
+	 * model object with EVT_ID = 1
1630
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1631
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1632
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1633
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1634
+	 * are not specified)
1635
+	 *
1636
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1637
+	 *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1638
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1639
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1640
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1641
+	 *                                         ID=34, we'd use this method as follows:
1642
+	 *                                         EEM_Transaction::instance()->update(
1643
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1644
+	 *                                         array(array('TXN_ID'=>34)));
1645
+	 * @param array   $query_params            @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1646
+	 *                                         Eg, consider updating Question's QST_admin_label field is of type
1647
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1648
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1649
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1650
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1651
+	 *                                         TRUE, it is assumed that you've already called
1652
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1653
+	 *                                         malicious javascript. However, if
1654
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1655
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1656
+	 *                                         and every other field, before insertion. We provide this parameter
1657
+	 *                                         because model objects perform their prepare_for_set function on all
1658
+	 *                                         their values, and so don't need to be called again (and in many cases,
1659
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1660
+	 *                                         prepare_for_set method...)
1661
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1662
+	 *                                         in this model's entity map according to $fields_n_values that match
1663
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1664
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1665
+	 *                                         could get out-of-sync with the database
1666
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1667
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1668
+	 *                                         bad)
1669
+	 * @throws EE_Error
1670
+	 */
1671
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1672
+	{
1673
+		if (! is_array($query_params)) {
1674
+			EE_Error::doing_it_wrong(
1675
+				'EEM_Base::update',
1676
+				sprintf(
1677
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1678
+					gettype($query_params)
1679
+				),
1680
+				'4.6.0'
1681
+			);
1682
+			$query_params = array();
1683
+		}
1684
+		/**
1685
+		 * Action called before a model update call has been made.
1686
+		 *
1687
+		 * @param EEM_Base $model
1688
+		 * @param array    $fields_n_values the updated fields and their new values
1689
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1690
+		 */
1691
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1692
+		/**
1693
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1694
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1695
+		 *
1696
+		 * @param array    $fields_n_values fields and their new values
1697
+		 * @param EEM_Base $model           the model being queried
1698
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1699
+		 */
1700
+		$fields_n_values = (array) apply_filters(
1701
+			'FHEE__EEM_Base__update__fields_n_values',
1702
+			$fields_n_values,
1703
+			$this,
1704
+			$query_params
1705
+		);
1706
+		// need to verify that, for any entry we want to update, there are entries in each secondary table.
1707
+		// to do that, for each table, verify that it's PK isn't null.
1708
+		$tables = $this->get_tables();
1709
+		// and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1710
+		// NOTE: we should make this code more efficient by NOT querying twice
1711
+		// before the real update, but that needs to first go through ALPHA testing
1712
+		// as it's dangerous. says Mike August 8 2014
1713
+		// we want to make sure the default_where strategy is ignored
1714
+		$this->_ignore_where_strategy = true;
1715
+		$wpdb_select_results = $this->_get_all_wpdb_results($query_params);
1716
+		foreach ($wpdb_select_results as $wpdb_result) {
1717
+			// type cast stdClass as array
1718
+			$wpdb_result = (array) $wpdb_result;
1719
+			// get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1720
+			if ($this->has_primary_key_field()) {
1721
+				$main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1722
+			} else {
1723
+				// if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1724
+				$main_table_pk_value = null;
1725
+			}
1726
+			// if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1727
+			// and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1728
+			if (count($tables) > 1) {
1729
+				// foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1730
+				// in that table, and so we'll want to insert one
1731
+				foreach ($tables as $table_obj) {
1732
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1733
+					// if there is no private key for this table on the results, it means there's no entry
1734
+					// in this table, right? so insert a row in the current table, using any fields available
1735
+					if (
1736
+						! (array_key_exists($this_table_pk_column, $wpdb_result)
1737
+						   && $wpdb_result[ $this_table_pk_column ])
1738
+					) {
1739
+						$success = $this->_insert_into_specific_table(
1740
+							$table_obj,
1741
+							$fields_n_values,
1742
+							$main_table_pk_value
1743
+						);
1744
+						// if we died here, report the error
1745
+						if (! $success) {
1746
+							return false;
1747
+						}
1748
+					}
1749
+				}
1750
+			}
1751
+			//              //and now check that if we have cached any models by that ID on the model, that
1752
+			//              //they also get updated properly
1753
+			//              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1754
+			//              if( $model_object ){
1755
+			//                  foreach( $fields_n_values as $field => $value ){
1756
+			//                      $model_object->set($field, $value);
1757
+			// let's make sure default_where strategy is followed now
1758
+			$this->_ignore_where_strategy = false;
1759
+		}
1760
+		// if we want to keep model objects in sync, AND
1761
+		// if this wasn't called from a model object (to update itself)
1762
+		// then we want to make sure we keep all the existing
1763
+		// model objects in sync with the db
1764
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1765
+			if ($this->has_primary_key_field()) {
1766
+				$model_objs_affected_ids = $this->get_col($query_params);
1767
+			} else {
1768
+				// we need to select a bunch of columns and then combine them into the the "index primary key string"s
1769
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1770
+				$model_objs_affected_ids = array();
1771
+				foreach ($models_affected_key_columns as $row) {
1772
+					$combined_index_key = $this->get_index_primary_key_string($row);
1773
+					$model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1774
+				}
1775
+			}
1776
+			if (! $model_objs_affected_ids) {
1777
+				// wait wait wait- if nothing was affected let's stop here
1778
+				return 0;
1779
+			}
1780
+			foreach ($model_objs_affected_ids as $id) {
1781
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1782
+				if ($model_obj_in_entity_map) {
1783
+					foreach ($fields_n_values as $field => $new_value) {
1784
+						$model_obj_in_entity_map->set($field, $new_value);
1785
+					}
1786
+				}
1787
+			}
1788
+			// if there is a primary key on this model, we can now do a slight optimization
1789
+			if ($this->has_primary_key_field()) {
1790
+				// we already know what we want to update. So let's make the query simpler so it's a little more efficient
1791
+				$query_params = array(
1792
+					array($this->primary_key_name() => array('IN', $model_objs_affected_ids)),
1793
+					'limit'                    => count($model_objs_affected_ids),
1794
+					'default_where_conditions' => EEM_Base::default_where_conditions_none,
1795
+				);
1796
+			}
1797
+		}
1798
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1799
+		$SQL = "UPDATE "
1800
+			   . $model_query_info->get_full_join_sql()
1801
+			   . " SET "
1802
+			   . $this->_construct_update_sql($fields_n_values)
1803
+			   . $model_query_info->get_where_sql();// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1804
+		$rows_affected = $this->_do_wpdb_query('query', array($SQL));
1805
+		/**
1806
+		 * Action called after a model update call has been made.
1807
+		 *
1808
+		 * @param EEM_Base $model
1809
+		 * @param array    $fields_n_values the updated fields and their new values
1810
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1811
+		 * @param int      $rows_affected
1812
+		 */
1813
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1814
+		return $rows_affected;// how many supposedly got updated
1815
+	}
1816
+
1817
+
1818
+
1819
+	/**
1820
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1821
+	 * are teh values of the field specified (or by default the primary key field)
1822
+	 * that matched the query params. Note that you should pass the name of the
1823
+	 * model FIELD, not the database table's column name.
1824
+	 *
1825
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1826
+	 * @param string $field_to_select
1827
+	 * @return array just like $wpdb->get_col()
1828
+	 * @throws EE_Error
1829
+	 */
1830
+	public function get_col($query_params = array(), $field_to_select = null)
1831
+	{
1832
+		if ($field_to_select) {
1833
+			$field = $this->field_settings_for($field_to_select);
1834
+		} elseif ($this->has_primary_key_field()) {
1835
+			$field = $this->get_primary_key_field();
1836
+		} else {
1837
+			// no primary key, just grab the first column
1838
+			$field_settings = $this->field_settings();
1839
+			$field = reset($field_settings);
1840
+		}
1841
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1842
+		$select_expressions = $field->get_qualified_column();
1843
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1844
+		return $this->_do_wpdb_query('get_col', array($SQL));
1845
+	}
1846
+
1847
+
1848
+
1849
+	/**
1850
+	 * Returns a single column value for a single row from the database
1851
+	 *
1852
+	 * @param array  $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1853
+	 * @param string $field_to_select @see EEM_Base::get_col()
1854
+	 * @return string
1855
+	 * @throws EE_Error
1856
+	 */
1857
+	public function get_var($query_params = array(), $field_to_select = null)
1858
+	{
1859
+		$query_params['limit'] = 1;
1860
+		$col = $this->get_col($query_params, $field_to_select);
1861
+		if (! empty($col)) {
1862
+			return reset($col);
1863
+		}
1864
+		return null;
1865
+	}
1866
+
1867
+
1868
+
1869
+	/**
1870
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1871
+	 * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1872
+	 * injection, but currently no further filtering is done
1873
+	 *
1874
+	 * @global      $wpdb
1875
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1876
+	 *                               be updated to in the DB
1877
+	 * @return string of SQL
1878
+	 * @throws EE_Error
1879
+	 */
1880
+	public function _construct_update_sql($fields_n_values)
1881
+	{
1882
+		/** @type WPDB $wpdb */
1883
+		global $wpdb;
1884
+		$cols_n_values = array();
1885
+		foreach ($fields_n_values as $field_name => $value) {
1886
+			$field_obj = $this->field_settings_for($field_name);
1887
+			// if the value is NULL, we want to assign the value to that.
1888
+			// wpdb->prepare doesn't really handle that properly
1889
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1890
+			$value_sql = $prepared_value === null ? 'NULL'
1891
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1892
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1893
+		}
1894
+		return implode(",", $cols_n_values);
1895
+	}
1896
+
1897
+
1898
+
1899
+	/**
1900
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1901
+	 * Performs a HARD delete, meaning the database row should always be removed,
1902
+	 * not just have a flag field on it switched
1903
+	 * Wrapper for EEM_Base::delete_permanently()
1904
+	 *
1905
+	 * @param mixed $id
1906
+	 * @param boolean $allow_blocking
1907
+	 * @return int the number of rows deleted
1908
+	 * @throws EE_Error
1909
+	 */
1910
+	public function delete_permanently_by_ID($id, $allow_blocking = true)
1911
+	{
1912
+		return $this->delete_permanently(
1913
+			array(
1914
+				array($this->get_primary_key_field()->get_name() => $id),
1915
+				'limit' => 1,
1916
+			),
1917
+			$allow_blocking
1918
+		);
1919
+	}
1920
+
1921
+
1922
+
1923
+	/**
1924
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1925
+	 * Wrapper for EEM_Base::delete()
1926
+	 *
1927
+	 * @param mixed $id
1928
+	 * @param boolean $allow_blocking
1929
+	 * @return int the number of rows deleted
1930
+	 * @throws EE_Error
1931
+	 */
1932
+	public function delete_by_ID($id, $allow_blocking = true)
1933
+	{
1934
+		return $this->delete(
1935
+			array(
1936
+				array($this->get_primary_key_field()->get_name() => $id),
1937
+				'limit' => 1,
1938
+			),
1939
+			$allow_blocking
1940
+		);
1941
+	}
1942
+
1943
+
1944
+
1945
+	/**
1946
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1947
+	 * meaning if the model has a field that indicates its been "trashed" or
1948
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
1949
+	 *
1950
+	 * @see EEM_Base::delete_permanently
1951
+	 * @param array   $query_params
1952
+	 * @param boolean $allow_blocking
1953
+	 * @return int how many rows got deleted
1954
+	 * @throws EE_Error
1955
+	 */
1956
+	public function delete($query_params, $allow_blocking = true)
1957
+	{
1958
+		return $this->delete_permanently($query_params, $allow_blocking);
1959
+	}
1960
+
1961
+
1962
+
1963
+	/**
1964
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
1965
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1966
+	 * as archived, not actually deleted
1967
+	 *
1968
+	 * @param array   $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1969
+	 * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1970
+	 *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1971
+	 *                                deletes regardless of other objects which may depend on it. Its generally
1972
+	 *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1973
+	 *                                DB
1974
+	 * @return int how many rows got deleted
1975
+	 * @throws EE_Error
1976
+	 */
1977
+	public function delete_permanently($query_params, $allow_blocking = true)
1978
+	{
1979
+		/**
1980
+		 * Action called just before performing a real deletion query. You can use the
1981
+		 * model and its $query_params to find exactly which items will be deleted
1982
+		 *
1983
+		 * @param EEM_Base $model
1984
+		 * @param array    $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1985
+		 * @param boolean  $allow_blocking whether or not to allow related model objects
1986
+		 *                                 to block (prevent) this deletion
1987
+		 */
1988
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1989
+		// some MySQL databases may be running safe mode, which may restrict
1990
+		// deletion if there is no KEY column used in the WHERE statement of a deletion.
1991
+		// to get around this, we first do a SELECT, get all the IDs, and then run another query
1992
+		// to delete them
1993
+		$items_for_deletion = $this->_get_all_wpdb_results($query_params);
1994
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1995
+		$deletion_where_query_part = $this->_build_query_part_for_deleting_from_columns_and_values(
1996
+			$columns_and_ids_for_deleting
1997
+		);
1998
+		/**
1999
+		 * Allows client code to act on the items being deleted before the query is actually executed.
2000
+		 *
2001
+		 * @param EEM_Base $this  The model instance being acted on.
2002
+		 * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
2003
+		 * @param bool     $allow_blocking @see param description in method phpdoc block.
2004
+		 * @param array $columns_and_ids_for_deleting       An array indicating what entities will get removed as
2005
+		 *                                                  derived from the incoming query parameters.
2006
+		 *                                                  @see details on the structure of this array in the phpdocs
2007
+		 *                                                  for the `_get_ids_for_delete_method`
2008
+		 *
2009
+		 */
2010
+		do_action(
2011
+			'AHEE__EEM_Base__delete__before_query',
2012
+			$this,
2013
+			$query_params,
2014
+			$allow_blocking,
2015
+			$columns_and_ids_for_deleting
2016
+		);
2017
+		if ($deletion_where_query_part) {
2018
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
2019
+			$table_aliases = array_keys($this->_tables);
2020
+			$SQL = "DELETE "
2021
+				   . implode(", ", $table_aliases)
2022
+				   . " FROM "
2023
+				   . $model_query_info->get_full_join_sql()
2024
+				   . " WHERE "
2025
+				   . $deletion_where_query_part;
2026
+			$rows_deleted = $this->_do_wpdb_query('query', array($SQL));
2027
+		} else {
2028
+			$rows_deleted = 0;
2029
+		}
2030
+
2031
+		// Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2032
+		// there was no error with the delete query.
2033
+		if (
2034
+			$this->has_primary_key_field()
2035
+			&& $rows_deleted !== false
2036
+			&& isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2037
+		) {
2038
+			$ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2039
+			foreach ($ids_for_removal as $id) {
2040
+				if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2041
+					unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2042
+				}
2043
+			}
2044
+
2045
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2046
+			// `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2047
+			// unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2048
+			// (although it is possible).
2049
+			// Note this can be skipped by using the provided filter and returning false.
2050
+			if (
2051
+				apply_filters(
2052
+					'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2053
+					! $this instanceof EEM_Extra_Meta,
2054
+					$this
2055
+				)
2056
+			) {
2057
+				EEM_Extra_Meta::instance()->delete_permanently(array(
2058
+					0 => array(
2059
+						'EXM_type' => $this->get_this_model_name(),
2060
+						'OBJ_ID'   => array(
2061
+							'IN',
2062
+							$ids_for_removal
2063
+						)
2064
+					)
2065
+				));
2066
+			}
2067
+		}
2068
+
2069
+		/**
2070
+		 * Action called just after performing a real deletion query. Although at this point the
2071
+		 * items should have been deleted
2072
+		 *
2073
+		 * @param EEM_Base $model
2074
+		 * @param array    $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2075
+		 * @param int      $rows_deleted
2076
+		 */
2077
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2078
+		return $rows_deleted;// how many supposedly got deleted
2079
+	}
2080
+
2081
+
2082
+
2083
+	/**
2084
+	 * Checks all the relations that throw error messages when there are blocking related objects
2085
+	 * for related model objects. If there are any related model objects on those relations,
2086
+	 * adds an EE_Error, and return true
2087
+	 *
2088
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2089
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2090
+	 *                                                 should be ignored when determining whether there are related
2091
+	 *                                                 model objects which block this model object's deletion. Useful
2092
+	 *                                                 if you know A is related to B and are considering deleting A,
2093
+	 *                                                 but want to see if A has any other objects blocking its deletion
2094
+	 *                                                 before removing the relation between A and B
2095
+	 * @return boolean
2096
+	 * @throws EE_Error
2097
+	 */
2098
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2099
+	{
2100
+		// first, if $ignore_this_model_obj was supplied, get its model
2101
+		if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2102
+			$ignored_model = $ignore_this_model_obj->get_model();
2103
+		} else {
2104
+			$ignored_model = null;
2105
+		}
2106
+		// now check all the relations of $this_model_obj_or_id and see if there
2107
+		// are any related model objects blocking it?
2108
+		$is_blocked = false;
2109
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2110
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2111
+				// if $ignore_this_model_obj was supplied, then for the query
2112
+				// on that model needs to be told to ignore $ignore_this_model_obj
2113
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2114
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id, array(
2115
+						array(
2116
+							$ignored_model->get_primary_key_field()->get_name() => array(
2117
+								'!=',
2118
+								$ignore_this_model_obj->ID(),
2119
+							),
2120
+						),
2121
+					));
2122
+				} else {
2123
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2124
+				}
2125
+				if ($related_model_objects) {
2126
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2127
+					$is_blocked = true;
2128
+				}
2129
+			}
2130
+		}
2131
+		return $is_blocked;
2132
+	}
2133
+
2134
+
2135
+	/**
2136
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2137
+	 * @param array $row_results_for_deleting
2138
+	 * @param bool  $allow_blocking
2139
+	 * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2140
+	 *                 model DOES have a primary_key_field, then the array will be a simple single dimension array where
2141
+	 *                 the key is the fully qualified primary key column and the value is an array of ids that will be
2142
+	 *                 deleted. Example:
2143
+	 *                      array('Event.EVT_ID' => array( 1,2,3))
2144
+	 *                 If the model DOES NOT have a primary_key_field, then the array will be a two dimensional array
2145
+	 *                 where each element is a group of columns and values that get deleted. Example:
2146
+	 *                      array(
2147
+	 *                          0 => array(
2148
+	 *                              'Term_Relationship.object_id' => 1
2149
+	 *                              'Term_Relationship.term_taxonomy_id' => 5
2150
+	 *                          ),
2151
+	 *                          1 => array(
2152
+	 *                              'Term_Relationship.object_id' => 1
2153
+	 *                              'Term_Relationship.term_taxonomy_id' => 6
2154
+	 *                          )
2155
+	 *                      )
2156
+	 * @throws EE_Error
2157
+	 */
2158
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2159
+	{
2160
+		$ids_to_delete_indexed_by_column = array();
2161
+		if ($this->has_primary_key_field()) {
2162
+			$primary_table = $this->_get_main_table();
2163
+			$primary_table_pk_field = $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2164
+			$other_tables = $this->_get_other_tables();
2165
+			$ids_to_delete_indexed_by_column = $query = array();
2166
+			foreach ($row_results_for_deleting as $item_to_delete) {
2167
+				// before we mark this item for deletion,
2168
+				// make sure there's no related entities blocking its deletion (if we're checking)
2169
+				if (
2170
+					$allow_blocking
2171
+					&& $this->delete_is_blocked_by_related_models(
2172
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2173
+					)
2174
+				) {
2175
+					continue;
2176
+				}
2177
+				// primary table deletes
2178
+				if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2179
+					$ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2180
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2181
+				}
2182
+			}
2183
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2184
+			$fields = $this->get_combined_primary_key_fields();
2185
+			foreach ($row_results_for_deleting as $item_to_delete) {
2186
+				$ids_to_delete_indexed_by_column_for_row = array();
2187
+				foreach ($fields as $cpk_field) {
2188
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2189
+						$ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2190
+							$item_to_delete[ $cpk_field->get_qualified_column() ];
2191
+					}
2192
+				}
2193
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2194
+			}
2195
+		} else {
2196
+			// so there's no primary key and no combined key...
2197
+			// sorry, can't help you
2198
+			throw new EE_Error(
2199
+				sprintf(
2200
+					esc_html__(
2201
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2202
+						"event_espresso"
2203
+					),
2204
+					get_class($this)
2205
+				)
2206
+			);
2207
+		}
2208
+		return $ids_to_delete_indexed_by_column;
2209
+	}
2210
+
2211
+
2212
+	/**
2213
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2214
+	 * the corresponding query_part for the query performing the delete.
2215
+	 *
2216
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2217
+	 * @return string
2218
+	 * @throws EE_Error
2219
+	 */
2220
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2221
+	{
2222
+		$query_part = '';
2223
+		if (empty($ids_to_delete_indexed_by_column)) {
2224
+			return $query_part;
2225
+		} elseif ($this->has_primary_key_field()) {
2226
+			$query = array();
2227
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2228
+				$query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2229
+			}
2230
+			$query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2231
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2232
+			$ways_to_identify_a_row = array();
2233
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2234
+				$values_for_each_combined_primary_key_for_a_row = array();
2235
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2236
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2237
+				}
2238
+				$ways_to_identify_a_row[] = '('
2239
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2240
+											. ')';
2241
+			}
2242
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2243
+		}
2244
+		return $query_part;
2245
+	}
2246
+
2247
+
2248
+
2249
+	/**
2250
+	 * Gets the model field by the fully qualified name
2251
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2252
+	 * @return EE_Model_Field_Base
2253
+	 */
2254
+	public function get_field_by_column($qualified_column_name)
2255
+	{
2256
+		foreach ($this->field_settings(true) as $field_name => $field_obj) {
2257
+			if ($field_obj->get_qualified_column() === $qualified_column_name) {
2258
+				return $field_obj;
2259
+			}
2260
+		}
2261
+		throw new EE_Error(
2262
+			sprintf(
2263
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2264
+				$this->get_this_model_name(),
2265
+				$qualified_column_name
2266
+			)
2267
+		);
2268
+	}
2269
+
2270
+
2271
+
2272
+	/**
2273
+	 * Count all the rows that match criteria the model query params.
2274
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2275
+	 * column
2276
+	 *
2277
+	 * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2278
+	 * @param string $field_to_count field on model to count by (not column name)
2279
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2280
+	 *                               that by the setting $distinct to TRUE;
2281
+	 * @return int
2282
+	 * @throws EE_Error
2283
+	 */
2284
+	public function count($query_params = array(), $field_to_count = null, $distinct = false)
2285
+	{
2286
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2287
+		if ($field_to_count) {
2288
+			$field_obj = $this->field_settings_for($field_to_count);
2289
+			$column_to_count = $field_obj->get_qualified_column();
2290
+		} elseif ($this->has_primary_key_field()) {
2291
+			$pk_field_obj = $this->get_primary_key_field();
2292
+			$column_to_count = $pk_field_obj->get_qualified_column();
2293
+		} else {
2294
+			// there's no primary key
2295
+			// if we're counting distinct items, and there's no primary key,
2296
+			// we need to list out the columns for distinction;
2297
+			// otherwise we can just use star
2298
+			if ($distinct) {
2299
+				$columns_to_use = array();
2300
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2301
+					$columns_to_use[] = $field_obj->get_qualified_column();
2302
+				}
2303
+				$column_to_count = implode(',', $columns_to_use);
2304
+			} else {
2305
+				$column_to_count = '*';
2306
+			}
2307
+		}
2308
+		$column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2309
+		$SQL = "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2310
+		return (int) $this->_do_wpdb_query('get_var', array($SQL));
2311
+	}
2312
+
2313
+
2314
+
2315
+	/**
2316
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2317
+	 *
2318
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2319
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2320
+	 * @return float
2321
+	 * @throws EE_Error
2322
+	 */
2323
+	public function sum($query_params, $field_to_sum = null)
2324
+	{
2325
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2326
+		if ($field_to_sum) {
2327
+			$field_obj = $this->field_settings_for($field_to_sum);
2328
+		} else {
2329
+			$field_obj = $this->get_primary_key_field();
2330
+		}
2331
+		$column_to_count = $field_obj->get_qualified_column();
2332
+		$SQL = "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2333
+		$return_value = $this->_do_wpdb_query('get_var', array($SQL));
2334
+		$data_type = $field_obj->get_wpdb_data_type();
2335
+		if ($data_type === '%d' || $data_type === '%s') {
2336
+			return (float) $return_value;
2337
+		}
2338
+		// must be %f
2339
+		return (float) $return_value;
2340
+	}
2341
+
2342
+
2343
+
2344
+	/**
2345
+	 * Just calls the specified method on $wpdb with the given arguments
2346
+	 * Consolidates a little extra error handling code
2347
+	 *
2348
+	 * @param string $wpdb_method
2349
+	 * @param array  $arguments_to_provide
2350
+	 * @throws EE_Error
2351
+	 * @global wpdb  $wpdb
2352
+	 * @return mixed
2353
+	 */
2354
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2355
+	{
2356
+		// if we're in maintenance mode level 2, DON'T run any queries
2357
+		// because level 2 indicates the database needs updating and
2358
+		// is probably out of sync with the code
2359
+		if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2360
+			throw new EE_Error(sprintf(esc_html__(
2361
+				"Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2362
+				"event_espresso"
2363
+			)));
2364
+		}
2365
+		/** @type WPDB $wpdb */
2366
+		global $wpdb;
2367
+		if (! method_exists($wpdb, $wpdb_method)) {
2368
+			throw new EE_Error(sprintf(esc_html__(
2369
+				'There is no method named "%s" on Wordpress\' $wpdb object',
2370
+				'event_espresso'
2371
+			), $wpdb_method));
2372
+		}
2373
+		if (WP_DEBUG) {
2374
+			$old_show_errors_value = $wpdb->show_errors;
2375
+			$wpdb->show_errors(false);
2376
+		}
2377
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2378
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2379
+		if (WP_DEBUG) {
2380
+			$wpdb->show_errors($old_show_errors_value);
2381
+			if (! empty($wpdb->last_error)) {
2382
+				throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2383
+			}
2384
+			if ($result === false) {
2385
+				throw new EE_Error(sprintf(esc_html__(
2386
+					'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2387
+					'event_espresso'
2388
+				), $wpdb_method, var_export($arguments_to_provide, true)));
2389
+			}
2390
+		} elseif ($result === false) {
2391
+			EE_Error::add_error(
2392
+				sprintf(
2393
+					esc_html__(
2394
+						'A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2395
+						'event_espresso'
2396
+					),
2397
+					$wpdb_method,
2398
+					var_export($arguments_to_provide, true),
2399
+					$wpdb->last_error
2400
+				),
2401
+				__FILE__,
2402
+				__FUNCTION__,
2403
+				__LINE__
2404
+			);
2405
+		}
2406
+		return $result;
2407
+	}
2408
+
2409
+
2410
+
2411
+	/**
2412
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2413
+	 * and if there's an error tries to verify the DB is correct. Uses
2414
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2415
+	 * we should try to fix the EE core db, the addons, or just give up
2416
+	 *
2417
+	 * @param string $wpdb_method
2418
+	 * @param array  $arguments_to_provide
2419
+	 * @return mixed
2420
+	 */
2421
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2422
+	{
2423
+		/** @type WPDB $wpdb */
2424
+		global $wpdb;
2425
+		$wpdb->last_error = null;
2426
+		$result = call_user_func_array(array($wpdb, $wpdb_method), $arguments_to_provide);
2427
+		// was there an error running the query? but we don't care on new activations
2428
+		// (we're going to setup the DB anyway on new activations)
2429
+		if (
2430
+			($result === false || ! empty($wpdb->last_error))
2431
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2432
+		) {
2433
+			switch (EEM_Base::$_db_verification_level) {
2434
+				case EEM_Base::db_verified_none:
2435
+					// let's double-check core's DB
2436
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2437
+					break;
2438
+				case EEM_Base::db_verified_core:
2439
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2440
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2441
+					break;
2442
+				case EEM_Base::db_verified_addons:
2443
+					// ummmm... you in trouble
2444
+					return $result;
2445
+					break;
2446
+			}
2447
+			if (! empty($error_message)) {
2448
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2449
+				trigger_error($error_message);
2450
+			}
2451
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2452
+		}
2453
+		return $result;
2454
+	}
2455
+
2456
+
2457
+
2458
+	/**
2459
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2460
+	 * EEM_Base::$_db_verification_level
2461
+	 *
2462
+	 * @param string $wpdb_method
2463
+	 * @param array  $arguments_to_provide
2464
+	 * @return string
2465
+	 */
2466
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2467
+	{
2468
+		/** @type WPDB $wpdb */
2469
+		global $wpdb;
2470
+		// ok remember that we've already attempted fixing the core db, in case the problem persists
2471
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2472
+		$error_message = sprintf(
2473
+			esc_html__(
2474
+				'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2475
+				'event_espresso'
2476
+			),
2477
+			$wpdb->last_error,
2478
+			$wpdb_method,
2479
+			wp_json_encode($arguments_to_provide)
2480
+		);
2481
+		EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2482
+		return $error_message;
2483
+	}
2484
+
2485
+
2486
+
2487
+	/**
2488
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2489
+	 * EEM_Base::$_db_verification_level
2490
+	 *
2491
+	 * @param $wpdb_method
2492
+	 * @param $arguments_to_provide
2493
+	 * @return string
2494
+	 */
2495
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2496
+	{
2497
+		/** @type WPDB $wpdb */
2498
+		global $wpdb;
2499
+		// ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2500
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2501
+		$error_message = sprintf(
2502
+			esc_html__(
2503
+				'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2504
+				'event_espresso'
2505
+			),
2506
+			$wpdb->last_error,
2507
+			$wpdb_method,
2508
+			wp_json_encode($arguments_to_provide)
2509
+		);
2510
+		EE_System::instance()->initialize_addons();
2511
+		return $error_message;
2512
+	}
2513
+
2514
+
2515
+
2516
+	/**
2517
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2518
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2519
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2520
+	 * ..."
2521
+	 *
2522
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2523
+	 * @return string
2524
+	 */
2525
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2526
+	{
2527
+		return " FROM " . $model_query_info->get_full_join_sql() .
2528
+			   $model_query_info->get_where_sql() .
2529
+			   $model_query_info->get_group_by_sql() .
2530
+			   $model_query_info->get_having_sql() .
2531
+			   $model_query_info->get_order_by_sql() .
2532
+			   $model_query_info->get_limit_sql();
2533
+	}
2534
+
2535
+
2536
+
2537
+	/**
2538
+	 * Set to easily debug the next X queries ran from this model.
2539
+	 *
2540
+	 * @param int $count
2541
+	 */
2542
+	public function show_next_x_db_queries($count = 1)
2543
+	{
2544
+		$this->_show_next_x_db_queries = $count;
2545
+	}
2546
+
2547
+
2548
+
2549
+	/**
2550
+	 * @param $sql_query
2551
+	 */
2552
+	public function show_db_query_if_previously_requested($sql_query)
2553
+	{
2554
+		if ($this->_show_next_x_db_queries > 0) {
2555
+			echo esc_html($sql_query);
2556
+			$this->_show_next_x_db_queries--;
2557
+		}
2558
+	}
2559
+
2560
+
2561
+
2562
+	/**
2563
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2564
+	 * There are the 3 cases:
2565
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2566
+	 * $otherModelObject has no ID, it is first saved.
2567
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2568
+	 * has no ID, it is first saved.
2569
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2570
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2571
+	 * join table
2572
+	 *
2573
+	 * @param        EE_Base_Class                     /int $thisModelObject
2574
+	 * @param        EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2575
+	 * @param string $relationName                     , key in EEM_Base::_relations
2576
+	 *                                                 an attendee to a group, you also want to specify which role they
2577
+	 *                                                 will have in that group. So you would use this parameter to
2578
+	 *                                                 specify array('role-column-name'=>'role-id')
2579
+	 * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2580
+	 *                                                 to for relation to methods that allow you to further specify
2581
+	 *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2582
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2583
+	 *                                                 because these will be inserted in any new rows created as well.
2584
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2585
+	 * @throws EE_Error
2586
+	 */
2587
+	public function add_relationship_to(
2588
+		$id_or_obj,
2589
+		$other_model_id_or_obj,
2590
+		$relationName,
2591
+		$extra_join_model_fields_n_values = array()
2592
+	) {
2593
+		$relation_obj = $this->related_settings_for($relationName);
2594
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2595
+	}
2596
+
2597
+
2598
+
2599
+	/**
2600
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2601
+	 * There are the 3 cases:
2602
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2603
+	 * error
2604
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2605
+	 * an error
2606
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2607
+	 *
2608
+	 * @param        EE_Base_Class /int $id_or_obj
2609
+	 * @param        EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2610
+	 * @param string $relationName key in EEM_Base::_relations
2611
+	 * @return boolean of success
2612
+	 * @throws EE_Error
2613
+	 * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2614
+	 *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2615
+	 *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2616
+	 *                             because these will be inserted in any new rows created as well.
2617
+	 */
2618
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = array())
2619
+	{
2620
+		$relation_obj = $this->related_settings_for($relationName);
2621
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2622
+	}
2623
+
2624
+
2625
+
2626
+	/**
2627
+	 * @param mixed           $id_or_obj
2628
+	 * @param string          $relationName
2629
+	 * @param array           $where_query_params
2630
+	 * @param EE_Base_Class[] objects to which relations were removed
2631
+	 * @return \EE_Base_Class[]
2632
+	 * @throws EE_Error
2633
+	 */
2634
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = array())
2635
+	{
2636
+		$relation_obj = $this->related_settings_for($relationName);
2637
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2638
+	}
2639
+
2640
+
2641
+
2642
+	/**
2643
+	 * Gets all the related items of the specified $model_name, using $query_params.
2644
+	 * Note: by default, we remove the "default query params"
2645
+	 * because we want to get even deleted items etc.
2646
+	 *
2647
+	 * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2648
+	 * @param string $model_name   like 'Event', 'Registration', etc. always singular
2649
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2650
+	 * @return EE_Base_Class[]
2651
+	 * @throws EE_Error
2652
+	 */
2653
+	public function get_all_related($id_or_obj, $model_name, $query_params = null)
2654
+	{
2655
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2656
+		$relation_settings = $this->related_settings_for($model_name);
2657
+		return $relation_settings->get_all_related($model_obj, $query_params);
2658
+	}
2659
+
2660
+
2661
+
2662
+	/**
2663
+	 * Deletes all the model objects across the relation indicated by $model_name
2664
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2665
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2666
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2667
+	 *
2668
+	 * @param EE_Base_Class|int|string $id_or_obj
2669
+	 * @param string                   $model_name
2670
+	 * @param array                    $query_params
2671
+	 * @return int how many deleted
2672
+	 * @throws EE_Error
2673
+	 */
2674
+	public function delete_related($id_or_obj, $model_name, $query_params = array())
2675
+	{
2676
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2677
+		$relation_settings = $this->related_settings_for($model_name);
2678
+		return $relation_settings->delete_all_related($model_obj, $query_params);
2679
+	}
2680
+
2681
+
2682
+
2683
+	/**
2684
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2685
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2686
+	 * the model objects can't be hard deleted because of blocking related model objects,
2687
+	 * just does a soft-delete on them instead.
2688
+	 *
2689
+	 * @param EE_Base_Class|int|string $id_or_obj
2690
+	 * @param string                   $model_name
2691
+	 * @param array                    $query_params
2692
+	 * @return int how many deleted
2693
+	 * @throws EE_Error
2694
+	 */
2695
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = array())
2696
+	{
2697
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2698
+		$relation_settings = $this->related_settings_for($model_name);
2699
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2700
+	}
2701
+
2702
+
2703
+
2704
+	/**
2705
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2706
+	 * unless otherwise specified in the $query_params
2707
+	 *
2708
+	 * @param        int             /EE_Base_Class $id_or_obj
2709
+	 * @param string $model_name     like 'Event', or 'Registration'
2710
+	 * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2711
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2712
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2713
+	 *                               that by the setting $distinct to TRUE;
2714
+	 * @return int
2715
+	 * @throws EE_Error
2716
+	 */
2717
+	public function count_related(
2718
+		$id_or_obj,
2719
+		$model_name,
2720
+		$query_params = array(),
2721
+		$field_to_count = null,
2722
+		$distinct = false
2723
+	) {
2724
+		$related_model = $this->get_related_model_obj($model_name);
2725
+		// we're just going to use the query params on the related model's normal get_all query,
2726
+		// except add a condition to say to match the current mod
2727
+		if (! isset($query_params['default_where_conditions'])) {
2728
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2729
+		}
2730
+		$this_model_name = $this->get_this_model_name();
2731
+		$this_pk_field_name = $this->get_primary_key_field()->get_name();
2732
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2733
+		return $related_model->count($query_params, $field_to_count, $distinct);
2734
+	}
2735
+
2736
+
2737
+
2738
+	/**
2739
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2740
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2741
+	 *
2742
+	 * @param        int           /EE_Base_Class $id_or_obj
2743
+	 * @param string $model_name   like 'Event', or 'Registration'
2744
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2745
+	 * @param string $field_to_sum name of field to count by. By default, uses primary key
2746
+	 * @return float
2747
+	 * @throws EE_Error
2748
+	 */
2749
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2750
+	{
2751
+		$related_model = $this->get_related_model_obj($model_name);
2752
+		if (! is_array($query_params)) {
2753
+			EE_Error::doing_it_wrong(
2754
+				'EEM_Base::sum_related',
2755
+				sprintf(
2756
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2757
+					gettype($query_params)
2758
+				),
2759
+				'4.6.0'
2760
+			);
2761
+			$query_params = array();
2762
+		}
2763
+		// we're just going to use the query params on the related model's normal get_all query,
2764
+		// except add a condition to say to match the current mod
2765
+		if (! isset($query_params['default_where_conditions'])) {
2766
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2767
+		}
2768
+		$this_model_name = $this->get_this_model_name();
2769
+		$this_pk_field_name = $this->get_primary_key_field()->get_name();
2770
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2771
+		return $related_model->sum($query_params, $field_to_sum);
2772
+	}
2773
+
2774
+
2775
+
2776
+	/**
2777
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2778
+	 * $modelObject
2779
+	 *
2780
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2781
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2782
+	 * @param array               $query_params     @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2783
+	 * @return EE_Base_Class
2784
+	 * @throws EE_Error
2785
+	 */
2786
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2787
+	{
2788
+		$query_params['limit'] = 1;
2789
+		$results = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2790
+		if ($results) {
2791
+			return array_shift($results);
2792
+		}
2793
+		return null;
2794
+	}
2795
+
2796
+
2797
+
2798
+	/**
2799
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2800
+	 *
2801
+	 * @return string
2802
+	 */
2803
+	public function get_this_model_name()
2804
+	{
2805
+		return str_replace("EEM_", "", get_class($this));
2806
+	}
2807
+
2808
+
2809
+
2810
+	/**
2811
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2812
+	 *
2813
+	 * @return EE_Any_Foreign_Model_Name_Field
2814
+	 * @throws EE_Error
2815
+	 */
2816
+	public function get_field_containing_related_model_name()
2817
+	{
2818
+		foreach ($this->field_settings(true) as $field) {
2819
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2820
+				$field_with_model_name = $field;
2821
+			}
2822
+		}
2823
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2824
+			throw new EE_Error(sprintf(
2825
+				esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2826
+				$this->get_this_model_name()
2827
+			));
2828
+		}
2829
+		return $field_with_model_name;
2830
+	}
2831
+
2832
+
2833
+
2834
+	/**
2835
+	 * Inserts a new entry into the database, for each table.
2836
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2837
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2838
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2839
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2840
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2841
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2842
+	 *
2843
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2844
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2845
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2846
+	 *                              of EEM_Base)
2847
+	 * @return int|string new primary key on main table that got inserted
2848
+	 * @throws EE_Error
2849
+	 */
2850
+	public function insert($field_n_values)
2851
+	{
2852
+		/**
2853
+		 * Filters the fields and their values before inserting an item using the models
2854
+		 *
2855
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2856
+		 * @param EEM_Base $model           the model used
2857
+		 */
2858
+		$field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2859
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2860
+			$main_table = $this->_get_main_table();
2861
+			$new_id = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2862
+			if ($new_id !== false) {
2863
+				foreach ($this->_get_other_tables() as $other_table) {
2864
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2865
+				}
2866
+			}
2867
+			/**
2868
+			 * Done just after attempting to insert a new model object
2869
+			 *
2870
+			 * @param EEM_Base   $model           used
2871
+			 * @param array      $fields_n_values fields and their values
2872
+			 * @param int|string the              ID of the newly-inserted model object
2873
+			 */
2874
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2875
+			return $new_id;
2876
+		}
2877
+		return false;
2878
+	}
2879
+
2880
+
2881
+
2882
+	/**
2883
+	 * Checks that the result would satisfy the unique indexes on this model
2884
+	 *
2885
+	 * @param array  $field_n_values
2886
+	 * @param string $action
2887
+	 * @return boolean
2888
+	 * @throws EE_Error
2889
+	 */
2890
+	protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2891
+	{
2892
+		foreach ($this->unique_indexes() as $index_name => $index) {
2893
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2894
+			if ($this->exists(array($uniqueness_where_params))) {
2895
+				EE_Error::add_error(
2896
+					sprintf(
2897
+						esc_html__(
2898
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2899
+							"event_espresso"
2900
+						),
2901
+						$action,
2902
+						$this->_get_class_name(),
2903
+						$index_name,
2904
+						implode(",", $index->field_names()),
2905
+						http_build_query($uniqueness_where_params)
2906
+					),
2907
+					__FILE__,
2908
+					__FUNCTION__,
2909
+					__LINE__
2910
+				);
2911
+				return false;
2912
+			}
2913
+		}
2914
+		return true;
2915
+	}
2916
+
2917
+
2918
+
2919
+	/**
2920
+	 * Checks the database for an item that conflicts (ie, if this item were
2921
+	 * saved to the DB would break some uniqueness requirement, like a primary key
2922
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
2923
+	 * can be either an EE_Base_Class or an array of fields n values
2924
+	 *
2925
+	 * @param EE_Base_Class|array $obj_or_fields_array
2926
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
2927
+	 *                                                 when looking for conflicts
2928
+	 *                                                 (ie, if false, we ignore the model object's primary key
2929
+	 *                                                 when finding "conflicts". If true, it's also considered).
2930
+	 *                                                 Only works for INT primary key,
2931
+	 *                                                 STRING primary keys cannot be ignored
2932
+	 * @throws EE_Error
2933
+	 * @return EE_Base_Class|array
2934
+	 */
2935
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2936
+	{
2937
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
2938
+			$fields_n_values = $obj_or_fields_array->model_field_array();
2939
+		} elseif (is_array($obj_or_fields_array)) {
2940
+			$fields_n_values = $obj_or_fields_array;
2941
+		} else {
2942
+			throw new EE_Error(
2943
+				sprintf(
2944
+					esc_html__(
2945
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2946
+						"event_espresso"
2947
+					),
2948
+					get_class($this),
2949
+					$obj_or_fields_array
2950
+				)
2951
+			);
2952
+		}
2953
+		$query_params = array();
2954
+		if (
2955
+			$this->has_primary_key_field()
2956
+			&& ($include_primary_key
2957
+				|| $this->get_primary_key_field()
2958
+				   instanceof
2959
+				   EE_Primary_Key_String_Field)
2960
+			&& isset($fields_n_values[ $this->primary_key_name() ])
2961
+		) {
2962
+			$query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2963
+		}
2964
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2965
+			$uniqueness_where_params = array_intersect_key($fields_n_values, $unique_index->fields());
2966
+			$query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2967
+		}
2968
+		// if there is nothing to base this search on, then we shouldn't find anything
2969
+		if (empty($query_params)) {
2970
+			return array();
2971
+		}
2972
+		return $this->get_one($query_params);
2973
+	}
2974
+
2975
+
2976
+
2977
+	/**
2978
+	 * Like count, but is optimized and returns a boolean instead of an int
2979
+	 *
2980
+	 * @param array $query_params
2981
+	 * @return boolean
2982
+	 * @throws EE_Error
2983
+	 */
2984
+	public function exists($query_params)
2985
+	{
2986
+		$query_params['limit'] = 1;
2987
+		return $this->count($query_params) > 0;
2988
+	}
2989
+
2990
+
2991
+
2992
+	/**
2993
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
2994
+	 *
2995
+	 * @param int|string $id
2996
+	 * @return boolean
2997
+	 * @throws EE_Error
2998
+	 */
2999
+	public function exists_by_ID($id)
3000
+	{
3001
+		return $this->exists(
3002
+			array(
3003
+				'default_where_conditions' => EEM_Base::default_where_conditions_none,
3004
+				array(
3005
+					$this->primary_key_name() => $id,
3006
+				),
3007
+			)
3008
+		);
3009
+	}
3010
+
3011
+
3012
+
3013
+	/**
3014
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3015
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3016
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3017
+	 * on the main table)
3018
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
3019
+	 * cases where we want to call it directly rather than via insert().
3020
+	 *
3021
+	 * @access   protected
3022
+	 * @param EE_Table_Base $table
3023
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3024
+	 *                                       float
3025
+	 * @param int           $new_id          for now we assume only int keys
3026
+	 * @throws EE_Error
3027
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3028
+	 * @return int ID of new row inserted, or FALSE on failure
3029
+	 */
3030
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3031
+	{
3032
+		global $wpdb;
3033
+		$insertion_col_n_values = array();
3034
+		$format_for_insertion = array();
3035
+		$fields_on_table = $this->_get_fields_for_table($table->get_table_alias());
3036
+		foreach ($fields_on_table as $field_name => $field_obj) {
3037
+			// check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3038
+			if ($field_obj->is_auto_increment()) {
3039
+				continue;
3040
+			}
3041
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3042
+			// if the value we want to assign it to is NULL, just don't mention it for the insertion
3043
+			if ($prepared_value !== null) {
3044
+				$insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3045
+				$format_for_insertion[] = $field_obj->get_wpdb_data_type();
3046
+			}
3047
+		}
3048
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3049
+			// its not the main table, so we should have already saved the main table's PK which we just inserted
3050
+			// so add the fk to the main table as a column
3051
+			$insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3052
+			$format_for_insertion[] = '%d';// yes right now we're only allowing these foreign keys to be INTs
3053
+		}
3054
+		// insert the new entry
3055
+		$result = $this->_do_wpdb_query(
3056
+			'insert',
3057
+			array($table->get_table_name(), $insertion_col_n_values, $format_for_insertion)
3058
+		);
3059
+		if ($result === false) {
3060
+			return false;
3061
+		}
3062
+		// ok, now what do we return for the ID of the newly-inserted thing?
3063
+		if ($this->has_primary_key_field()) {
3064
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3065
+				return $wpdb->insert_id;
3066
+			}
3067
+			// it's not an auto-increment primary key, so
3068
+			// it must have been supplied
3069
+			return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3070
+		}
3071
+		// we can't return a  primary key because there is none. instead return
3072
+		// a unique string indicating this model
3073
+		return $this->get_index_primary_key_string($fields_n_values);
3074
+	}
3075
+
3076
+
3077
+
3078
+	/**
3079
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3080
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3081
+	 * and there is no default, we pass it along. WPDB will take care of it)
3082
+	 *
3083
+	 * @param EE_Model_Field_Base $field_obj
3084
+	 * @param array               $fields_n_values
3085
+	 * @return mixed string|int|float depending on what the table column will be expecting
3086
+	 * @throws EE_Error
3087
+	 */
3088
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3089
+	{
3090
+		// if this field doesn't allow nullable, don't allow it
3091
+		if (
3092
+			! $field_obj->is_nullable()
3093
+			&& (
3094
+				! isset($fields_n_values[ $field_obj->get_name() ])
3095
+				|| $fields_n_values[ $field_obj->get_name() ] === null
3096
+			)
3097
+		) {
3098
+			$fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3099
+		}
3100
+		$unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3101
+			? $fields_n_values[ $field_obj->get_name() ]
3102
+			: null;
3103
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3104
+	}
3105
+
3106
+
3107
+
3108
+	/**
3109
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3110
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3111
+	 * the field's prepare_for_set() method.
3112
+	 *
3113
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3114
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3115
+	 *                                   top of file)
3116
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3117
+	 *                                   $value is a custom selection
3118
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3119
+	 */
3120
+	private function _prepare_value_for_use_in_db($value, $field)
3121
+	{
3122
+		if ($field && $field instanceof EE_Model_Field_Base) {
3123
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3124
+			switch ($this->_values_already_prepared_by_model_object) {
3125
+				/** @noinspection PhpMissingBreakStatementInspection */
3126
+				case self::not_prepared_by_model_object:
3127
+					$value = $field->prepare_for_set($value);
3128
+				// purposefully left out "return"
3129
+				// no break
3130
+				case self::prepared_by_model_object:
3131
+					/** @noinspection SuspiciousAssignmentsInspection */
3132
+					$value = $field->prepare_for_use_in_db($value);
3133
+					// no break
3134
+				case self::prepared_for_use_in_db:
3135
+					// leave the value alone
3136
+			}
3137
+			return $value;
3138
+			// phpcs:enable
3139
+		}
3140
+		return $value;
3141
+	}
3142
+
3143
+
3144
+
3145
+	/**
3146
+	 * Returns the main table on this model
3147
+	 *
3148
+	 * @return EE_Primary_Table
3149
+	 * @throws EE_Error
3150
+	 */
3151
+	protected function _get_main_table()
3152
+	{
3153
+		foreach ($this->_tables as $table) {
3154
+			if ($table instanceof EE_Primary_Table) {
3155
+				return $table;
3156
+			}
3157
+		}
3158
+		throw new EE_Error(sprintf(esc_html__(
3159
+			'There are no main tables on %s. They should be added to _tables array in the constructor',
3160
+			'event_espresso'
3161
+		), get_class($this)));
3162
+	}
3163
+
3164
+
3165
+
3166
+	/**
3167
+	 * table
3168
+	 * returns EE_Primary_Table table name
3169
+	 *
3170
+	 * @return string
3171
+	 * @throws EE_Error
3172
+	 */
3173
+	public function table()
3174
+	{
3175
+		return $this->_get_main_table()->get_table_name();
3176
+	}
3177
+
3178
+
3179
+
3180
+	/**
3181
+	 * table
3182
+	 * returns first EE_Secondary_Table table name
3183
+	 *
3184
+	 * @return string
3185
+	 */
3186
+	public function second_table()
3187
+	{
3188
+		// grab second table from tables array
3189
+		$second_table = end($this->_tables);
3190
+		return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3191
+	}
3192
+
3193
+
3194
+
3195
+	/**
3196
+	 * get_table_obj_by_alias
3197
+	 * returns table name given it's alias
3198
+	 *
3199
+	 * @param string $table_alias
3200
+	 * @return EE_Primary_Table | EE_Secondary_Table
3201
+	 */
3202
+	public function get_table_obj_by_alias($table_alias = '')
3203
+	{
3204
+		return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3205
+	}
3206
+
3207
+
3208
+
3209
+	/**
3210
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3211
+	 *
3212
+	 * @return EE_Secondary_Table[]
3213
+	 */
3214
+	protected function _get_other_tables()
3215
+	{
3216
+		$other_tables = array();
3217
+		foreach ($this->_tables as $table_alias => $table) {
3218
+			if ($table instanceof EE_Secondary_Table) {
3219
+				$other_tables[ $table_alias ] = $table;
3220
+			}
3221
+		}
3222
+		return $other_tables;
3223
+	}
3224
+
3225
+
3226
+
3227
+	/**
3228
+	 * Finds all the fields that correspond to the given table
3229
+	 *
3230
+	 * @param string $table_alias , array key in EEM_Base::_tables
3231
+	 * @return EE_Model_Field_Base[]
3232
+	 */
3233
+	public function _get_fields_for_table($table_alias)
3234
+	{
3235
+		return $this->_fields[ $table_alias ];
3236
+	}
3237
+
3238
+
3239
+
3240
+	/**
3241
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3242
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3243
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3244
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3245
+	 * related Registration, Transaction, and Payment models.
3246
+	 *
3247
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3248
+	 * @return EE_Model_Query_Info_Carrier
3249
+	 * @throws EE_Error
3250
+	 */
3251
+	public function _extract_related_models_from_query($query_params)
3252
+	{
3253
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3254
+		if (array_key_exists(0, $query_params)) {
3255
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3256
+		}
3257
+		if (array_key_exists('group_by', $query_params)) {
3258
+			if (is_array($query_params['group_by'])) {
3259
+				$this->_extract_related_models_from_sub_params_array_values(
3260
+					$query_params['group_by'],
3261
+					$query_info_carrier,
3262
+					'group_by'
3263
+				);
3264
+			} elseif (! empty($query_params['group_by'])) {
3265
+				$this->_extract_related_model_info_from_query_param(
3266
+					$query_params['group_by'],
3267
+					$query_info_carrier,
3268
+					'group_by'
3269
+				);
3270
+			}
3271
+		}
3272
+		if (array_key_exists('having', $query_params)) {
3273
+			$this->_extract_related_models_from_sub_params_array_keys(
3274
+				$query_params[0],
3275
+				$query_info_carrier,
3276
+				'having'
3277
+			);
3278
+		}
3279
+		if (array_key_exists('order_by', $query_params)) {
3280
+			if (is_array($query_params['order_by'])) {
3281
+				$this->_extract_related_models_from_sub_params_array_keys(
3282
+					$query_params['order_by'],
3283
+					$query_info_carrier,
3284
+					'order_by'
3285
+				);
3286
+			} elseif (! empty($query_params['order_by'])) {
3287
+				$this->_extract_related_model_info_from_query_param(
3288
+					$query_params['order_by'],
3289
+					$query_info_carrier,
3290
+					'order_by'
3291
+				);
3292
+			}
3293
+		}
3294
+		if (array_key_exists('force_join', $query_params)) {
3295
+			$this->_extract_related_models_from_sub_params_array_values(
3296
+				$query_params['force_join'],
3297
+				$query_info_carrier,
3298
+				'force_join'
3299
+			);
3300
+		}
3301
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3302
+		return $query_info_carrier;
3303
+	}
3304
+
3305
+
3306
+
3307
+	/**
3308
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3309
+	 *
3310
+	 * @param array                       $sub_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3311
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3312
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3313
+	 * @throws EE_Error
3314
+	 * @return \EE_Model_Query_Info_Carrier
3315
+	 */
3316
+	private function _extract_related_models_from_sub_params_array_keys(
3317
+		$sub_query_params,
3318
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3319
+		$query_param_type
3320
+	) {
3321
+		if (! empty($sub_query_params)) {
3322
+			$sub_query_params = (array) $sub_query_params;
3323
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3324
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3325
+				$this->_extract_related_model_info_from_query_param(
3326
+					$param,
3327
+					$model_query_info_carrier,
3328
+					$query_param_type
3329
+				);
3330
+				// if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3331
+				// indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3332
+				// extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3333
+				// of array('Registration.TXN_ID'=>23)
3334
+				$query_param_sans_stars = $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3335
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3336
+					if (! is_array($possibly_array_of_params)) {
3337
+						throw new EE_Error(sprintf(
3338
+							esc_html__(
3339
+								"You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3340
+								"event_espresso"
3341
+							),
3342
+							$param,
3343
+							$possibly_array_of_params
3344
+						));
3345
+					}
3346
+					$this->_extract_related_models_from_sub_params_array_keys(
3347
+						$possibly_array_of_params,
3348
+						$model_query_info_carrier,
3349
+						$query_param_type
3350
+					);
3351
+				} elseif (
3352
+					$query_param_type === 0 // ie WHERE
3353
+						  && is_array($possibly_array_of_params)
3354
+						  && isset($possibly_array_of_params[2])
3355
+						  && $possibly_array_of_params[2] == true
3356
+				) {
3357
+					// then $possible_array_of_params looks something like array('<','DTT_sold',true)
3358
+					// indicating that $possible_array_of_params[1] is actually a field name,
3359
+					// from which we should extract query parameters!
3360
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3361
+						throw new EE_Error(sprintf(esc_html__(
3362
+							"Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3363
+							"event_espresso"
3364
+						), $query_param_type, implode(",", $possibly_array_of_params)));
3365
+					}
3366
+					$this->_extract_related_model_info_from_query_param(
3367
+						$possibly_array_of_params[1],
3368
+						$model_query_info_carrier,
3369
+						$query_param_type
3370
+					);
3371
+				}
3372
+			}
3373
+		}
3374
+		return $model_query_info_carrier;
3375
+	}
3376
+
3377
+
3378
+
3379
+	/**
3380
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3381
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3382
+	 *
3383
+	 * @param array                       $sub_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3384
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3385
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3386
+	 * @throws EE_Error
3387
+	 * @return \EE_Model_Query_Info_Carrier
3388
+	 */
3389
+	private function _extract_related_models_from_sub_params_array_values(
3390
+		$sub_query_params,
3391
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3392
+		$query_param_type
3393
+	) {
3394
+		if (! empty($sub_query_params)) {
3395
+			if (! is_array($sub_query_params)) {
3396
+				throw new EE_Error(sprintf(
3397
+					esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3398
+					$sub_query_params
3399
+				));
3400
+			}
3401
+			foreach ($sub_query_params as $param) {
3402
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3403
+				$this->_extract_related_model_info_from_query_param(
3404
+					$param,
3405
+					$model_query_info_carrier,
3406
+					$query_param_type
3407
+				);
3408
+			}
3409
+		}
3410
+		return $model_query_info_carrier;
3411
+	}
3412
+
3413
+
3414
+	/**
3415
+	 * Extract all the query parts from  model query params
3416
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3417
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3418
+	 * but use them in a different order. Eg, we need to know what models we are querying
3419
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3420
+	 * other models before we can finalize the where clause SQL.
3421
+	 *
3422
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3423
+	 * @throws EE_Error
3424
+	 * @return EE_Model_Query_Info_Carrier
3425
+	 * @throws ModelConfigurationException
3426
+	 */
3427
+	public function _create_model_query_info_carrier($query_params)
3428
+	{
3429
+		if (! is_array($query_params)) {
3430
+			EE_Error::doing_it_wrong(
3431
+				'EEM_Base::_create_model_query_info_carrier',
3432
+				sprintf(
3433
+					esc_html__(
3434
+						'$query_params should be an array, you passed a variable of type %s',
3435
+						'event_espresso'
3436
+					),
3437
+					gettype($query_params)
3438
+				),
3439
+				'4.6.0'
3440
+			);
3441
+			$query_params = array();
3442
+		}
3443
+		$query_params[0] = isset($query_params[0]) ? $query_params[0] : array();
3444
+		// first check if we should alter the query to account for caps or not
3445
+		// because the caps might require us to do extra joins
3446
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3447
+			$query_params[0] = array_replace_recursive(
3448
+				$query_params[0],
3449
+				$this->caps_where_conditions($query_params['caps'])
3450
+			);
3451
+		}
3452
+
3453
+		// check if we should alter the query to remove data related to protected
3454
+		// custom post types
3455
+		if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3456
+			$where_param_key_for_password = $this->modelChainAndPassword();
3457
+			// only include if related to a cpt where no password has been set
3458
+			$query_params[0]['OR*nopassword'] = array(
3459
+				$where_param_key_for_password => '',
3460
+				$where_param_key_for_password . '*' => array('IS_NULL')
3461
+			);
3462
+		}
3463
+		$query_object = $this->_extract_related_models_from_query($query_params);
3464
+		// verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3465
+		foreach ($query_params[0] as $key => $value) {
3466
+			if (is_int($key)) {
3467
+				throw new EE_Error(
3468
+					sprintf(
3469
+						esc_html__(
3470
+							"WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3471
+							"event_espresso"
3472
+						),
3473
+						$key,
3474
+						var_export($value, true),
3475
+						var_export($query_params, true),
3476
+						get_class($this)
3477
+					)
3478
+				);
3479
+			}
3480
+		}
3481
+		if (
3482
+			array_key_exists('default_where_conditions', $query_params)
3483
+			&& ! empty($query_params['default_where_conditions'])
3484
+		) {
3485
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3486
+		} else {
3487
+			$use_default_where_conditions = EEM_Base::default_where_conditions_all;
3488
+		}
3489
+		$query_params[0] = array_merge(
3490
+			$this->_get_default_where_conditions_for_models_in_query(
3491
+				$query_object,
3492
+				$use_default_where_conditions,
3493
+				$query_params[0]
3494
+			),
3495
+			$query_params[0]
3496
+		);
3497
+		$query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3498
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3499
+		// So we need to setup a subquery and use that for the main join.
3500
+		// Note for now this only works on the primary table for the model.
3501
+		// So for instance, you could set the limit array like this:
3502
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3503
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3504
+			$query_object->set_main_model_join_sql(
3505
+				$this->_construct_limit_join_select(
3506
+					$query_params['on_join_limit'][0],
3507
+					$query_params['on_join_limit'][1]
3508
+				)
3509
+			);
3510
+		}
3511
+		// set limit
3512
+		if (array_key_exists('limit', $query_params)) {
3513
+			if (is_array($query_params['limit'])) {
3514
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3515
+					$e = sprintf(
3516
+						esc_html__(
3517
+							"Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3518
+							"event_espresso"
3519
+						),
3520
+						http_build_query($query_params['limit'])
3521
+					);
3522
+					throw new EE_Error($e . "|" . $e);
3523
+				}
3524
+				// they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3525
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3526
+			} elseif (! empty($query_params['limit'])) {
3527
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3528
+			}
3529
+		}
3530
+		// set order by
3531
+		if (array_key_exists('order_by', $query_params)) {
3532
+			if (is_array($query_params['order_by'])) {
3533
+				// if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3534
+				// specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3535
+				// including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3536
+				if (array_key_exists('order', $query_params)) {
3537
+					throw new EE_Error(
3538
+						sprintf(
3539
+							esc_html__(
3540
+								"In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3541
+								"event_espresso"
3542
+							),
3543
+							get_class($this),
3544
+							implode(", ", array_keys($query_params['order_by'])),
3545
+							implode(", ", $query_params['order_by']),
3546
+							$query_params['order']
3547
+						)
3548
+					);
3549
+				}
3550
+				$this->_extract_related_models_from_sub_params_array_keys(
3551
+					$query_params['order_by'],
3552
+					$query_object,
3553
+					'order_by'
3554
+				);
3555
+				// assume it's an array of fields to order by
3556
+				$order_array = array();
3557
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3558
+					$order = $this->_extract_order($order);
3559
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3560
+				}
3561
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3562
+			} elseif (! empty($query_params['order_by'])) {
3563
+				$this->_extract_related_model_info_from_query_param(
3564
+					$query_params['order_by'],
3565
+					$query_object,
3566
+					'order',
3567
+					$query_params['order_by']
3568
+				);
3569
+				$order = isset($query_params['order'])
3570
+					? $this->_extract_order($query_params['order'])
3571
+					: 'DESC';
3572
+				$query_object->set_order_by_sql(
3573
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3574
+				);
3575
+			}
3576
+		}
3577
+		// if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3578
+		if (
3579
+			! array_key_exists('order_by', $query_params)
3580
+			&& array_key_exists('order', $query_params)
3581
+			&& ! empty($query_params['order'])
3582
+		) {
3583
+			$pk_field = $this->get_primary_key_field();
3584
+			$order = $this->_extract_order($query_params['order']);
3585
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3586
+		}
3587
+		// set group by
3588
+		if (array_key_exists('group_by', $query_params)) {
3589
+			if (is_array($query_params['group_by'])) {
3590
+				// it's an array, so assume we'll be grouping by a bunch of stuff
3591
+				$group_by_array = array();
3592
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3593
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3594
+				}
3595
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3596
+			} elseif (! empty($query_params['group_by'])) {
3597
+				$query_object->set_group_by_sql(
3598
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3599
+				);
3600
+			}
3601
+		}
3602
+		// set having
3603
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3604
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3605
+		}
3606
+		// now, just verify they didn't pass anything wack
3607
+		foreach ($query_params as $query_key => $query_value) {
3608
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3609
+				throw new EE_Error(
3610
+					sprintf(
3611
+						esc_html__(
3612
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3613
+							'event_espresso'
3614
+						),
3615
+						$query_key,
3616
+						get_class($this),
3617
+						//                      print_r( $this->_allowed_query_params, TRUE )
3618
+						implode(',', $this->_allowed_query_params)
3619
+					)
3620
+				);
3621
+			}
3622
+		}
3623
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3624
+		if (empty($main_model_join_sql)) {
3625
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3626
+		}
3627
+		return $query_object;
3628
+	}
3629
+
3630
+
3631
+
3632
+	/**
3633
+	 * Gets the where conditions that should be imposed on the query based on the
3634
+	 * context (eg reading frontend, backend, edit or delete).
3635
+	 *
3636
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3637
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3638
+	 * @throws EE_Error
3639
+	 */
3640
+	public function caps_where_conditions($context = self::caps_read)
3641
+	{
3642
+		EEM_Base::verify_is_valid_cap_context($context);
3643
+		$cap_where_conditions = array();
3644
+		$cap_restrictions = $this->caps_missing($context);
3645
+		/**
3646
+		 * @var $cap_restrictions EE_Default_Where_Conditions[]
3647
+		 */
3648
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3649
+			$cap_where_conditions = array_replace_recursive(
3650
+				$cap_where_conditions,
3651
+				$restriction_if_no_cap->get_default_where_conditions()
3652
+			);
3653
+		}
3654
+		return apply_filters(
3655
+			'FHEE__EEM_Base__caps_where_conditions__return',
3656
+			$cap_where_conditions,
3657
+			$this,
3658
+			$context,
3659
+			$cap_restrictions
3660
+		);
3661
+	}
3662
+
3663
+
3664
+
3665
+	/**
3666
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3667
+	 * otherwise throws an exception
3668
+	 *
3669
+	 * @param string $should_be_order_string
3670
+	 * @return string either ASC, asc, DESC or desc
3671
+	 * @throws EE_Error
3672
+	 */
3673
+	private function _extract_order($should_be_order_string)
3674
+	{
3675
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3676
+			return $should_be_order_string;
3677
+		}
3678
+		throw new EE_Error(
3679
+			sprintf(
3680
+				esc_html__(
3681
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3682
+					"event_espresso"
3683
+				),
3684
+				get_class($this),
3685
+				$should_be_order_string
3686
+			)
3687
+		);
3688
+	}
3689
+
3690
+
3691
+
3692
+	/**
3693
+	 * Looks at all the models which are included in this query, and asks each
3694
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3695
+	 * so they can be merged
3696
+	 *
3697
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3698
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3699
+	 *                                                                  'none' means NO default where conditions will
3700
+	 *                                                                  be used AT ALL during this query.
3701
+	 *                                                                  'other_models_only' means default where
3702
+	 *                                                                  conditions from other models will be used, but
3703
+	 *                                                                  not for this primary model. 'all', the default,
3704
+	 *                                                                  means default where conditions will apply as
3705
+	 *                                                                  normal
3706
+	 * @param array                       $where_query_params           @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3707
+	 * @throws EE_Error
3708
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3709
+	 */
3710
+	private function _get_default_where_conditions_for_models_in_query(
3711
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3712
+		$use_default_where_conditions = EEM_Base::default_where_conditions_all,
3713
+		$where_query_params = array()
3714
+	) {
3715
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3716
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3717
+			throw new EE_Error(sprintf(
3718
+				esc_html__(
3719
+					"You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3720
+					"event_espresso"
3721
+				),
3722
+				$use_default_where_conditions,
3723
+				implode(", ", $allowed_used_default_where_conditions_values)
3724
+			));
3725
+		}
3726
+		$universal_query_params = array();
3727
+		if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3728
+			$universal_query_params = $this->_get_default_where_conditions();
3729
+		} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3730
+			$universal_query_params = $this->_get_minimum_where_conditions();
3731
+		}
3732
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3733
+			$related_model = $this->get_related_model_obj($model_name);
3734
+			if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3735
+				$related_model_universal_where_params = $related_model->_get_default_where_conditions($model_relation_path);
3736
+			} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3737
+				$related_model_universal_where_params = $related_model->_get_minimum_where_conditions($model_relation_path);
3738
+			} else {
3739
+				// we don't want to add full or even minimum default where conditions from this model, so just continue
3740
+				continue;
3741
+			}
3742
+			$overrides = $this->_override_defaults_or_make_null_friendly(
3743
+				$related_model_universal_where_params,
3744
+				$where_query_params,
3745
+				$related_model,
3746
+				$model_relation_path
3747
+			);
3748
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3749
+				$universal_query_params,
3750
+				$overrides
3751
+			);
3752
+		}
3753
+		return $universal_query_params;
3754
+	}
3755
+
3756
+
3757
+
3758
+	/**
3759
+	 * Determines whether or not we should use default where conditions for the model in question
3760
+	 * (this model, or other related models).
3761
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3762
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3763
+	 * We should use default where conditions on related models when they requested to use default where conditions
3764
+	 * on all models, or specifically just on other related models
3765
+	 * @param      $default_where_conditions_value
3766
+	 * @param bool $for_this_model false means this is for OTHER related models
3767
+	 * @return bool
3768
+	 */
3769
+	private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3770
+	{
3771
+		return (
3772
+				   $for_this_model
3773
+				   && in_array(
3774
+					   $default_where_conditions_value,
3775
+					   array(
3776
+						   EEM_Base::default_where_conditions_all,
3777
+						   EEM_Base::default_where_conditions_this_only,
3778
+						   EEM_Base::default_where_conditions_minimum_others,
3779
+					   ),
3780
+					   true
3781
+				   )
3782
+			   )
3783
+			   || (
3784
+				   ! $for_this_model
3785
+				   && in_array(
3786
+					   $default_where_conditions_value,
3787
+					   array(
3788
+						   EEM_Base::default_where_conditions_all,
3789
+						   EEM_Base::default_where_conditions_others_only,
3790
+					   ),
3791
+					   true
3792
+				   )
3793
+			   );
3794
+	}
3795
+
3796
+	/**
3797
+	 * Determines whether or not we should use default minimum conditions for the model in question
3798
+	 * (this model, or other related models).
3799
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3800
+	 * where conditions.
3801
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3802
+	 * on this model or others
3803
+	 * @param      $default_where_conditions_value
3804
+	 * @param bool $for_this_model false means this is for OTHER related models
3805
+	 * @return bool
3806
+	 */
3807
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3808
+	{
3809
+		return (
3810
+				   $for_this_model
3811
+				   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3812
+			   )
3813
+			   || (
3814
+				   ! $for_this_model
3815
+				   && in_array(
3816
+					   $default_where_conditions_value,
3817
+					   array(
3818
+						   EEM_Base::default_where_conditions_minimum_others,
3819
+						   EEM_Base::default_where_conditions_minimum_all,
3820
+					   ),
3821
+					   true
3822
+				   )
3823
+			   );
3824
+	}
3825
+
3826
+
3827
+	/**
3828
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3829
+	 * then we also add a special where condition which allows for that model's primary key
3830
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3831
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3832
+	 *
3833
+	 * @param array    $default_where_conditions
3834
+	 * @param array    $provided_where_conditions
3835
+	 * @param EEM_Base $model
3836
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3837
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3838
+	 * @throws EE_Error
3839
+	 */
3840
+	private function _override_defaults_or_make_null_friendly(
3841
+		$default_where_conditions,
3842
+		$provided_where_conditions,
3843
+		$model,
3844
+		$model_relation_path
3845
+	) {
3846
+		$null_friendly_where_conditions = array();
3847
+		$none_overridden = true;
3848
+		$or_condition_key_for_defaults = 'OR*' . get_class($model);
3849
+		foreach ($default_where_conditions as $key => $val) {
3850
+			if (isset($provided_where_conditions[ $key ])) {
3851
+				$none_overridden = false;
3852
+			} else {
3853
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3854
+			}
3855
+		}
3856
+		if ($none_overridden && $default_where_conditions) {
3857
+			if ($model->has_primary_key_field()) {
3858
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3859
+																				. "."
3860
+																				. $model->primary_key_name() ] = array('IS NULL');
3861
+			}/*else{
3862 3862
                 //@todo NO PK, use other defaults
3863 3863
             }*/
3864
-        }
3865
-        return $null_friendly_where_conditions;
3866
-    }
3867
-
3868
-
3869
-
3870
-    /**
3871
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3872
-     * default where conditions on all get_all, update, and delete queries done by this model.
3873
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3874
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3875
-     *
3876
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3877
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3878
-     */
3879
-    private function _get_default_where_conditions($model_relation_path = '')
3880
-    {
3881
-        if ($this->_ignore_where_strategy) {
3882
-            return array();
3883
-        }
3884
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3885
-    }
3886
-
3887
-
3888
-
3889
-    /**
3890
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3891
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3892
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3893
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3894
-     * Similar to _get_default_where_conditions
3895
-     *
3896
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3897
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3898
-     */
3899
-    protected function _get_minimum_where_conditions($model_relation_path = '')
3900
-    {
3901
-        if ($this->_ignore_where_strategy) {
3902
-            return array();
3903
-        }
3904
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3905
-    }
3906
-
3907
-
3908
-
3909
-    /**
3910
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3911
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3912
-     *
3913
-     * @param EE_Model_Query_Info_Carrier $model_query_info
3914
-     * @return string
3915
-     * @throws EE_Error
3916
-     */
3917
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3918
-    {
3919
-        $selects = $this->_get_columns_to_select_for_this_model();
3920
-        foreach (
3921
-            $model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3922
-        ) {
3923
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3924
-            $other_model_selects = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3925
-            foreach ($other_model_selects as $key => $value) {
3926
-                $selects[] = $value;
3927
-            }
3928
-        }
3929
-        return implode(", ", $selects);
3930
-    }
3931
-
3932
-
3933
-
3934
-    /**
3935
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3936
-     * So that's going to be the columns for all the fields on the model
3937
-     *
3938
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
3939
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3940
-     */
3941
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3942
-    {
3943
-        $fields = $this->field_settings();
3944
-        $selects = array();
3945
-        $table_alias_with_model_relation_chain_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3946
-            $model_relation_chain,
3947
-            $this->get_this_model_name()
3948
-        );
3949
-        foreach ($fields as $field_obj) {
3950
-            $selects[] = $table_alias_with_model_relation_chain_prefix
3951
-                         . $field_obj->get_table_alias()
3952
-                         . "."
3953
-                         . $field_obj->get_table_column()
3954
-                         . " AS '"
3955
-                         . $table_alias_with_model_relation_chain_prefix
3956
-                         . $field_obj->get_table_alias()
3957
-                         . "."
3958
-                         . $field_obj->get_table_column()
3959
-                         . "'";
3960
-        }
3961
-        // make sure we are also getting the PKs of each table
3962
-        $tables = $this->get_tables();
3963
-        if (count($tables) > 1) {
3964
-            foreach ($tables as $table_obj) {
3965
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3966
-                                       . $table_obj->get_fully_qualified_pk_column();
3967
-                if (! in_array($qualified_pk_column, $selects)) {
3968
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3969
-                }
3970
-            }
3971
-        }
3972
-        return $selects;
3973
-    }
3974
-
3975
-
3976
-
3977
-    /**
3978
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3979
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3980
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3981
-     * SQL for joining, and the data types
3982
-     *
3983
-     * @param null|string                 $original_query_param
3984
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
3985
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3986
-     * @param    string                   $query_param_type     like Registration.Transaction.TXN_ID
3987
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3988
-     *                                                          column name. We only want model names, eg 'Event.Venue'
3989
-     *                                                          or 'Registration's
3990
-     * @param string                      $original_query_param what it originally was (eg
3991
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3992
-     *                                                          matches $query_param
3993
-     * @throws EE_Error
3994
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3995
-     */
3996
-    private function _extract_related_model_info_from_query_param(
3997
-        $query_param,
3998
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
3999
-        $query_param_type,
4000
-        $original_query_param = null
4001
-    ) {
4002
-        if ($original_query_param === null) {
4003
-            $original_query_param = $query_param;
4004
-        }
4005
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4006
-        // check to see if we have a field on this model
4007
-        $this_model_fields = $this->field_settings(true);
4008
-        if (array_key_exists($query_param, $this_model_fields)) {
4009
-            $field_is_allowed = in_array(
4010
-                $query_param_type,
4011
-                [0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4012
-                true
4013
-            );
4014
-            if ($field_is_allowed) {
4015
-                return;
4016
-            }
4017
-            throw new EE_Error(
4018
-                sprintf(
4019
-                    esc_html__(
4020
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4021
-                        "event_espresso"
4022
-                    ),
4023
-                    $query_param,
4024
-                    get_class($this),
4025
-                    $query_param_type,
4026
-                    $original_query_param
4027
-                )
4028
-            );
4029
-        }
4030
-        // check if this is a special logic query param
4031
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4032
-            $operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4033
-            if ($operator_is_allowed) {
4034
-                return;
4035
-            }
4036
-            throw new EE_Error(
4037
-                sprintf(
4038
-                    esc_html__(
4039
-                        'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
4040
-                        'event_espresso'
4041
-                    ),
4042
-                    implode('", "', $this->_logic_query_param_keys),
4043
-                    $query_param,
4044
-                    get_class($this),
4045
-                    '<br />',
4046
-                    "\t"
4047
-                    . ' $passed_in_query_info = <pre>'
4048
-                    . print_r($passed_in_query_info, true)
4049
-                    . '</pre>'
4050
-                    . "\n\t"
4051
-                    . ' $query_param_type = '
4052
-                    . $query_param_type
4053
-                    . "\n\t"
4054
-                    . ' $original_query_param = '
4055
-                    . $original_query_param
4056
-                )
4057
-            );
4058
-        }
4059
-        // check if it's a custom selection
4060
-        if (
4061
-            $this->_custom_selections instanceof CustomSelects
4062
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4063
-        ) {
4064
-            return;
4065
-        }
4066
-        // check if has a model name at the beginning
4067
-        // and
4068
-        // check if it's a field on a related model
4069
-        if (
4070
-            $this->extractJoinModelFromQueryParams(
4071
-                $passed_in_query_info,
4072
-                $query_param,
4073
-                $original_query_param,
4074
-                $query_param_type
4075
-            )
4076
-        ) {
4077
-            return;
4078
-        }
4079
-
4080
-        // ok so $query_param didn't start with a model name
4081
-        // and we previously confirmed it wasn't a logic query param or field on the current model
4082
-        // it's wack, that's what it is
4083
-        throw new EE_Error(
4084
-            sprintf(
4085
-                esc_html__(
4086
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4087
-                    "event_espresso"
4088
-                ),
4089
-                $query_param,
4090
-                get_class($this),
4091
-                $query_param_type,
4092
-                $original_query_param
4093
-            )
4094
-        );
4095
-    }
4096
-
4097
-
4098
-    /**
4099
-     * Extracts any possible join model information from the provided possible_join_string.
4100
-     * This method will read the provided $possible_join_string value and determine if there are any possible model join
4101
-     * parts that should be added to the query.
4102
-     *
4103
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4104
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4105
-     * @param null|string                 $original_query_param
4106
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4107
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects' etc.)
4108
-     * @return bool  returns true if a join was added and false if not.
4109
-     * @throws EE_Error
4110
-     */
4111
-    private function extractJoinModelFromQueryParams(
4112
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4113
-        $possible_join_string,
4114
-        $original_query_param,
4115
-        $query_parameter_type
4116
-    ) {
4117
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4118
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4119
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4120
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4121
-                if ($possible_join_string === '') {
4122
-                    // nothing left to $query_param
4123
-                    // we should actually end in a field name, not a model like this!
4124
-                    throw new EE_Error(
4125
-                        sprintf(
4126
-                            esc_html__(
4127
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4128
-                                "event_espresso"
4129
-                            ),
4130
-                            $possible_join_string,
4131
-                            $query_parameter_type,
4132
-                            get_class($this),
4133
-                            $valid_related_model_name
4134
-                        )
4135
-                    );
4136
-                }
4137
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4138
-                $related_model_obj->_extract_related_model_info_from_query_param(
4139
-                    $possible_join_string,
4140
-                    $query_info_carrier,
4141
-                    $query_parameter_type,
4142
-                    $original_query_param
4143
-                );
4144
-                return true;
4145
-            }
4146
-            if ($possible_join_string === $valid_related_model_name) {
4147
-                $this->_add_join_to_model(
4148
-                    $valid_related_model_name,
4149
-                    $query_info_carrier,
4150
-                    $original_query_param
4151
-                );
4152
-                return true;
4153
-            }
4154
-        }
4155
-        return false;
4156
-    }
4157
-
4158
-
4159
-    /**
4160
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4161
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4162
-     * @throws EE_Error
4163
-     */
4164
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4165
-    {
4166
-        if (
4167
-            $this->_custom_selections instanceof CustomSelects
4168
-            && (
4169
-                $this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4170
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4171
-            )
4172
-        ) {
4173
-            $original_selects = $this->_custom_selections->originalSelects();
4174
-            foreach ($original_selects as $alias => $select_configuration) {
4175
-                $this->extractJoinModelFromQueryParams(
4176
-                    $query_info_carrier,
4177
-                    $select_configuration[0],
4178
-                    $select_configuration[0],
4179
-                    'custom_selects'
4180
-                );
4181
-            }
4182
-        }
4183
-    }
4184
-
4185
-
4186
-
4187
-    /**
4188
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4189
-     * and store it on $passed_in_query_info
4190
-     *
4191
-     * @param string                      $model_name
4192
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4193
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4194
-     *                                                          model and $model_name. Eg, if we are querying Event,
4195
-     *                                                          and are adding a join to 'Payment' with the original
4196
-     *                                                          query param key
4197
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4198
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4199
-     *                                                          Payment wants to add default query params so that it
4200
-     *                                                          will know what models to prepend onto its default query
4201
-     *                                                          params or in case it wants to rename tables (in case
4202
-     *                                                          there are multiple joins to the same table)
4203
-     * @return void
4204
-     * @throws EE_Error
4205
-     */
4206
-    private function _add_join_to_model(
4207
-        $model_name,
4208
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4209
-        $original_query_param
4210
-    ) {
4211
-        $relation_obj = $this->related_settings_for($model_name);
4212
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4213
-        // check if the relation is HABTM, because then we're essentially doing two joins
4214
-        // If so, join first to the JOIN table, and add its data types, and then continue as normal
4215
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4216
-            $join_model_obj = $relation_obj->get_join_model();
4217
-            // replace the model specified with the join model for this relation chain, whi
4218
-            $relation_chain_to_join_model = EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4219
-                $model_name,
4220
-                $join_model_obj->get_this_model_name(),
4221
-                $model_relation_chain
4222
-            );
4223
-            $passed_in_query_info->merge(
4224
-                new EE_Model_Query_Info_Carrier(
4225
-                    array($relation_chain_to_join_model => $join_model_obj->get_this_model_name()),
4226
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4227
-                )
4228
-            );
4229
-        }
4230
-        // now just join to the other table pointed to by the relation object, and add its data types
4231
-        $passed_in_query_info->merge(
4232
-            new EE_Model_Query_Info_Carrier(
4233
-                array($model_relation_chain => $model_name),
4234
-                $relation_obj->get_join_statement($model_relation_chain)
4235
-            )
4236
-        );
4237
-    }
4238
-
4239
-
4240
-
4241
-    /**
4242
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4243
-     *
4244
-     * @param array $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4245
-     * @return string of SQL
4246
-     * @throws EE_Error
4247
-     */
4248
-    private function _construct_where_clause($where_params)
4249
-    {
4250
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4251
-        if ($SQL) {
4252
-            return " WHERE " . $SQL;
4253
-        }
4254
-        return '';
4255
-    }
4256
-
4257
-
4258
-
4259
-    /**
4260
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4261
-     * and should be passed HAVING parameters, not WHERE parameters
4262
-     *
4263
-     * @param array $having_params
4264
-     * @return string
4265
-     * @throws EE_Error
4266
-     */
4267
-    private function _construct_having_clause($having_params)
4268
-    {
4269
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4270
-        if ($SQL) {
4271
-            return " HAVING " . $SQL;
4272
-        }
4273
-        return '';
4274
-    }
4275
-
4276
-
4277
-    /**
4278
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4279
-     * Event_Meta.meta_value = 'foo'))"
4280
-     *
4281
-     * @param array  $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4282
-     * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4283
-     * @throws EE_Error
4284
-     * @return string of SQL
4285
-     */
4286
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4287
-    {
4288
-        $where_clauses = array();
4289
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4290
-            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4291
-            if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4292
-                switch ($query_param) {
4293
-                    case 'not':
4294
-                    case 'NOT':
4295
-                        $where_clauses[] = "! ("
4296
-                                           . $this->_construct_condition_clause_recursive(
4297
-                                               $op_and_value_or_sub_condition,
4298
-                                               $glue
4299
-                                           )
4300
-                                           . ")";
4301
-                        break;
4302
-                    case 'and':
4303
-                    case 'AND':
4304
-                        $where_clauses[] = " ("
4305
-                                           . $this->_construct_condition_clause_recursive(
4306
-                                               $op_and_value_or_sub_condition,
4307
-                                               ' AND '
4308
-                                           )
4309
-                                           . ")";
4310
-                        break;
4311
-                    case 'or':
4312
-                    case 'OR':
4313
-                        $where_clauses[] = " ("
4314
-                                           . $this->_construct_condition_clause_recursive(
4315
-                                               $op_and_value_or_sub_condition,
4316
-                                               ' OR '
4317
-                                           )
4318
-                                           . ")";
4319
-                        break;
4320
-                }
4321
-            } else {
4322
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4323
-                // if it's not a normal field, maybe it's a custom selection?
4324
-                if (! $field_obj) {
4325
-                    if ($this->_custom_selections instanceof CustomSelects) {
4326
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4327
-                    } else {
4328
-                        throw new EE_Error(sprintf(esc_html__(
4329
-                            "%s is neither a valid model field name, nor a custom selection",
4330
-                            "event_espresso"
4331
-                        ), $query_param));
4332
-                    }
4333
-                }
4334
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4335
-                $where_clauses[] = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4336
-            }
4337
-        }
4338
-        return $where_clauses ? implode($glue, $where_clauses) : '';
4339
-    }
4340
-
4341
-
4342
-
4343
-    /**
4344
-     * Takes the input parameter and extract the table name (alias) and column name
4345
-     *
4346
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4347
-     * @throws EE_Error
4348
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4349
-     */
4350
-    private function _deduce_column_name_from_query_param($query_param)
4351
-    {
4352
-        $field = $this->_deduce_field_from_query_param($query_param);
4353
-        if ($field) {
4354
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4355
-                $field->get_model_name(),
4356
-                $query_param
4357
-            );
4358
-            return $table_alias_prefix . $field->get_qualified_column();
4359
-        }
4360
-        if (
4361
-            $this->_custom_selections instanceof CustomSelects
4362
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4363
-        ) {
4364
-            // maybe it's custom selection item?
4365
-            // if so, just use it as the "column name"
4366
-            return $query_param;
4367
-        }
4368
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4369
-            ? implode(',', $this->_custom_selections->columnAliases())
4370
-            : '';
4371
-        throw new EE_Error(
4372
-            sprintf(
4373
-                esc_html__(
4374
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4375
-                    "event_espresso"
4376
-                ),
4377
-                $query_param,
4378
-                $custom_select_aliases
4379
-            )
4380
-        );
4381
-    }
4382
-
4383
-
4384
-
4385
-    /**
4386
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4387
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4388
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4389
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4390
-     *
4391
-     * @param string $condition_query_param_key
4392
-     * @return string
4393
-     */
4394
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4395
-    {
4396
-        $pos_of_star = strpos($condition_query_param_key, '*');
4397
-        if ($pos_of_star === false) {
4398
-            return $condition_query_param_key;
4399
-        }
4400
-        $condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
4401
-        return $condition_query_param_sans_star;
4402
-    }
4403
-
4404
-
4405
-
4406
-    /**
4407
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4408
-     *
4409
-     * @param                            mixed      array | string    $op_and_value
4410
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4411
-     * @throws EE_Error
4412
-     * @return string
4413
-     */
4414
-    private function _construct_op_and_value($op_and_value, $field_obj)
4415
-    {
4416
-        if (is_array($op_and_value)) {
4417
-            $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4418
-            if (! $operator) {
4419
-                $php_array_like_string = array();
4420
-                foreach ($op_and_value as $key => $value) {
4421
-                    $php_array_like_string[] = "$key=>$value";
4422
-                }
4423
-                throw new EE_Error(
4424
-                    sprintf(
4425
-                        esc_html__(
4426
-                            "You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4427
-                            "event_espresso"
4428
-                        ),
4429
-                        implode(",", $php_array_like_string)
4430
-                    )
4431
-                );
4432
-            }
4433
-            $value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4434
-        } else {
4435
-            $operator = '=';
4436
-            $value = $op_and_value;
4437
-        }
4438
-        // check to see if the value is actually another field
4439
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4440
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4441
-        }
4442
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4443
-            // in this case, the value should be an array, or at least a comma-separated list
4444
-            // it will need to handle a little differently
4445
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4446
-            // note: $cleaned_value has already been run through $wpdb->prepare()
4447
-            return $operator . SP . $cleaned_value;
4448
-        }
4449
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4450
-            // the value should be an array with count of two.
4451
-            if (count($value) !== 2) {
4452
-                throw new EE_Error(
4453
-                    sprintf(
4454
-                        esc_html__(
4455
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4456
-                            'event_espresso'
4457
-                        ),
4458
-                        "BETWEEN"
4459
-                    )
4460
-                );
4461
-            }
4462
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4463
-            return $operator . SP . $cleaned_value;
4464
-        }
4465
-        if (in_array($operator, $this->valid_null_style_operators())) {
4466
-            if ($value !== null) {
4467
-                throw new EE_Error(
4468
-                    sprintf(
4469
-                        esc_html__(
4470
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4471
-                            "event_espresso"
4472
-                        ),
4473
-                        $value,
4474
-                        $operator
4475
-                    )
4476
-                );
4477
-            }
4478
-            return $operator;
4479
-        }
4480
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4481
-            // if the operator is 'LIKE', we want to allow percent signs (%) and not
4482
-            // remove other junk. So just treat it as a string.
4483
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4484
-        }
4485
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4486
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4487
-        }
4488
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4489
-            throw new EE_Error(
4490
-                sprintf(
4491
-                    esc_html__(
4492
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4493
-                        'event_espresso'
4494
-                    ),
4495
-                    $operator,
4496
-                    $operator
4497
-                )
4498
-            );
4499
-        }
4500
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4501
-            throw new EE_Error(
4502
-                sprintf(
4503
-                    esc_html__(
4504
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4505
-                        'event_espresso'
4506
-                    ),
4507
-                    $operator,
4508
-                    $operator
4509
-                )
4510
-            );
4511
-        }
4512
-        throw new EE_Error(
4513
-            sprintf(
4514
-                esc_html__(
4515
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4516
-                    "event_espresso"
4517
-                ),
4518
-                http_build_query($op_and_value)
4519
-            )
4520
-        );
4521
-    }
4522
-
4523
-
4524
-
4525
-    /**
4526
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4527
-     *
4528
-     * @param array                      $values
4529
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4530
-     *                                              '%s'
4531
-     * @return string
4532
-     * @throws EE_Error
4533
-     */
4534
-    public function _construct_between_value($values, $field_obj)
4535
-    {
4536
-        $cleaned_values = array();
4537
-        foreach ($values as $value) {
4538
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4539
-        }
4540
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4541
-    }
4542
-
4543
-
4544
-    /**
4545
-     * Takes an array or a comma-separated list of $values and cleans them
4546
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4547
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4548
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4549
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4550
-     *
4551
-     * @param mixed                      $values    array or comma-separated string
4552
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4553
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4554
-     * @throws EE_Error
4555
-     */
4556
-    public function _construct_in_value($values, $field_obj)
4557
-    {
4558
-        $prepped = [];
4559
-        // check if the value is a CSV list
4560
-        if (is_string($values)) {
4561
-            // in which case, turn it into an array
4562
-            $values = explode(',', $values);
4563
-        }
4564
-        // make sure we only have one of each value in the list
4565
-        $values = array_unique($values);
4566
-        foreach ($values as $value) {
4567
-            $prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4568
-        }
4569
-        // we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4570
-        // but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4571
-        // which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4572
-        if (empty($prepped)) {
4573
-            $all_fields = $this->field_settings();
4574
-            $first_field    = reset($all_fields);
4575
-            $main_table = $this->_get_main_table();
4576
-            $prepped[]  = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4577
-        }
4578
-        return '(' . implode(',', $prepped) . ')';
4579
-    }
4580
-
4581
-
4582
-
4583
-    /**
4584
-     * @param mixed                      $value
4585
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4586
-     * @throws EE_Error
4587
-     * @return false|null|string
4588
-     */
4589
-    private function _wpdb_prepare_using_field($value, $field_obj)
4590
-    {
4591
-        /** @type WPDB $wpdb */
4592
-        global $wpdb;
4593
-        if ($field_obj instanceof EE_Model_Field_Base) {
4594
-            return $wpdb->prepare(
4595
-                $field_obj->get_wpdb_data_type(),
4596
-                $this->_prepare_value_for_use_in_db($value, $field_obj)
4597
-            );
4598
-        } //$field_obj should really just be a data type
4599
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4600
-            throw new EE_Error(
4601
-                sprintf(
4602
-                    esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4603
-                    $field_obj,
4604
-                    implode(",", $this->_valid_wpdb_data_types)
4605
-                )
4606
-            );
4607
-        }
4608
-        return $wpdb->prepare($field_obj, $value);
4609
-    }
4610
-
4611
-
4612
-
4613
-    /**
4614
-     * Takes the input parameter and finds the model field that it indicates.
4615
-     *
4616
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4617
-     * @throws EE_Error
4618
-     * @return EE_Model_Field_Base
4619
-     */
4620
-    protected function _deduce_field_from_query_param($query_param_name)
4621
-    {
4622
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
4623
-        // which will help us find the database table and column
4624
-        $query_param_parts = explode(".", $query_param_name);
4625
-        if (empty($query_param_parts)) {
4626
-            throw new EE_Error(sprintf(esc_html__(
4627
-                "_extract_column_name is empty when trying to extract column and table name from %s",
4628
-                'event_espresso'
4629
-            ), $query_param_name));
4630
-        }
4631
-        $number_of_parts = count($query_param_parts);
4632
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4633
-        if ($number_of_parts === 1) {
4634
-            $field_name = $last_query_param_part;
4635
-            $model_obj = $this;
4636
-        } else {// $number_of_parts >= 2
4637
-            // the last part is the column name, and there are only 2parts. therefore...
4638
-            $field_name = $last_query_param_part;
4639
-            $model_obj = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4640
-        }
4641
-        try {
4642
-            return $model_obj->field_settings_for($field_name);
4643
-        } catch (EE_Error $e) {
4644
-            return null;
4645
-        }
4646
-    }
4647
-
4648
-
4649
-
4650
-    /**
4651
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4652
-     * alias and column which corresponds to it
4653
-     *
4654
-     * @param string $field_name
4655
-     * @throws EE_Error
4656
-     * @return string
4657
-     */
4658
-    public function _get_qualified_column_for_field($field_name)
4659
-    {
4660
-        $all_fields = $this->field_settings();
4661
-        $field = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4662
-        if ($field) {
4663
-            return $field->get_qualified_column();
4664
-        }
4665
-        throw new EE_Error(
4666
-            sprintf(
4667
-                esc_html__(
4668
-                    "There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4669
-                    'event_espresso'
4670
-                ),
4671
-                $field_name,
4672
-                get_class($this)
4673
-            )
4674
-        );
4675
-    }
4676
-
4677
-
4678
-
4679
-    /**
4680
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4681
-     * Example usage:
4682
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4683
-     *      array(),
4684
-     *      ARRAY_A,
4685
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4686
-     *  );
4687
-     * is equivalent to
4688
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4689
-     * and
4690
-     *  EEM_Event::instance()->get_all_wpdb_results(
4691
-     *      array(
4692
-     *          array(
4693
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4694
-     *          ),
4695
-     *          ARRAY_A,
4696
-     *          implode(
4697
-     *              ', ',
4698
-     *              array_merge(
4699
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4700
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4701
-     *              )
4702
-     *          )
4703
-     *      )
4704
-     *  );
4705
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4706
-     *
4707
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4708
-     *                                            and the one whose fields you are selecting for example: when querying
4709
-     *                                            tickets model and selecting fields from the tickets model you would
4710
-     *                                            leave this parameter empty, because no models are needed to join
4711
-     *                                            between the queried model and the selected one. Likewise when
4712
-     *                                            querying the datetime model and selecting fields from the tickets
4713
-     *                                            model, it would also be left empty, because there is a direct
4714
-     *                                            relation from datetimes to tickets, so no model is needed to join
4715
-     *                                            them together. However, when querying from the event model and
4716
-     *                                            selecting fields from the ticket model, you should provide the string
4717
-     *                                            'Datetime', indicating that the event model must first join to the
4718
-     *                                            datetime model in order to find its relation to ticket model.
4719
-     *                                            Also, when querying from the venue model and selecting fields from
4720
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4721
-     *                                            indicating you need to join the venue model to the event model,
4722
-     *                                            to the datetime model, in order to find its relation to the ticket model.
4723
-     *                                            This string is used to deduce the prefix that gets added onto the
4724
-     *                                            models' tables qualified columns
4725
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4726
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4727
-     *                                            qualified column names
4728
-     * @return array|string
4729
-     */
4730
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4731
-    {
4732
-        $table_prefix = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4733
-        $qualified_columns = array();
4734
-        foreach ($this->field_settings() as $field_name => $field) {
4735
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4736
-        }
4737
-        return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4738
-    }
4739
-
4740
-
4741
-
4742
-    /**
4743
-     * constructs the select use on special limit joins
4744
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4745
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4746
-     * (as that is typically where the limits would be set).
4747
-     *
4748
-     * @param  string       $table_alias The table the select is being built for
4749
-     * @param  mixed|string $limit       The limit for this select
4750
-     * @return string                The final select join element for the query.
4751
-     */
4752
-    public function _construct_limit_join_select($table_alias, $limit)
4753
-    {
4754
-        $SQL = '';
4755
-        foreach ($this->_tables as $table_obj) {
4756
-            if ($table_obj instanceof EE_Primary_Table) {
4757
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4758
-                    ? $table_obj->get_select_join_limit($limit)
4759
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4760
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4761
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4762
-                    ? $table_obj->get_select_join_limit_join($limit)
4763
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4764
-            }
4765
-        }
4766
-        return $SQL;
4767
-    }
4768
-
4769
-
4770
-
4771
-    /**
4772
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4773
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4774
-     *
4775
-     * @return string SQL
4776
-     * @throws EE_Error
4777
-     */
4778
-    public function _construct_internal_join()
4779
-    {
4780
-        $SQL = $this->_get_main_table()->get_table_sql();
4781
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4782
-        return $SQL;
4783
-    }
4784
-
4785
-
4786
-
4787
-    /**
4788
-     * Constructs the SQL for joining all the tables on this model.
4789
-     * Normally $alias should be the primary table's alias, but in cases where
4790
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4791
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4792
-     * alias, this will construct SQL like:
4793
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4794
-     * With $alias being a secondary table's alias, this will construct SQL like:
4795
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4796
-     *
4797
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4798
-     * @return string
4799
-     */
4800
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4801
-    {
4802
-        $SQL = '';
4803
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4804
-        foreach ($this->_tables as $table_obj) {
4805
-            if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4806
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4807
-                    // so we're joining to this table, meaning the table is already in
4808
-                    // the FROM statement, BUT the primary table isn't. So we want
4809
-                    // to add the inverse join sql
4810
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4811
-                } else {
4812
-                    // just add a regular JOIN to this table from the primary table
4813
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4814
-                }
4815
-            }//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4816
-        }
4817
-        return $SQL;
4818
-    }
4819
-
4820
-
4821
-
4822
-    /**
4823
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4824
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4825
-     * their data type (eg, '%s', '%d', etc)
4826
-     *
4827
-     * @return array
4828
-     */
4829
-    public function _get_data_types()
4830
-    {
4831
-        $data_types = array();
4832
-        foreach ($this->field_settings() as $field_obj) {
4833
-            // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4834
-            /** @var $field_obj EE_Model_Field_Base */
4835
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4836
-        }
4837
-        return $data_types;
4838
-    }
4839
-
4840
-
4841
-
4842
-    /**
4843
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4844
-     *
4845
-     * @param string $model_name
4846
-     * @throws EE_Error
4847
-     * @return EEM_Base
4848
-     */
4849
-    public function get_related_model_obj($model_name)
4850
-    {
4851
-        $model_classname = "EEM_" . $model_name;
4852
-        if (! class_exists($model_classname)) {
4853
-            throw new EE_Error(sprintf(esc_html__(
4854
-                "You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4855
-                'event_espresso'
4856
-            ), $model_name, $model_classname));
4857
-        }
4858
-        return call_user_func($model_classname . "::instance");
4859
-    }
4860
-
4861
-
4862
-
4863
-    /**
4864
-     * Returns the array of EE_ModelRelations for this model.
4865
-     *
4866
-     * @return EE_Model_Relation_Base[]
4867
-     */
4868
-    public function relation_settings()
4869
-    {
4870
-        return $this->_model_relations;
4871
-    }
4872
-
4873
-
4874
-
4875
-    /**
4876
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4877
-     * because without THOSE models, this model probably doesn't have much purpose.
4878
-     * (Eg, without an event, datetimes have little purpose.)
4879
-     *
4880
-     * @return EE_Belongs_To_Relation[]
4881
-     */
4882
-    public function belongs_to_relations()
4883
-    {
4884
-        $belongs_to_relations = array();
4885
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
4886
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
4887
-                $belongs_to_relations[ $model_name ] = $relation_obj;
4888
-            }
4889
-        }
4890
-        return $belongs_to_relations;
4891
-    }
4892
-
4893
-
4894
-
4895
-    /**
4896
-     * Returns the specified EE_Model_Relation, or throws an exception
4897
-     *
4898
-     * @param string $relation_name name of relation, key in $this->_relatedModels
4899
-     * @throws EE_Error
4900
-     * @return EE_Model_Relation_Base
4901
-     */
4902
-    public function related_settings_for($relation_name)
4903
-    {
4904
-        $relatedModels = $this->relation_settings();
4905
-        if (! array_key_exists($relation_name, $relatedModels)) {
4906
-            throw new EE_Error(
4907
-                sprintf(
4908
-                    esc_html__(
4909
-                        'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4910
-                        'event_espresso'
4911
-                    ),
4912
-                    $relation_name,
4913
-                    $this->_get_class_name(),
4914
-                    implode(', ', array_keys($relatedModels))
4915
-                )
4916
-            );
4917
-        }
4918
-        return $relatedModels[ $relation_name ];
4919
-    }
4920
-
4921
-
4922
-
4923
-    /**
4924
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4925
-     * fields
4926
-     *
4927
-     * @param string $fieldName
4928
-     * @param boolean $include_db_only_fields
4929
-     * @throws EE_Error
4930
-     * @return EE_Model_Field_Base
4931
-     */
4932
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
4933
-    {
4934
-        $fieldSettings = $this->field_settings($include_db_only_fields);
4935
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4936
-            throw new EE_Error(sprintf(
4937
-                esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4938
-                $fieldName,
4939
-                get_class($this)
4940
-            ));
4941
-        }
4942
-        return $fieldSettings[ $fieldName ];
4943
-    }
4944
-
4945
-
4946
-
4947
-    /**
4948
-     * Checks if this field exists on this model
4949
-     *
4950
-     * @param string $fieldName a key in the model's _field_settings array
4951
-     * @return boolean
4952
-     */
4953
-    public function has_field($fieldName)
4954
-    {
4955
-        $fieldSettings = $this->field_settings(true);
4956
-        if (isset($fieldSettings[ $fieldName ])) {
4957
-            return true;
4958
-        }
4959
-        return false;
4960
-    }
4961
-
4962
-
4963
-
4964
-    /**
4965
-     * Returns whether or not this model has a relation to the specified model
4966
-     *
4967
-     * @param string $relation_name possibly one of the keys in the relation_settings array
4968
-     * @return boolean
4969
-     */
4970
-    public function has_relation($relation_name)
4971
-    {
4972
-        $relations = $this->relation_settings();
4973
-        if (isset($relations[ $relation_name ])) {
4974
-            return true;
4975
-        }
4976
-        return false;
4977
-    }
4978
-
4979
-
4980
-
4981
-    /**
4982
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4983
-     * Eg, on EE_Answer that would be ANS_ID field object
4984
-     *
4985
-     * @param $field_obj
4986
-     * @return boolean
4987
-     */
4988
-    public function is_primary_key_field($field_obj)
4989
-    {
4990
-        return $field_obj instanceof EE_Primary_Key_Field_Base ? true : false;
4991
-    }
4992
-
4993
-
4994
-
4995
-    /**
4996
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4997
-     * Eg, on EE_Answer that would be ANS_ID field object
4998
-     *
4999
-     * @return EE_Primary_Key_Field_Base
5000
-     * @throws EE_Error
5001
-     */
5002
-    public function get_primary_key_field()
5003
-    {
5004
-        if ($this->_primary_key_field === null) {
5005
-            foreach ($this->field_settings(true) as $field_obj) {
5006
-                if ($this->is_primary_key_field($field_obj)) {
5007
-                    $this->_primary_key_field = $field_obj;
5008
-                    break;
5009
-                }
5010
-            }
5011
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5012
-                throw new EE_Error(sprintf(
5013
-                    esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5014
-                    get_class($this)
5015
-                ));
5016
-            }
5017
-        }
5018
-        return $this->_primary_key_field;
5019
-    }
5020
-
5021
-
5022
-
5023
-    /**
5024
-     * Returns whether or not not there is a primary key on this model.
5025
-     * Internally does some caching.
5026
-     *
5027
-     * @return boolean
5028
-     */
5029
-    public function has_primary_key_field()
5030
-    {
5031
-        if ($this->_has_primary_key_field === null) {
5032
-            try {
5033
-                $this->get_primary_key_field();
5034
-                $this->_has_primary_key_field = true;
5035
-            } catch (EE_Error $e) {
5036
-                $this->_has_primary_key_field = false;
5037
-            }
5038
-        }
5039
-        return $this->_has_primary_key_field;
5040
-    }
5041
-
5042
-
5043
-
5044
-    /**
5045
-     * Finds the first field of type $field_class_name.
5046
-     *
5047
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5048
-     *                                 EE_Foreign_Key_Field, etc
5049
-     * @return EE_Model_Field_Base or null if none is found
5050
-     */
5051
-    public function get_a_field_of_type($field_class_name)
5052
-    {
5053
-        foreach ($this->field_settings() as $field) {
5054
-            if ($field instanceof $field_class_name) {
5055
-                return $field;
5056
-            }
5057
-        }
5058
-        return null;
5059
-    }
5060
-
5061
-
5062
-
5063
-    /**
5064
-     * Gets a foreign key field pointing to model.
5065
-     *
5066
-     * @param string $model_name eg Event, Registration, not EEM_Event
5067
-     * @return EE_Foreign_Key_Field_Base
5068
-     * @throws EE_Error
5069
-     */
5070
-    public function get_foreign_key_to($model_name)
5071
-    {
5072
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5073
-            foreach ($this->field_settings() as $field) {
5074
-                if (
5075
-                    $field instanceof EE_Foreign_Key_Field_Base
5076
-                    && in_array($model_name, $field->get_model_names_pointed_to())
5077
-                ) {
5078
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5079
-                    break;
5080
-                }
5081
-            }
5082
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5083
-                throw new EE_Error(sprintf(esc_html__(
5084
-                    "There is no foreign key field pointing to model %s on model %s",
5085
-                    'event_espresso'
5086
-                ), $model_name, get_class($this)));
5087
-            }
5088
-        }
5089
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5090
-    }
5091
-
5092
-
5093
-
5094
-    /**
5095
-     * Gets the table name (including $wpdb->prefix) for the table alias
5096
-     *
5097
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5098
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5099
-     *                            Either one works
5100
-     * @return string
5101
-     */
5102
-    public function get_table_for_alias($table_alias)
5103
-    {
5104
-        $table_alias_sans_model_relation_chain_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5105
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5106
-    }
5107
-
5108
-
5109
-
5110
-    /**
5111
-     * Returns a flat array of all field son this model, instead of organizing them
5112
-     * by table_alias as they are in the constructor.
5113
-     *
5114
-     * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5115
-     * @return EE_Model_Field_Base[] where the keys are the field's name
5116
-     */
5117
-    public function field_settings($include_db_only_fields = false)
5118
-    {
5119
-        if ($include_db_only_fields) {
5120
-            if ($this->_cached_fields === null) {
5121
-                $this->_cached_fields = array();
5122
-                foreach ($this->_fields as $fields_corresponding_to_table) {
5123
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5124
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5125
-                    }
5126
-                }
5127
-            }
5128
-            return $this->_cached_fields;
5129
-        }
5130
-        if ($this->_cached_fields_non_db_only === null) {
5131
-            $this->_cached_fields_non_db_only = array();
5132
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5133
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5134
-                    /** @var $field_obj EE_Model_Field_Base */
5135
-                    if (! $field_obj->is_db_only_field()) {
5136
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5137
-                    }
5138
-                }
5139
-            }
5140
-        }
5141
-        return $this->_cached_fields_non_db_only;
5142
-    }
5143
-
5144
-
5145
-
5146
-    /**
5147
-     *        cycle though array of attendees and create objects out of each item
5148
-     *
5149
-     * @access        private
5150
-     * @param        array $rows of results of $wpdb->get_results($query,ARRAY_A)
5151
-     * @return \EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5152
-     *                           numerically indexed)
5153
-     * @throws EE_Error
5154
-     */
5155
-    protected function _create_objects($rows = array())
5156
-    {
5157
-        $array_of_objects = array();
5158
-        if (empty($rows)) {
5159
-            return array();
5160
-        }
5161
-        $count_if_model_has_no_primary_key = 0;
5162
-        $has_primary_key = $this->has_primary_key_field();
5163
-        $primary_key_field = $has_primary_key ? $this->get_primary_key_field() : null;
5164
-        foreach ((array) $rows as $row) {
5165
-            if (empty($row)) {
5166
-                // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5167
-                return array();
5168
-            }
5169
-            // check if we've already set this object in the results array,
5170
-            // in which case there's no need to process it further (again)
5171
-            if ($has_primary_key) {
5172
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5173
-                    $row,
5174
-                    $primary_key_field->get_qualified_column(),
5175
-                    $primary_key_field->get_table_column()
5176
-                );
5177
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5178
-                    continue;
5179
-                }
5180
-            }
5181
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5182
-            if (! $classInstance) {
5183
-                throw new EE_Error(
5184
-                    sprintf(
5185
-                        esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5186
-                        $this->get_this_model_name(),
5187
-                        http_build_query($row)
5188
-                    )
5189
-                );
5190
-            }
5191
-            // set the timezone on the instantiated objects
5192
-            $classInstance->set_timezone($this->_timezone);
5193
-            // make sure if there is any timezone setting present that we set the timezone for the object
5194
-            $key = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5195
-            $array_of_objects[ $key ] = $classInstance;
5196
-            // also, for all the relations of type BelongsTo, see if we can cache
5197
-            // those related models
5198
-            // (we could do this for other relations too, but if there are conditions
5199
-            // that filtered out some fo the results, then we'd be caching an incomplete set
5200
-            // so it requires a little more thought than just caching them immediately...)
5201
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5202
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5203
-                    // check if this model's INFO is present. If so, cache it on the model
5204
-                    $other_model = $relation_obj->get_other_model();
5205
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5206
-                    // if we managed to make a model object from the results, cache it on the main model object
5207
-                    if ($other_model_obj_maybe) {
5208
-                        // set timezone on these other model objects if they are present
5209
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5210
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5211
-                    }
5212
-                }
5213
-            }
5214
-            // also, if this was a custom select query, let's see if there are any results for the custom select fields
5215
-            // and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5216
-            // the field in the CustomSelects object
5217
-            if ($this->_custom_selections instanceof CustomSelects) {
5218
-                $classInstance->setCustomSelectsValues(
5219
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5220
-                );
5221
-            }
5222
-        }
5223
-        return $array_of_objects;
5224
-    }
5225
-
5226
-
5227
-    /**
5228
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5229
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5230
-     *
5231
-     * @param array $db_results_row
5232
-     * @return array
5233
-     */
5234
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5235
-    {
5236
-        $results = array();
5237
-        if ($this->_custom_selections instanceof CustomSelects) {
5238
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5239
-                if (isset($db_results_row[ $alias ])) {
5240
-                    $results[ $alias ] = $this->convertValueToDataType(
5241
-                        $db_results_row[ $alias ],
5242
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5243
-                    );
5244
-                }
5245
-            }
5246
-        }
5247
-        return $results;
5248
-    }
5249
-
5250
-
5251
-    /**
5252
-     * This will set the value for the given alias
5253
-     * @param string $value
5254
-     * @param string $datatype (one of %d, %s, %f)
5255
-     * @return int|string|float (int for %d, string for %s, float for %f)
5256
-     */
5257
-    protected function convertValueToDataType($value, $datatype)
5258
-    {
5259
-        switch ($datatype) {
5260
-            case '%f':
5261
-                return (float) $value;
5262
-            case '%d':
5263
-                return (int) $value;
5264
-            default:
5265
-                return (string) $value;
5266
-        }
5267
-    }
5268
-
5269
-
5270
-    /**
5271
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5272
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5273
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5274
-     * object (as set in the model_field!).
5275
-     *
5276
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5277
-     */
5278
-    public function create_default_object()
5279
-    {
5280
-        $this_model_fields_and_values = array();
5281
-        // setup the row using default values;
5282
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5283
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5284
-        }
5285
-        $className = $this->_get_class_name();
5286
-        $classInstance = EE_Registry::instance()
5287
-                                    ->load_class($className, array($this_model_fields_and_values), false, false);
5288
-        return $classInstance;
5289
-    }
5290
-
5291
-
5292
-
5293
-    /**
5294
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5295
-     *                             or an stdClass where each property is the name of a column,
5296
-     * @return EE_Base_Class
5297
-     * @throws EE_Error
5298
-     */
5299
-    public function instantiate_class_from_array_or_object($cols_n_values)
5300
-    {
5301
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5302
-            $cols_n_values = get_object_vars($cols_n_values);
5303
-        }
5304
-        $primary_key = null;
5305
-        // make sure the array only has keys that are fields/columns on this model
5306
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5307
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5308
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5309
-        }
5310
-        $className = $this->_get_class_name();
5311
-        // check we actually found results that we can use to build our model object
5312
-        // if not, return null
5313
-        if ($this->has_primary_key_field()) {
5314
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5315
-                return null;
5316
-            }
5317
-        } elseif ($this->unique_indexes()) {
5318
-            $first_column = reset($this_model_fields_n_values);
5319
-            if (empty($first_column)) {
5320
-                return null;
5321
-            }
5322
-        }
5323
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5324
-        if ($primary_key) {
5325
-            $classInstance = $this->get_from_entity_map($primary_key);
5326
-            if (! $classInstance) {
5327
-                $classInstance = EE_Registry::instance()
5328
-                                            ->load_class(
5329
-                                                $className,
5330
-                                                array($this_model_fields_n_values, $this->_timezone),
5331
-                                                true,
5332
-                                                false
5333
-                                            );
5334
-                // add this new object to the entity map
5335
-                $classInstance = $this->add_to_entity_map($classInstance);
5336
-            }
5337
-        } else {
5338
-            $classInstance = EE_Registry::instance()
5339
-                                        ->load_class(
5340
-                                            $className,
5341
-                                            array($this_model_fields_n_values, $this->_timezone),
5342
-                                            true,
5343
-                                            false
5344
-                                        );
5345
-        }
5346
-        return $classInstance;
5347
-    }
5348
-
5349
-
5350
-
5351
-    /**
5352
-     * Gets the model object from the  entity map if it exists
5353
-     *
5354
-     * @param int|string $id the ID of the model object
5355
-     * @return EE_Base_Class
5356
-     */
5357
-    public function get_from_entity_map($id)
5358
-    {
5359
-        return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5360
-            ? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5361
-    }
5362
-
5363
-
5364
-
5365
-    /**
5366
-     * add_to_entity_map
5367
-     * Adds the object to the model's entity mappings
5368
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5369
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5370
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5371
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5372
-     *        then this method should be called immediately after the update query
5373
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5374
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5375
-     *
5376
-     * @param    EE_Base_Class $object
5377
-     * @throws EE_Error
5378
-     * @return \EE_Base_Class
5379
-     */
5380
-    public function add_to_entity_map(EE_Base_Class $object)
5381
-    {
5382
-        $className = $this->_get_class_name();
5383
-        if (! $object instanceof $className) {
5384
-            throw new EE_Error(sprintf(
5385
-                esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5386
-                is_object($object) ? get_class($object) : $object,
5387
-                $className
5388
-            ));
5389
-        }
5390
-        /** @var $object EE_Base_Class */
5391
-        if (! $object->ID()) {
5392
-            throw new EE_Error(sprintf(esc_html__(
5393
-                "You tried storing a model object with NO ID in the %s entity mapper.",
5394
-                "event_espresso"
5395
-            ), get_class($this)));
5396
-        }
5397
-        // double check it's not already there
5398
-        $classInstance = $this->get_from_entity_map($object->ID());
5399
-        if ($classInstance) {
5400
-            return $classInstance;
5401
-        }
5402
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5403
-        return $object;
5404
-    }
5405
-
5406
-
5407
-
5408
-    /**
5409
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5410
-     * if no identifier is provided, then the entire entity map is emptied
5411
-     *
5412
-     * @param int|string $id the ID of the model object
5413
-     * @return boolean
5414
-     */
5415
-    public function clear_entity_map($id = null)
5416
-    {
5417
-        if (empty($id)) {
5418
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = array();
5419
-            return true;
5420
-        }
5421
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5422
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5423
-            return true;
5424
-        }
5425
-        return false;
5426
-    }
5427
-
5428
-
5429
-
5430
-    /**
5431
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5432
-     * Given an array where keys are column (or column alias) names and values,
5433
-     * returns an array of their corresponding field names and database values
5434
-     *
5435
-     * @param array $cols_n_values
5436
-     * @return array
5437
-     */
5438
-    public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5439
-    {
5440
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5441
-    }
5442
-
5443
-
5444
-
5445
-    /**
5446
-     * _deduce_fields_n_values_from_cols_n_values
5447
-     * Given an array where keys are column (or column alias) names and values,
5448
-     * returns an array of their corresponding field names and database values
5449
-     *
5450
-     * @param string $cols_n_values
5451
-     * @return array
5452
-     */
5453
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5454
-    {
5455
-        $this_model_fields_n_values = array();
5456
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5457
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5458
-                $cols_n_values,
5459
-                $table_obj->get_fully_qualified_pk_column(),
5460
-                $table_obj->get_pk_column()
5461
-            );
5462
-            // there is a primary key on this table and its not set. Use defaults for all its columns
5463
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5464
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5465
-                    if (! $field_obj->is_db_only_field()) {
5466
-                        // prepare field as if its coming from db
5467
-                        $prepared_value = $field_obj->prepare_for_set($field_obj->get_default_value());
5468
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5469
-                    }
5470
-                }
5471
-            } else {
5472
-                // the table's rows existed. Use their values
5473
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5474
-                    if (! $field_obj->is_db_only_field()) {
5475
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5476
-                            $cols_n_values,
5477
-                            $field_obj->get_qualified_column(),
5478
-                            $field_obj->get_table_column()
5479
-                        );
5480
-                    }
5481
-                }
5482
-            }
5483
-        }
5484
-        return $this_model_fields_n_values;
5485
-    }
5486
-
5487
-
5488
-    /**
5489
-     * @param $cols_n_values
5490
-     * @param $qualified_column
5491
-     * @param $regular_column
5492
-     * @return null
5493
-     * @throws EE_Error
5494
-     * @throws ReflectionException
5495
-     */
5496
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5497
-    {
5498
-        $value = null;
5499
-        // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5500
-        // does the field on the model relate to this column retrieved from the db?
5501
-        // or is it a db-only field? (not relating to the model)
5502
-        if (isset($cols_n_values[ $qualified_column ])) {
5503
-            $value = $cols_n_values[ $qualified_column ];
5504
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5505
-            $value = $cols_n_values[ $regular_column ];
5506
-        } elseif (! empty($this->foreign_key_aliases)) {
5507
-            // no PK?  ok check if there is a foreign key alias set for this table
5508
-            // then check if that alias exists in the incoming data
5509
-            // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5510
-            foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5511
-                if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5512
-                    $value = $cols_n_values[ $FK_alias ];
5513
-                    [$pk_class] = explode('.', $PK_column);
5514
-                    $pk_model_name = "EEM_{$pk_class}";
5515
-                    /** @var EEM_Base $pk_model */
5516
-                    $pk_model = EE_Registry::instance()->load_model($pk_model_name);
5517
-                    if ($pk_model instanceof EEM_Base) {
5518
-                        // make sure object is pulled from db and added to entity map
5519
-                        $pk_model->get_one_by_ID($value);
5520
-                    }
5521
-                    break;
5522
-                }
5523
-            }
5524
-        }
5525
-        return $value;
5526
-    }
5527
-
5528
-
5529
-    /**
5530
-     * refresh_entity_map_from_db
5531
-     * Makes sure the model object in the entity map at $id assumes the values
5532
-     * of the database (opposite of EE_base_Class::save())
5533
-     *
5534
-     * @param int|string $id
5535
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5536
-     * @throws EE_Error
5537
-     * @throws ReflectionException
5538
-     */
5539
-    public function refresh_entity_map_from_db($id)
5540
-    {
5541
-        $obj_in_map = $this->get_from_entity_map($id);
5542
-        if ($obj_in_map) {
5543
-            $wpdb_results = $this->_get_all_wpdb_results(
5544
-                array(array($this->get_primary_key_field()->get_name() => $id), 'limit' => 1)
5545
-            );
5546
-            if ($wpdb_results && is_array($wpdb_results)) {
5547
-                $one_row = reset($wpdb_results);
5548
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5549
-                    $obj_in_map->set_from_db($field_name, $db_value);
5550
-                }
5551
-                // clear the cache of related model objects
5552
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5553
-                    $obj_in_map->clear_cache($relation_name, null, true);
5554
-                }
5555
-            }
5556
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5557
-            return $obj_in_map;
5558
-        }
5559
-        return $this->get_one_by_ID($id);
5560
-    }
5561
-
5562
-
5563
-
5564
-    /**
5565
-     * refresh_entity_map_with
5566
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5567
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5568
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5569
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5570
-     *
5571
-     * @param int|string    $id
5572
-     * @param EE_Base_Class $replacing_model_obj
5573
-     * @return \EE_Base_Class
5574
-     * @throws EE_Error
5575
-     */
5576
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5577
-    {
5578
-        $obj_in_map = $this->get_from_entity_map($id);
5579
-        if ($obj_in_map) {
5580
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5581
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5582
-                    $obj_in_map->set($field_name, $value);
5583
-                }
5584
-                // make the model object in the entity map's cache match the $replacing_model_obj
5585
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5586
-                    $obj_in_map->clear_cache($relation_name, null, true);
5587
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5588
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5589
-                    }
5590
-                }
5591
-            }
5592
-            return $obj_in_map;
5593
-        }
5594
-        $this->add_to_entity_map($replacing_model_obj);
5595
-        return $replacing_model_obj;
5596
-    }
5597
-
5598
-
5599
-
5600
-    /**
5601
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5602
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5603
-     * require_once($this->_getClassName().".class.php");
5604
-     *
5605
-     * @return string
5606
-     */
5607
-    private function _get_class_name()
5608
-    {
5609
-        return "EE_" . $this->get_this_model_name();
5610
-    }
5611
-
5612
-
5613
-
5614
-    /**
5615
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5616
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5617
-     * it would be 'Events'.
5618
-     *
5619
-     * @param int|float|null $quantity
5620
-     * @return string
5621
-     */
5622
-    public function item_name($quantity = 1): string
5623
-    {
5624
-        $quantity = floor($quantity);
5625
-        return apply_filters(
5626
-            'FHEE__EEM_Base__item_name__plural_or_singular',
5627
-            $quantity > 1 ? $this->plural_item : $this->singular_item,
5628
-            $quantity,
5629
-            $this->plural_item,
5630
-            $this->singular_item
5631
-        );
5632
-    }
5633
-
5634
-
5635
-
5636
-    /**
5637
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5638
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5639
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5640
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5641
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5642
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5643
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5644
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5645
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5646
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5647
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5648
-     *        return $previousReturnValue.$returnString;
5649
-     * }
5650
-     * require('EEM_Answer.model.php');
5651
-     * echo EEM_Answer::instance()->my_callback('monkeys',100);
5652
-     * // will output "you called my_callback! and passed args:monkeys,100"
5653
-     *
5654
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5655
-     * @param array  $args       array of original arguments passed to the function
5656
-     * @throws EE_Error
5657
-     * @return mixed whatever the plugin which calls add_filter decides
5658
-     */
5659
-    public function __call($methodName, $args)
5660
-    {
5661
-        $className = get_class($this);
5662
-        $tagName = "FHEE__{$className}__{$methodName}";
5663
-        if (! has_filter($tagName)) {
5664
-            throw new EE_Error(
5665
-                sprintf(
5666
-                    esc_html__(
5667
-                        '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 );',
5668
-                        'event_espresso'
5669
-                    ),
5670
-                    $methodName,
5671
-                    $className,
5672
-                    $tagName,
5673
-                    '<br />'
5674
-                )
5675
-            );
5676
-        }
5677
-        return apply_filters($tagName, null, $this, $args);
5678
-    }
5679
-
5680
-
5681
-
5682
-    /**
5683
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5684
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5685
-     *
5686
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5687
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5688
-     *                                                       the object's class name
5689
-     *                                                       or object's ID
5690
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5691
-     *                                                       exists in the database. If it does not, we add it
5692
-     * @throws EE_Error
5693
-     * @return EE_Base_Class
5694
-     */
5695
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5696
-    {
5697
-        $className = $this->_get_class_name();
5698
-        if ($base_class_obj_or_id instanceof $className) {
5699
-            $model_object = $base_class_obj_or_id;
5700
-        } else {
5701
-            $primary_key_field = $this->get_primary_key_field();
5702
-            if (
5703
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5704
-                && (
5705
-                    is_int($base_class_obj_or_id)
5706
-                    || is_string($base_class_obj_or_id)
5707
-                )
5708
-            ) {
5709
-                // assume it's an ID.
5710
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5711
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5712
-            } elseif (
5713
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5714
-                && is_string($base_class_obj_or_id)
5715
-            ) {
5716
-                // assume its a string representation of the object
5717
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5718
-            } else {
5719
-                throw new EE_Error(
5720
-                    sprintf(
5721
-                        esc_html__(
5722
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5723
-                            'event_espresso'
5724
-                        ),
5725
-                        $base_class_obj_or_id,
5726
-                        $this->_get_class_name(),
5727
-                        print_r($base_class_obj_or_id, true)
5728
-                    )
5729
-                );
5730
-            }
5731
-        }
5732
-        if ($ensure_is_in_db && $model_object->ID() !== null) {
5733
-            $model_object->save();
5734
-        }
5735
-        return $model_object;
5736
-    }
5737
-
5738
-
5739
-
5740
-    /**
5741
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5742
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5743
-     * returns it ID.
5744
-     *
5745
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5746
-     * @return int|string depending on the type of this model object's ID
5747
-     * @throws EE_Error
5748
-     */
5749
-    public function ensure_is_ID($base_class_obj_or_id)
5750
-    {
5751
-        $className = $this->_get_class_name();
5752
-        if ($base_class_obj_or_id instanceof $className) {
5753
-            /** @var $base_class_obj_or_id EE_Base_Class */
5754
-            $id = $base_class_obj_or_id->ID();
5755
-        } elseif (is_int($base_class_obj_or_id)) {
5756
-            // assume it's an ID
5757
-            $id = $base_class_obj_or_id;
5758
-        } elseif (is_string($base_class_obj_or_id)) {
5759
-            // assume its a string representation of the object
5760
-            $id = $base_class_obj_or_id;
5761
-        } else {
5762
-            throw new EE_Error(sprintf(
5763
-                esc_html__(
5764
-                    "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5765
-                    'event_espresso'
5766
-                ),
5767
-                $base_class_obj_or_id,
5768
-                $this->_get_class_name(),
5769
-                print_r($base_class_obj_or_id, true)
5770
-            ));
5771
-        }
5772
-        return $id;
5773
-    }
5774
-
5775
-
5776
-
5777
-    /**
5778
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5779
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5780
-     * been sanitized and converted into the appropriate domain.
5781
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5782
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5783
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5784
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5785
-     * $EVT = EEM_Event::instance(); $old_setting =
5786
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5787
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5788
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5789
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5790
-     *
5791
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5792
-     * @return void
5793
-     */
5794
-    public function assume_values_already_prepared_by_model_object(
5795
-        $values_already_prepared = self::not_prepared_by_model_object
5796
-    ) {
5797
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5798
-    }
5799
-
5800
-
5801
-
5802
-    /**
5803
-     * Read comments for assume_values_already_prepared_by_model_object()
5804
-     *
5805
-     * @return int
5806
-     */
5807
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5808
-    {
5809
-        return $this->_values_already_prepared_by_model_object;
5810
-    }
5811
-
5812
-
5813
-
5814
-    /**
5815
-     * Gets all the indexes on this model
5816
-     *
5817
-     * @return EE_Index[]
5818
-     */
5819
-    public function indexes()
5820
-    {
5821
-        return $this->_indexes;
5822
-    }
5823
-
5824
-
5825
-
5826
-    /**
5827
-     * Gets all the Unique Indexes on this model
5828
-     *
5829
-     * @return EE_Unique_Index[]
5830
-     */
5831
-    public function unique_indexes()
5832
-    {
5833
-        $unique_indexes = array();
5834
-        foreach ($this->_indexes as $name => $index) {
5835
-            if ($index instanceof EE_Unique_Index) {
5836
-                $unique_indexes [ $name ] = $index;
5837
-            }
5838
-        }
5839
-        return $unique_indexes;
5840
-    }
5841
-
5842
-
5843
-
5844
-    /**
5845
-     * Gets all the fields which, when combined, make the primary key.
5846
-     * This is usually just an array with 1 element (the primary key), but in cases
5847
-     * where there is no primary key, it's a combination of fields as defined
5848
-     * on a primary index
5849
-     *
5850
-     * @return EE_Model_Field_Base[] indexed by the field's name
5851
-     * @throws EE_Error
5852
-     */
5853
-    public function get_combined_primary_key_fields()
5854
-    {
5855
-        foreach ($this->indexes() as $index) {
5856
-            if ($index instanceof EE_Primary_Key_Index) {
5857
-                return $index->fields();
5858
-            }
5859
-        }
5860
-        return array($this->primary_key_name() => $this->get_primary_key_field());
5861
-    }
5862
-
5863
-
5864
-
5865
-    /**
5866
-     * Used to build a primary key string (when the model has no primary key),
5867
-     * which can be used a unique string to identify this model object.
5868
-     *
5869
-     * @param array $fields_n_values keys are field names, values are their values.
5870
-     *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5871
-     *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5872
-     *                               before passing it to this function (that will convert it from columns-n-values
5873
-     *                               to field-names-n-values).
5874
-     * @return string
5875
-     * @throws EE_Error
5876
-     */
5877
-    public function get_index_primary_key_string($fields_n_values)
5878
-    {
5879
-        $cols_n_values_for_primary_key_index = array_intersect_key(
5880
-            $fields_n_values,
5881
-            $this->get_combined_primary_key_fields()
5882
-        );
5883
-        return http_build_query($cols_n_values_for_primary_key_index);
5884
-    }
5885
-
5886
-
5887
-
5888
-    /**
5889
-     * Gets the field values from the primary key string
5890
-     *
5891
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5892
-     * @param string $index_primary_key_string
5893
-     * @return null|array
5894
-     * @throws EE_Error
5895
-     */
5896
-    public function parse_index_primary_key_string($index_primary_key_string)
5897
-    {
5898
-        $key_fields = $this->get_combined_primary_key_fields();
5899
-        // check all of them are in the $id
5900
-        $key_vals_in_combined_pk = array();
5901
-        parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5902
-        foreach ($key_fields as $key_field_name => $field_obj) {
5903
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
5904
-                return null;
5905
-            }
5906
-        }
5907
-        return $key_vals_in_combined_pk;
5908
-    }
5909
-
5910
-
5911
-
5912
-    /**
5913
-     * verifies that an array of key-value pairs for model fields has a key
5914
-     * for each field comprising the primary key index
5915
-     *
5916
-     * @param array $key_vals
5917
-     * @return boolean
5918
-     * @throws EE_Error
5919
-     */
5920
-    public function has_all_combined_primary_key_fields($key_vals)
5921
-    {
5922
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5923
-        foreach ($keys_it_should_have as $key) {
5924
-            if (! isset($key_vals[ $key ])) {
5925
-                return false;
5926
-            }
5927
-        }
5928
-        return true;
5929
-    }
5930
-
5931
-
5932
-
5933
-    /**
5934
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5935
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
5936
-     *
5937
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5938
-     * @param array               $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5939
-     * @throws EE_Error
5940
-     * @return \EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5941
-     *                                                              indexed)
5942
-     */
5943
-    public function get_all_copies($model_object_or_attributes_array, $query_params = array())
5944
-    {
5945
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5946
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
5947
-        } elseif (is_array($model_object_or_attributes_array)) {
5948
-            $attributes_array = $model_object_or_attributes_array;
5949
-        } else {
5950
-            throw new EE_Error(sprintf(esc_html__(
5951
-                "get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5952
-                "event_espresso"
5953
-            ), $model_object_or_attributes_array));
5954
-        }
5955
-        // even copies obviously won't have the same ID, so remove the primary key
5956
-        // from the WHERE conditions for finding copies (if there is a primary key, of course)
5957
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5958
-            unset($attributes_array[ $this->primary_key_name() ]);
5959
-        }
5960
-        if (isset($query_params[0])) {
5961
-            $query_params[0] = array_merge($attributes_array, $query_params);
5962
-        } else {
5963
-            $query_params[0] = $attributes_array;
5964
-        }
5965
-        return $this->get_all($query_params);
5966
-    }
5967
-
5968
-
5969
-
5970
-    /**
5971
-     * Gets the first copy we find. See get_all_copies for more details
5972
-     *
5973
-     * @param       mixed EE_Base_Class | array        $model_object_or_attributes_array
5974
-     * @param array $query_params
5975
-     * @return EE_Base_Class
5976
-     * @throws EE_Error
5977
-     */
5978
-    public function get_one_copy($model_object_or_attributes_array, $query_params = array())
5979
-    {
5980
-        if (! is_array($query_params)) {
5981
-            EE_Error::doing_it_wrong(
5982
-                'EEM_Base::get_one_copy',
5983
-                sprintf(
5984
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5985
-                    gettype($query_params)
5986
-                ),
5987
-                '4.6.0'
5988
-            );
5989
-            $query_params = array();
5990
-        }
5991
-        $query_params['limit'] = 1;
5992
-        $copies = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5993
-        if (is_array($copies)) {
5994
-            return array_shift($copies);
5995
-        }
5996
-        return null;
5997
-    }
5998
-
5999
-
6000
-
6001
-    /**
6002
-     * Updates the item with the specified id. Ignores default query parameters because
6003
-     * we have specified the ID, and its assumed we KNOW what we're doing
6004
-     *
6005
-     * @param array      $fields_n_values keys are field names, values are their new values
6006
-     * @param int|string $id              the value of the primary key to update
6007
-     * @return int number of rows updated
6008
-     * @throws EE_Error
6009
-     */
6010
-    public function update_by_ID($fields_n_values, $id)
6011
-    {
6012
-        $query_params = array(
6013
-            0                          => array($this->get_primary_key_field()->get_name() => $id),
6014
-            'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6015
-        );
6016
-        return $this->update($fields_n_values, $query_params);
6017
-    }
6018
-
6019
-
6020
-
6021
-    /**
6022
-     * Changes an operator which was supplied to the models into one usable in SQL
6023
-     *
6024
-     * @param string $operator_supplied
6025
-     * @return string an operator which can be used in SQL
6026
-     * @throws EE_Error
6027
-     */
6028
-    private function _prepare_operator_for_sql($operator_supplied)
6029
-    {
6030
-        $sql_operator = isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6031
-            : null;
6032
-        if ($sql_operator) {
6033
-            return $sql_operator;
6034
-        }
6035
-        throw new EE_Error(
6036
-            sprintf(
6037
-                esc_html__(
6038
-                    "The operator '%s' is not in the list of valid operators: %s",
6039
-                    "event_espresso"
6040
-                ),
6041
-                $operator_supplied,
6042
-                implode(",", array_keys($this->_valid_operators))
6043
-            )
6044
-        );
6045
-    }
6046
-
6047
-
6048
-
6049
-    /**
6050
-     * Gets the valid operators
6051
-     * @return array keys are accepted strings, values are the SQL they are converted to
6052
-     */
6053
-    public function valid_operators()
6054
-    {
6055
-        return $this->_valid_operators;
6056
-    }
6057
-
6058
-
6059
-
6060
-    /**
6061
-     * Gets the between-style operators (take 2 arguments).
6062
-     * @return array keys are accepted strings, values are the SQL they are converted to
6063
-     */
6064
-    public function valid_between_style_operators()
6065
-    {
6066
-        return array_intersect(
6067
-            $this->valid_operators(),
6068
-            $this->_between_style_operators
6069
-        );
6070
-    }
6071
-
6072
-    /**
6073
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6074
-     * @return array keys are accepted strings, values are the SQL they are converted to
6075
-     */
6076
-    public function valid_like_style_operators()
6077
-    {
6078
-        return array_intersect(
6079
-            $this->valid_operators(),
6080
-            $this->_like_style_operators
6081
-        );
6082
-    }
6083
-
6084
-    /**
6085
-     * Gets the "in"-style operators
6086
-     * @return array keys are accepted strings, values are the SQL they are converted to
6087
-     */
6088
-    public function valid_in_style_operators()
6089
-    {
6090
-        return array_intersect(
6091
-            $this->valid_operators(),
6092
-            $this->_in_style_operators
6093
-        );
6094
-    }
6095
-
6096
-    /**
6097
-     * Gets the "null"-style operators (accept no arguments)
6098
-     * @return array keys are accepted strings, values are the SQL they are converted to
6099
-     */
6100
-    public function valid_null_style_operators()
6101
-    {
6102
-        return array_intersect(
6103
-            $this->valid_operators(),
6104
-            $this->_null_style_operators
6105
-        );
6106
-    }
6107
-
6108
-    /**
6109
-     * Gets an array where keys are the primary keys and values are their 'names'
6110
-     * (as determined by the model object's name() function, which is often overridden)
6111
-     *
6112
-     * @param array $query_params like get_all's
6113
-     * @return string[]
6114
-     * @throws EE_Error
6115
-     */
6116
-    public function get_all_names($query_params = array())
6117
-    {
6118
-        $objs = $this->get_all($query_params);
6119
-        $names = array();
6120
-        foreach ($objs as $obj) {
6121
-            $names[ $obj->ID() ] = $obj->name();
6122
-        }
6123
-        return $names;
6124
-    }
6125
-
6126
-
6127
-
6128
-    /**
6129
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
6130
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6131
-     * this is duplicated effort and reduces efficiency) you would be better to use
6132
-     * array_keys() on $model_objects.
6133
-     *
6134
-     * @param \EE_Base_Class[] $model_objects
6135
-     * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6136
-     *                                               in the returned array
6137
-     * @return array
6138
-     * @throws EE_Error
6139
-     */
6140
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
6141
-    {
6142
-        if (! $this->has_primary_key_field()) {
6143
-            if (WP_DEBUG) {
6144
-                EE_Error::add_error(
6145
-                    esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6146
-                    __FILE__,
6147
-                    __FUNCTION__,
6148
-                    __LINE__
6149
-                );
6150
-            }
6151
-        }
6152
-        $IDs = array();
6153
-        foreach ($model_objects as $model_object) {
6154
-            $id = $model_object->ID();
6155
-            if (! $id) {
6156
-                if ($filter_out_empty_ids) {
6157
-                    continue;
6158
-                }
6159
-                if (WP_DEBUG) {
6160
-                    EE_Error::add_error(
6161
-                        esc_html__(
6162
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6163
-                            'event_espresso'
6164
-                        ),
6165
-                        __FILE__,
6166
-                        __FUNCTION__,
6167
-                        __LINE__
6168
-                    );
6169
-                }
6170
-            }
6171
-            $IDs[] = $id;
6172
-        }
6173
-        return $IDs;
6174
-    }
6175
-
6176
-
6177
-
6178
-    /**
6179
-     * Returns the string used in capabilities relating to this model. If there
6180
-     * are no capabilities that relate to this model returns false
6181
-     *
6182
-     * @return string|false
6183
-     */
6184
-    public function cap_slug()
6185
-    {
6186
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6187
-    }
6188
-
6189
-
6190
-
6191
-    /**
6192
-     * Returns the capability-restrictions array (@see EEM_Base::_cap_restrictions).
6193
-     * If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6194
-     * only returns the cap restrictions array in that context (ie, the array
6195
-     * at that key)
6196
-     *
6197
-     * @param string $context
6198
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6199
-     * @throws EE_Error
6200
-     */
6201
-    public function cap_restrictions($context = EEM_Base::caps_read)
6202
-    {
6203
-        EEM_Base::verify_is_valid_cap_context($context);
6204
-        // check if we ought to run the restriction generator first
6205
-        if (
6206
-            isset($this->_cap_restriction_generators[ $context ])
6207
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6208
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6209
-        ) {
6210
-            $this->_cap_restrictions[ $context ] = array_merge(
6211
-                $this->_cap_restrictions[ $context ],
6212
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6213
-            );
6214
-        }
6215
-        // and make sure we've finalized the construction of each restriction
6216
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6217
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6218
-                $where_conditions_obj->_finalize_construct($this);
6219
-            }
6220
-        }
6221
-        return $this->_cap_restrictions[ $context ];
6222
-    }
6223
-
6224
-
6225
-
6226
-    /**
6227
-     * Indicating whether or not this model thinks its a wp core model
6228
-     *
6229
-     * @return boolean
6230
-     */
6231
-    public function is_wp_core_model()
6232
-    {
6233
-        return $this->_wp_core_model;
6234
-    }
6235
-
6236
-
6237
-
6238
-    /**
6239
-     * Gets all the caps that are missing which impose a restriction on
6240
-     * queries made in this context
6241
-     *
6242
-     * @param string $context one of EEM_Base::caps_ constants
6243
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6244
-     * @throws EE_Error
6245
-     */
6246
-    public function caps_missing($context = EEM_Base::caps_read)
6247
-    {
6248
-        $missing_caps = array();
6249
-        $cap_restrictions = $this->cap_restrictions($context);
6250
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6251
-            if (
6252
-                ! EE_Capabilities::instance()
6253
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6254
-            ) {
6255
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6256
-            }
6257
-        }
6258
-        return $missing_caps;
6259
-    }
6260
-
6261
-
6262
-
6263
-    /**
6264
-     * Gets the mapping from capability contexts to action strings used in capability names
6265
-     *
6266
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6267
-     * one of 'read', 'edit', or 'delete'
6268
-     */
6269
-    public function cap_contexts_to_cap_action_map()
6270
-    {
6271
-        return apply_filters(
6272
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6273
-            $this->_cap_contexts_to_cap_action_map,
6274
-            $this
6275
-        );
6276
-    }
6277
-
6278
-
6279
-
6280
-    /**
6281
-     * Gets the action string for the specified capability context
6282
-     *
6283
-     * @param string $context
6284
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6285
-     * @throws EE_Error
6286
-     */
6287
-    public function cap_action_for_context($context)
6288
-    {
6289
-        $mapping = $this->cap_contexts_to_cap_action_map();
6290
-        if (isset($mapping[ $context ])) {
6291
-            return $mapping[ $context ];
6292
-        }
6293
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6294
-            return $action;
6295
-        }
6296
-        throw new EE_Error(
6297
-            sprintf(
6298
-                esc_html__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6299
-                $context,
6300
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6301
-            )
6302
-        );
6303
-    }
6304
-
6305
-
6306
-
6307
-    /**
6308
-     * Returns all the capability contexts which are valid when querying models
6309
-     *
6310
-     * @return array
6311
-     */
6312
-    public static function valid_cap_contexts()
6313
-    {
6314
-        return apply_filters('FHEE__EEM_Base__valid_cap_contexts', array(
6315
-            self::caps_read,
6316
-            self::caps_read_admin,
6317
-            self::caps_edit,
6318
-            self::caps_delete,
6319
-        ));
6320
-    }
6321
-
6322
-
6323
-
6324
-    /**
6325
-     * Returns all valid options for 'default_where_conditions'
6326
-     *
6327
-     * @return array
6328
-     */
6329
-    public static function valid_default_where_conditions()
6330
-    {
6331
-        return array(
6332
-            EEM_Base::default_where_conditions_all,
6333
-            EEM_Base::default_where_conditions_this_only,
6334
-            EEM_Base::default_where_conditions_others_only,
6335
-            EEM_Base::default_where_conditions_minimum_all,
6336
-            EEM_Base::default_where_conditions_minimum_others,
6337
-            EEM_Base::default_where_conditions_none
6338
-        );
6339
-    }
6340
-
6341
-    // public static function default_where_conditions_full
6342
-    /**
6343
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6344
-     *
6345
-     * @param string $context
6346
-     * @return bool
6347
-     * @throws EE_Error
6348
-     */
6349
-    public static function verify_is_valid_cap_context($context)
6350
-    {
6351
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6352
-        if (in_array($context, $valid_cap_contexts)) {
6353
-            return true;
6354
-        }
6355
-        throw new EE_Error(
6356
-            sprintf(
6357
-                esc_html__(
6358
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6359
-                    'event_espresso'
6360
-                ),
6361
-                $context,
6362
-                'EEM_Base',
6363
-                implode(',', $valid_cap_contexts)
6364
-            )
6365
-        );
6366
-    }
6367
-
6368
-
6369
-
6370
-    /**
6371
-     * Clears all the models field caches. This is only useful when a sub-class
6372
-     * might have added a field or something and these caches might be invalidated
6373
-     */
6374
-    protected function _invalidate_field_caches()
6375
-    {
6376
-        $this->_cache_foreign_key_to_fields = array();
6377
-        $this->_cached_fields = null;
6378
-        $this->_cached_fields_non_db_only = null;
6379
-    }
6380
-
6381
-
6382
-
6383
-    /**
6384
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6385
-     * (eg "and", "or", "not").
6386
-     *
6387
-     * @return array
6388
-     */
6389
-    public function logic_query_param_keys()
6390
-    {
6391
-        return $this->_logic_query_param_keys;
6392
-    }
6393
-
6394
-
6395
-
6396
-    /**
6397
-     * Determines whether or not the where query param array key is for a logic query param.
6398
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6399
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6400
-     *
6401
-     * @param $query_param_key
6402
-     * @return bool
6403
-     */
6404
-    public function is_logic_query_param_key($query_param_key)
6405
-    {
6406
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6407
-            if (
6408
-                $query_param_key === $logic_query_param_key
6409
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6410
-            ) {
6411
-                return true;
6412
-            }
6413
-        }
6414
-        return false;
6415
-    }
6416
-
6417
-    /**
6418
-     * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6419
-     * @since 4.9.74.p
6420
-     * @return boolean
6421
-     */
6422
-    public function hasPassword()
6423
-    {
6424
-        // if we don't yet know if there's a password field, find out and remember it for next time.
6425
-        if ($this->has_password_field === null) {
6426
-            $password_field = $this->getPasswordField();
6427
-            $this->has_password_field = $password_field instanceof EE_Password_Field ? true : false;
6428
-        }
6429
-        return $this->has_password_field;
6430
-    }
6431
-
6432
-    /**
6433
-     * Returns the password field on this model, if there is one
6434
-     * @since 4.9.74.p
6435
-     * @return EE_Password_Field|null
6436
-     */
6437
-    public function getPasswordField()
6438
-    {
6439
-        // if we definetely already know there is a password field or not (because has_password_field is true or false)
6440
-        // there's no need to search for it. If we don't know yet, then find out
6441
-        if ($this->has_password_field === null && $this->password_field === null) {
6442
-            $this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6443
-        }
6444
-        // don't bother setting has_password_field because that's hasPassword()'s job.
6445
-        return $this->password_field;
6446
-    }
6447
-
6448
-
6449
-    /**
6450
-     * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6451
-     * @since 4.9.74.p
6452
-     * @return EE_Model_Field_Base[]
6453
-     * @throws EE_Error
6454
-     */
6455
-    public function getPasswordProtectedFields()
6456
-    {
6457
-        $password_field = $this->getPasswordField();
6458
-        $fields = array();
6459
-        if ($password_field instanceof EE_Password_Field) {
6460
-            $field_names = $password_field->protectedFields();
6461
-            foreach ($field_names as $field_name) {
6462
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6463
-            }
6464
-        }
6465
-        return $fields;
6466
-    }
6467
-
6468
-
6469
-    /**
6470
-     * Checks if the current user can perform the requested action on this model
6471
-     * @since 4.9.74.p
6472
-     * @param string $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6473
-     * @param EE_Base_Class|array $model_obj_or_fields_n_values
6474
-     * @return bool
6475
-     * @throws EE_Error
6476
-     * @throws InvalidArgumentException
6477
-     * @throws InvalidDataTypeException
6478
-     * @throws InvalidInterfaceException
6479
-     * @throws ReflectionException
6480
-     * @throws UnexpectedEntityException
6481
-     */
6482
-    public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6483
-    {
6484
-        if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6485
-            $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6486
-        }
6487
-        if (!is_array($model_obj_or_fields_n_values)) {
6488
-            throw new UnexpectedEntityException(
6489
-                $model_obj_or_fields_n_values,
6490
-                'EE_Base_Class',
6491
-                sprintf(
6492
-                    esc_html__('%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.', 'event_espresso'),
6493
-                    __FUNCTION__
6494
-                )
6495
-            );
6496
-        }
6497
-        return $this->exists(
6498
-            $this->alter_query_params_to_restrict_by_ID(
6499
-                $this->get_index_primary_key_string($model_obj_or_fields_n_values),
6500
-                array(
6501
-                    'default_where_conditions' => 'none',
6502
-                    'caps'                     => $cap_to_check,
6503
-                )
6504
-            )
6505
-        );
6506
-    }
6507
-
6508
-    /**
6509
-     * Returns the query param where conditions key to the password affecting this model.
6510
-     * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6511
-     * @since 4.9.74.p
6512
-     * @return null|string
6513
-     * @throws EE_Error
6514
-     * @throws InvalidArgumentException
6515
-     * @throws InvalidDataTypeException
6516
-     * @throws InvalidInterfaceException
6517
-     * @throws ModelConfigurationException
6518
-     * @throws ReflectionException
6519
-     */
6520
-    public function modelChainAndPassword()
6521
-    {
6522
-        if ($this->model_chain_to_password === null) {
6523
-            throw new ModelConfigurationException(
6524
-                $this,
6525
-                esc_html_x(
6526
-                // @codingStandardsIgnoreStart
6527
-                    'Cannot exclude protected data because the model has not specified which model has the password.',
6528
-                    // @codingStandardsIgnoreEnd
6529
-                    '1: model name',
6530
-                    'event_espresso'
6531
-                )
6532
-            );
6533
-        }
6534
-        if ($this->model_chain_to_password === '') {
6535
-            $model_with_password = $this;
6536
-        } else {
6537
-            if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6538
-                $last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6539
-            } else {
6540
-                $last_model_in_chain = $this->model_chain_to_password;
6541
-            }
6542
-            $model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6543
-        }
6544
-
6545
-        $password_field = $model_with_password->getPasswordField();
6546
-        if ($password_field instanceof EE_Password_Field) {
6547
-            $password_field_name = $password_field->get_name();
6548
-        } else {
6549
-            throw new ModelConfigurationException(
6550
-                $this,
6551
-                sprintf(
6552
-                    esc_html_x(
6553
-                        '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"',
6554
-                        '1: model name, 2: special string',
6555
-                        'event_espresso'
6556
-                    ),
6557
-                    $model_with_password->get_this_model_name(),
6558
-                    $this->model_chain_to_password
6559
-                )
6560
-            );
6561
-        }
6562
-        return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6563
-    }
6564
-
6565
-    /**
6566
-     * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6567
-     * or if this model itself has a password affecting access to some of its other fields.
6568
-     * @since 4.9.74.p
6569
-     * @return boolean
6570
-     */
6571
-    public function restrictedByRelatedModelPassword()
6572
-    {
6573
-        return $this->model_chain_to_password !== null;
6574
-    }
3864
+		}
3865
+		return $null_friendly_where_conditions;
3866
+	}
3867
+
3868
+
3869
+
3870
+	/**
3871
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3872
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3873
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3874
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3875
+	 *
3876
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3877
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3878
+	 */
3879
+	private function _get_default_where_conditions($model_relation_path = '')
3880
+	{
3881
+		if ($this->_ignore_where_strategy) {
3882
+			return array();
3883
+		}
3884
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3885
+	}
3886
+
3887
+
3888
+
3889
+	/**
3890
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3891
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3892
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3893
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3894
+	 * Similar to _get_default_where_conditions
3895
+	 *
3896
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3897
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3898
+	 */
3899
+	protected function _get_minimum_where_conditions($model_relation_path = '')
3900
+	{
3901
+		if ($this->_ignore_where_strategy) {
3902
+			return array();
3903
+		}
3904
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3905
+	}
3906
+
3907
+
3908
+
3909
+	/**
3910
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3911
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3912
+	 *
3913
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
3914
+	 * @return string
3915
+	 * @throws EE_Error
3916
+	 */
3917
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3918
+	{
3919
+		$selects = $this->_get_columns_to_select_for_this_model();
3920
+		foreach (
3921
+			$model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3922
+		) {
3923
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3924
+			$other_model_selects = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3925
+			foreach ($other_model_selects as $key => $value) {
3926
+				$selects[] = $value;
3927
+			}
3928
+		}
3929
+		return implode(", ", $selects);
3930
+	}
3931
+
3932
+
3933
+
3934
+	/**
3935
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3936
+	 * So that's going to be the columns for all the fields on the model
3937
+	 *
3938
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
3939
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3940
+	 */
3941
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3942
+	{
3943
+		$fields = $this->field_settings();
3944
+		$selects = array();
3945
+		$table_alias_with_model_relation_chain_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3946
+			$model_relation_chain,
3947
+			$this->get_this_model_name()
3948
+		);
3949
+		foreach ($fields as $field_obj) {
3950
+			$selects[] = $table_alias_with_model_relation_chain_prefix
3951
+						 . $field_obj->get_table_alias()
3952
+						 . "."
3953
+						 . $field_obj->get_table_column()
3954
+						 . " AS '"
3955
+						 . $table_alias_with_model_relation_chain_prefix
3956
+						 . $field_obj->get_table_alias()
3957
+						 . "."
3958
+						 . $field_obj->get_table_column()
3959
+						 . "'";
3960
+		}
3961
+		// make sure we are also getting the PKs of each table
3962
+		$tables = $this->get_tables();
3963
+		if (count($tables) > 1) {
3964
+			foreach ($tables as $table_obj) {
3965
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3966
+									   . $table_obj->get_fully_qualified_pk_column();
3967
+				if (! in_array($qualified_pk_column, $selects)) {
3968
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3969
+				}
3970
+			}
3971
+		}
3972
+		return $selects;
3973
+	}
3974
+
3975
+
3976
+
3977
+	/**
3978
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3979
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3980
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3981
+	 * SQL for joining, and the data types
3982
+	 *
3983
+	 * @param null|string                 $original_query_param
3984
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
3985
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3986
+	 * @param    string                   $query_param_type     like Registration.Transaction.TXN_ID
3987
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3988
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
3989
+	 *                                                          or 'Registration's
3990
+	 * @param string                      $original_query_param what it originally was (eg
3991
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3992
+	 *                                                          matches $query_param
3993
+	 * @throws EE_Error
3994
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3995
+	 */
3996
+	private function _extract_related_model_info_from_query_param(
3997
+		$query_param,
3998
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
3999
+		$query_param_type,
4000
+		$original_query_param = null
4001
+	) {
4002
+		if ($original_query_param === null) {
4003
+			$original_query_param = $query_param;
4004
+		}
4005
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4006
+		// check to see if we have a field on this model
4007
+		$this_model_fields = $this->field_settings(true);
4008
+		if (array_key_exists($query_param, $this_model_fields)) {
4009
+			$field_is_allowed = in_array(
4010
+				$query_param_type,
4011
+				[0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4012
+				true
4013
+			);
4014
+			if ($field_is_allowed) {
4015
+				return;
4016
+			}
4017
+			throw new EE_Error(
4018
+				sprintf(
4019
+					esc_html__(
4020
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4021
+						"event_espresso"
4022
+					),
4023
+					$query_param,
4024
+					get_class($this),
4025
+					$query_param_type,
4026
+					$original_query_param
4027
+				)
4028
+			);
4029
+		}
4030
+		// check if this is a special logic query param
4031
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4032
+			$operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4033
+			if ($operator_is_allowed) {
4034
+				return;
4035
+			}
4036
+			throw new EE_Error(
4037
+				sprintf(
4038
+					esc_html__(
4039
+						'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
4040
+						'event_espresso'
4041
+					),
4042
+					implode('", "', $this->_logic_query_param_keys),
4043
+					$query_param,
4044
+					get_class($this),
4045
+					'<br />',
4046
+					"\t"
4047
+					. ' $passed_in_query_info = <pre>'
4048
+					. print_r($passed_in_query_info, true)
4049
+					. '</pre>'
4050
+					. "\n\t"
4051
+					. ' $query_param_type = '
4052
+					. $query_param_type
4053
+					. "\n\t"
4054
+					. ' $original_query_param = '
4055
+					. $original_query_param
4056
+				)
4057
+			);
4058
+		}
4059
+		// check if it's a custom selection
4060
+		if (
4061
+			$this->_custom_selections instanceof CustomSelects
4062
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4063
+		) {
4064
+			return;
4065
+		}
4066
+		// check if has a model name at the beginning
4067
+		// and
4068
+		// check if it's a field on a related model
4069
+		if (
4070
+			$this->extractJoinModelFromQueryParams(
4071
+				$passed_in_query_info,
4072
+				$query_param,
4073
+				$original_query_param,
4074
+				$query_param_type
4075
+			)
4076
+		) {
4077
+			return;
4078
+		}
4079
+
4080
+		// ok so $query_param didn't start with a model name
4081
+		// and we previously confirmed it wasn't a logic query param or field on the current model
4082
+		// it's wack, that's what it is
4083
+		throw new EE_Error(
4084
+			sprintf(
4085
+				esc_html__(
4086
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4087
+					"event_espresso"
4088
+				),
4089
+				$query_param,
4090
+				get_class($this),
4091
+				$query_param_type,
4092
+				$original_query_param
4093
+			)
4094
+		);
4095
+	}
4096
+
4097
+
4098
+	/**
4099
+	 * Extracts any possible join model information from the provided possible_join_string.
4100
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model join
4101
+	 * parts that should be added to the query.
4102
+	 *
4103
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4104
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4105
+	 * @param null|string                 $original_query_param
4106
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4107
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects' etc.)
4108
+	 * @return bool  returns true if a join was added and false if not.
4109
+	 * @throws EE_Error
4110
+	 */
4111
+	private function extractJoinModelFromQueryParams(
4112
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4113
+		$possible_join_string,
4114
+		$original_query_param,
4115
+		$query_parameter_type
4116
+	) {
4117
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4118
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4119
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4120
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4121
+				if ($possible_join_string === '') {
4122
+					// nothing left to $query_param
4123
+					// we should actually end in a field name, not a model like this!
4124
+					throw new EE_Error(
4125
+						sprintf(
4126
+							esc_html__(
4127
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4128
+								"event_espresso"
4129
+							),
4130
+							$possible_join_string,
4131
+							$query_parameter_type,
4132
+							get_class($this),
4133
+							$valid_related_model_name
4134
+						)
4135
+					);
4136
+				}
4137
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4138
+				$related_model_obj->_extract_related_model_info_from_query_param(
4139
+					$possible_join_string,
4140
+					$query_info_carrier,
4141
+					$query_parameter_type,
4142
+					$original_query_param
4143
+				);
4144
+				return true;
4145
+			}
4146
+			if ($possible_join_string === $valid_related_model_name) {
4147
+				$this->_add_join_to_model(
4148
+					$valid_related_model_name,
4149
+					$query_info_carrier,
4150
+					$original_query_param
4151
+				);
4152
+				return true;
4153
+			}
4154
+		}
4155
+		return false;
4156
+	}
4157
+
4158
+
4159
+	/**
4160
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4161
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4162
+	 * @throws EE_Error
4163
+	 */
4164
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4165
+	{
4166
+		if (
4167
+			$this->_custom_selections instanceof CustomSelects
4168
+			&& (
4169
+				$this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4170
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4171
+			)
4172
+		) {
4173
+			$original_selects = $this->_custom_selections->originalSelects();
4174
+			foreach ($original_selects as $alias => $select_configuration) {
4175
+				$this->extractJoinModelFromQueryParams(
4176
+					$query_info_carrier,
4177
+					$select_configuration[0],
4178
+					$select_configuration[0],
4179
+					'custom_selects'
4180
+				);
4181
+			}
4182
+		}
4183
+	}
4184
+
4185
+
4186
+
4187
+	/**
4188
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4189
+	 * and store it on $passed_in_query_info
4190
+	 *
4191
+	 * @param string                      $model_name
4192
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4193
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4194
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4195
+	 *                                                          and are adding a join to 'Payment' with the original
4196
+	 *                                                          query param key
4197
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4198
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4199
+	 *                                                          Payment wants to add default query params so that it
4200
+	 *                                                          will know what models to prepend onto its default query
4201
+	 *                                                          params or in case it wants to rename tables (in case
4202
+	 *                                                          there are multiple joins to the same table)
4203
+	 * @return void
4204
+	 * @throws EE_Error
4205
+	 */
4206
+	private function _add_join_to_model(
4207
+		$model_name,
4208
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4209
+		$original_query_param
4210
+	) {
4211
+		$relation_obj = $this->related_settings_for($model_name);
4212
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4213
+		// check if the relation is HABTM, because then we're essentially doing two joins
4214
+		// If so, join first to the JOIN table, and add its data types, and then continue as normal
4215
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4216
+			$join_model_obj = $relation_obj->get_join_model();
4217
+			// replace the model specified with the join model for this relation chain, whi
4218
+			$relation_chain_to_join_model = EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4219
+				$model_name,
4220
+				$join_model_obj->get_this_model_name(),
4221
+				$model_relation_chain
4222
+			);
4223
+			$passed_in_query_info->merge(
4224
+				new EE_Model_Query_Info_Carrier(
4225
+					array($relation_chain_to_join_model => $join_model_obj->get_this_model_name()),
4226
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4227
+				)
4228
+			);
4229
+		}
4230
+		// now just join to the other table pointed to by the relation object, and add its data types
4231
+		$passed_in_query_info->merge(
4232
+			new EE_Model_Query_Info_Carrier(
4233
+				array($model_relation_chain => $model_name),
4234
+				$relation_obj->get_join_statement($model_relation_chain)
4235
+			)
4236
+		);
4237
+	}
4238
+
4239
+
4240
+
4241
+	/**
4242
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4243
+	 *
4244
+	 * @param array $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4245
+	 * @return string of SQL
4246
+	 * @throws EE_Error
4247
+	 */
4248
+	private function _construct_where_clause($where_params)
4249
+	{
4250
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4251
+		if ($SQL) {
4252
+			return " WHERE " . $SQL;
4253
+		}
4254
+		return '';
4255
+	}
4256
+
4257
+
4258
+
4259
+	/**
4260
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4261
+	 * and should be passed HAVING parameters, not WHERE parameters
4262
+	 *
4263
+	 * @param array $having_params
4264
+	 * @return string
4265
+	 * @throws EE_Error
4266
+	 */
4267
+	private function _construct_having_clause($having_params)
4268
+	{
4269
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4270
+		if ($SQL) {
4271
+			return " HAVING " . $SQL;
4272
+		}
4273
+		return '';
4274
+	}
4275
+
4276
+
4277
+	/**
4278
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4279
+	 * Event_Meta.meta_value = 'foo'))"
4280
+	 *
4281
+	 * @param array  $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4282
+	 * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4283
+	 * @throws EE_Error
4284
+	 * @return string of SQL
4285
+	 */
4286
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4287
+	{
4288
+		$where_clauses = array();
4289
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4290
+			$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4291
+			if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4292
+				switch ($query_param) {
4293
+					case 'not':
4294
+					case 'NOT':
4295
+						$where_clauses[] = "! ("
4296
+										   . $this->_construct_condition_clause_recursive(
4297
+											   $op_and_value_or_sub_condition,
4298
+											   $glue
4299
+										   )
4300
+										   . ")";
4301
+						break;
4302
+					case 'and':
4303
+					case 'AND':
4304
+						$where_clauses[] = " ("
4305
+										   . $this->_construct_condition_clause_recursive(
4306
+											   $op_and_value_or_sub_condition,
4307
+											   ' AND '
4308
+										   )
4309
+										   . ")";
4310
+						break;
4311
+					case 'or':
4312
+					case 'OR':
4313
+						$where_clauses[] = " ("
4314
+										   . $this->_construct_condition_clause_recursive(
4315
+											   $op_and_value_or_sub_condition,
4316
+											   ' OR '
4317
+										   )
4318
+										   . ")";
4319
+						break;
4320
+				}
4321
+			} else {
4322
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4323
+				// if it's not a normal field, maybe it's a custom selection?
4324
+				if (! $field_obj) {
4325
+					if ($this->_custom_selections instanceof CustomSelects) {
4326
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4327
+					} else {
4328
+						throw new EE_Error(sprintf(esc_html__(
4329
+							"%s is neither a valid model field name, nor a custom selection",
4330
+							"event_espresso"
4331
+						), $query_param));
4332
+					}
4333
+				}
4334
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4335
+				$where_clauses[] = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4336
+			}
4337
+		}
4338
+		return $where_clauses ? implode($glue, $where_clauses) : '';
4339
+	}
4340
+
4341
+
4342
+
4343
+	/**
4344
+	 * Takes the input parameter and extract the table name (alias) and column name
4345
+	 *
4346
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4347
+	 * @throws EE_Error
4348
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4349
+	 */
4350
+	private function _deduce_column_name_from_query_param($query_param)
4351
+	{
4352
+		$field = $this->_deduce_field_from_query_param($query_param);
4353
+		if ($field) {
4354
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4355
+				$field->get_model_name(),
4356
+				$query_param
4357
+			);
4358
+			return $table_alias_prefix . $field->get_qualified_column();
4359
+		}
4360
+		if (
4361
+			$this->_custom_selections instanceof CustomSelects
4362
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4363
+		) {
4364
+			// maybe it's custom selection item?
4365
+			// if so, just use it as the "column name"
4366
+			return $query_param;
4367
+		}
4368
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4369
+			? implode(',', $this->_custom_selections->columnAliases())
4370
+			: '';
4371
+		throw new EE_Error(
4372
+			sprintf(
4373
+				esc_html__(
4374
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4375
+					"event_espresso"
4376
+				),
4377
+				$query_param,
4378
+				$custom_select_aliases
4379
+			)
4380
+		);
4381
+	}
4382
+
4383
+
4384
+
4385
+	/**
4386
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4387
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4388
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4389
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4390
+	 *
4391
+	 * @param string $condition_query_param_key
4392
+	 * @return string
4393
+	 */
4394
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4395
+	{
4396
+		$pos_of_star = strpos($condition_query_param_key, '*');
4397
+		if ($pos_of_star === false) {
4398
+			return $condition_query_param_key;
4399
+		}
4400
+		$condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
4401
+		return $condition_query_param_sans_star;
4402
+	}
4403
+
4404
+
4405
+
4406
+	/**
4407
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4408
+	 *
4409
+	 * @param                            mixed      array | string    $op_and_value
4410
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4411
+	 * @throws EE_Error
4412
+	 * @return string
4413
+	 */
4414
+	private function _construct_op_and_value($op_and_value, $field_obj)
4415
+	{
4416
+		if (is_array($op_and_value)) {
4417
+			$operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4418
+			if (! $operator) {
4419
+				$php_array_like_string = array();
4420
+				foreach ($op_and_value as $key => $value) {
4421
+					$php_array_like_string[] = "$key=>$value";
4422
+				}
4423
+				throw new EE_Error(
4424
+					sprintf(
4425
+						esc_html__(
4426
+							"You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4427
+							"event_espresso"
4428
+						),
4429
+						implode(",", $php_array_like_string)
4430
+					)
4431
+				);
4432
+			}
4433
+			$value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4434
+		} else {
4435
+			$operator = '=';
4436
+			$value = $op_and_value;
4437
+		}
4438
+		// check to see if the value is actually another field
4439
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4440
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4441
+		}
4442
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4443
+			// in this case, the value should be an array, or at least a comma-separated list
4444
+			// it will need to handle a little differently
4445
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4446
+			// note: $cleaned_value has already been run through $wpdb->prepare()
4447
+			return $operator . SP . $cleaned_value;
4448
+		}
4449
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4450
+			// the value should be an array with count of two.
4451
+			if (count($value) !== 2) {
4452
+				throw new EE_Error(
4453
+					sprintf(
4454
+						esc_html__(
4455
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4456
+							'event_espresso'
4457
+						),
4458
+						"BETWEEN"
4459
+					)
4460
+				);
4461
+			}
4462
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4463
+			return $operator . SP . $cleaned_value;
4464
+		}
4465
+		if (in_array($operator, $this->valid_null_style_operators())) {
4466
+			if ($value !== null) {
4467
+				throw new EE_Error(
4468
+					sprintf(
4469
+						esc_html__(
4470
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4471
+							"event_espresso"
4472
+						),
4473
+						$value,
4474
+						$operator
4475
+					)
4476
+				);
4477
+			}
4478
+			return $operator;
4479
+		}
4480
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4481
+			// if the operator is 'LIKE', we want to allow percent signs (%) and not
4482
+			// remove other junk. So just treat it as a string.
4483
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4484
+		}
4485
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4486
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4487
+		}
4488
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4489
+			throw new EE_Error(
4490
+				sprintf(
4491
+					esc_html__(
4492
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4493
+						'event_espresso'
4494
+					),
4495
+					$operator,
4496
+					$operator
4497
+				)
4498
+			);
4499
+		}
4500
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4501
+			throw new EE_Error(
4502
+				sprintf(
4503
+					esc_html__(
4504
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4505
+						'event_espresso'
4506
+					),
4507
+					$operator,
4508
+					$operator
4509
+				)
4510
+			);
4511
+		}
4512
+		throw new EE_Error(
4513
+			sprintf(
4514
+				esc_html__(
4515
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4516
+					"event_espresso"
4517
+				),
4518
+				http_build_query($op_and_value)
4519
+			)
4520
+		);
4521
+	}
4522
+
4523
+
4524
+
4525
+	/**
4526
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4527
+	 *
4528
+	 * @param array                      $values
4529
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4530
+	 *                                              '%s'
4531
+	 * @return string
4532
+	 * @throws EE_Error
4533
+	 */
4534
+	public function _construct_between_value($values, $field_obj)
4535
+	{
4536
+		$cleaned_values = array();
4537
+		foreach ($values as $value) {
4538
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4539
+		}
4540
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4541
+	}
4542
+
4543
+
4544
+	/**
4545
+	 * Takes an array or a comma-separated list of $values and cleans them
4546
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4547
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4548
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4549
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4550
+	 *
4551
+	 * @param mixed                      $values    array or comma-separated string
4552
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4553
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4554
+	 * @throws EE_Error
4555
+	 */
4556
+	public function _construct_in_value($values, $field_obj)
4557
+	{
4558
+		$prepped = [];
4559
+		// check if the value is a CSV list
4560
+		if (is_string($values)) {
4561
+			// in which case, turn it into an array
4562
+			$values = explode(',', $values);
4563
+		}
4564
+		// make sure we only have one of each value in the list
4565
+		$values = array_unique($values);
4566
+		foreach ($values as $value) {
4567
+			$prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4568
+		}
4569
+		// we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4570
+		// but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4571
+		// which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4572
+		if (empty($prepped)) {
4573
+			$all_fields = $this->field_settings();
4574
+			$first_field    = reset($all_fields);
4575
+			$main_table = $this->_get_main_table();
4576
+			$prepped[]  = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4577
+		}
4578
+		return '(' . implode(',', $prepped) . ')';
4579
+	}
4580
+
4581
+
4582
+
4583
+	/**
4584
+	 * @param mixed                      $value
4585
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4586
+	 * @throws EE_Error
4587
+	 * @return false|null|string
4588
+	 */
4589
+	private function _wpdb_prepare_using_field($value, $field_obj)
4590
+	{
4591
+		/** @type WPDB $wpdb */
4592
+		global $wpdb;
4593
+		if ($field_obj instanceof EE_Model_Field_Base) {
4594
+			return $wpdb->prepare(
4595
+				$field_obj->get_wpdb_data_type(),
4596
+				$this->_prepare_value_for_use_in_db($value, $field_obj)
4597
+			);
4598
+		} //$field_obj should really just be a data type
4599
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4600
+			throw new EE_Error(
4601
+				sprintf(
4602
+					esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4603
+					$field_obj,
4604
+					implode(",", $this->_valid_wpdb_data_types)
4605
+				)
4606
+			);
4607
+		}
4608
+		return $wpdb->prepare($field_obj, $value);
4609
+	}
4610
+
4611
+
4612
+
4613
+	/**
4614
+	 * Takes the input parameter and finds the model field that it indicates.
4615
+	 *
4616
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4617
+	 * @throws EE_Error
4618
+	 * @return EE_Model_Field_Base
4619
+	 */
4620
+	protected function _deduce_field_from_query_param($query_param_name)
4621
+	{
4622
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
4623
+		// which will help us find the database table and column
4624
+		$query_param_parts = explode(".", $query_param_name);
4625
+		if (empty($query_param_parts)) {
4626
+			throw new EE_Error(sprintf(esc_html__(
4627
+				"_extract_column_name is empty when trying to extract column and table name from %s",
4628
+				'event_espresso'
4629
+			), $query_param_name));
4630
+		}
4631
+		$number_of_parts = count($query_param_parts);
4632
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4633
+		if ($number_of_parts === 1) {
4634
+			$field_name = $last_query_param_part;
4635
+			$model_obj = $this;
4636
+		} else {// $number_of_parts >= 2
4637
+			// the last part is the column name, and there are only 2parts. therefore...
4638
+			$field_name = $last_query_param_part;
4639
+			$model_obj = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4640
+		}
4641
+		try {
4642
+			return $model_obj->field_settings_for($field_name);
4643
+		} catch (EE_Error $e) {
4644
+			return null;
4645
+		}
4646
+	}
4647
+
4648
+
4649
+
4650
+	/**
4651
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4652
+	 * alias and column which corresponds to it
4653
+	 *
4654
+	 * @param string $field_name
4655
+	 * @throws EE_Error
4656
+	 * @return string
4657
+	 */
4658
+	public function _get_qualified_column_for_field($field_name)
4659
+	{
4660
+		$all_fields = $this->field_settings();
4661
+		$field = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4662
+		if ($field) {
4663
+			return $field->get_qualified_column();
4664
+		}
4665
+		throw new EE_Error(
4666
+			sprintf(
4667
+				esc_html__(
4668
+					"There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4669
+					'event_espresso'
4670
+				),
4671
+				$field_name,
4672
+				get_class($this)
4673
+			)
4674
+		);
4675
+	}
4676
+
4677
+
4678
+
4679
+	/**
4680
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4681
+	 * Example usage:
4682
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4683
+	 *      array(),
4684
+	 *      ARRAY_A,
4685
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4686
+	 *  );
4687
+	 * is equivalent to
4688
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4689
+	 * and
4690
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4691
+	 *      array(
4692
+	 *          array(
4693
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4694
+	 *          ),
4695
+	 *          ARRAY_A,
4696
+	 *          implode(
4697
+	 *              ', ',
4698
+	 *              array_merge(
4699
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4700
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4701
+	 *              )
4702
+	 *          )
4703
+	 *      )
4704
+	 *  );
4705
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4706
+	 *
4707
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4708
+	 *                                            and the one whose fields you are selecting for example: when querying
4709
+	 *                                            tickets model and selecting fields from the tickets model you would
4710
+	 *                                            leave this parameter empty, because no models are needed to join
4711
+	 *                                            between the queried model and the selected one. Likewise when
4712
+	 *                                            querying the datetime model and selecting fields from the tickets
4713
+	 *                                            model, it would also be left empty, because there is a direct
4714
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4715
+	 *                                            them together. However, when querying from the event model and
4716
+	 *                                            selecting fields from the ticket model, you should provide the string
4717
+	 *                                            'Datetime', indicating that the event model must first join to the
4718
+	 *                                            datetime model in order to find its relation to ticket model.
4719
+	 *                                            Also, when querying from the venue model and selecting fields from
4720
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4721
+	 *                                            indicating you need to join the venue model to the event model,
4722
+	 *                                            to the datetime model, in order to find its relation to the ticket model.
4723
+	 *                                            This string is used to deduce the prefix that gets added onto the
4724
+	 *                                            models' tables qualified columns
4725
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4726
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4727
+	 *                                            qualified column names
4728
+	 * @return array|string
4729
+	 */
4730
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4731
+	{
4732
+		$table_prefix = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4733
+		$qualified_columns = array();
4734
+		foreach ($this->field_settings() as $field_name => $field) {
4735
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4736
+		}
4737
+		return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4738
+	}
4739
+
4740
+
4741
+
4742
+	/**
4743
+	 * constructs the select use on special limit joins
4744
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4745
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4746
+	 * (as that is typically where the limits would be set).
4747
+	 *
4748
+	 * @param  string       $table_alias The table the select is being built for
4749
+	 * @param  mixed|string $limit       The limit for this select
4750
+	 * @return string                The final select join element for the query.
4751
+	 */
4752
+	public function _construct_limit_join_select($table_alias, $limit)
4753
+	{
4754
+		$SQL = '';
4755
+		foreach ($this->_tables as $table_obj) {
4756
+			if ($table_obj instanceof EE_Primary_Table) {
4757
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4758
+					? $table_obj->get_select_join_limit($limit)
4759
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4760
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4761
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4762
+					? $table_obj->get_select_join_limit_join($limit)
4763
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4764
+			}
4765
+		}
4766
+		return $SQL;
4767
+	}
4768
+
4769
+
4770
+
4771
+	/**
4772
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4773
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4774
+	 *
4775
+	 * @return string SQL
4776
+	 * @throws EE_Error
4777
+	 */
4778
+	public function _construct_internal_join()
4779
+	{
4780
+		$SQL = $this->_get_main_table()->get_table_sql();
4781
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4782
+		return $SQL;
4783
+	}
4784
+
4785
+
4786
+
4787
+	/**
4788
+	 * Constructs the SQL for joining all the tables on this model.
4789
+	 * Normally $alias should be the primary table's alias, but in cases where
4790
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4791
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4792
+	 * alias, this will construct SQL like:
4793
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4794
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4795
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4796
+	 *
4797
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4798
+	 * @return string
4799
+	 */
4800
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4801
+	{
4802
+		$SQL = '';
4803
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4804
+		foreach ($this->_tables as $table_obj) {
4805
+			if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4806
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4807
+					// so we're joining to this table, meaning the table is already in
4808
+					// the FROM statement, BUT the primary table isn't. So we want
4809
+					// to add the inverse join sql
4810
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4811
+				} else {
4812
+					// just add a regular JOIN to this table from the primary table
4813
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4814
+				}
4815
+			}//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4816
+		}
4817
+		return $SQL;
4818
+	}
4819
+
4820
+
4821
+
4822
+	/**
4823
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4824
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4825
+	 * their data type (eg, '%s', '%d', etc)
4826
+	 *
4827
+	 * @return array
4828
+	 */
4829
+	public function _get_data_types()
4830
+	{
4831
+		$data_types = array();
4832
+		foreach ($this->field_settings() as $field_obj) {
4833
+			// $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4834
+			/** @var $field_obj EE_Model_Field_Base */
4835
+			$data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4836
+		}
4837
+		return $data_types;
4838
+	}
4839
+
4840
+
4841
+
4842
+	/**
4843
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4844
+	 *
4845
+	 * @param string $model_name
4846
+	 * @throws EE_Error
4847
+	 * @return EEM_Base
4848
+	 */
4849
+	public function get_related_model_obj($model_name)
4850
+	{
4851
+		$model_classname = "EEM_" . $model_name;
4852
+		if (! class_exists($model_classname)) {
4853
+			throw new EE_Error(sprintf(esc_html__(
4854
+				"You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4855
+				'event_espresso'
4856
+			), $model_name, $model_classname));
4857
+		}
4858
+		return call_user_func($model_classname . "::instance");
4859
+	}
4860
+
4861
+
4862
+
4863
+	/**
4864
+	 * Returns the array of EE_ModelRelations for this model.
4865
+	 *
4866
+	 * @return EE_Model_Relation_Base[]
4867
+	 */
4868
+	public function relation_settings()
4869
+	{
4870
+		return $this->_model_relations;
4871
+	}
4872
+
4873
+
4874
+
4875
+	/**
4876
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4877
+	 * because without THOSE models, this model probably doesn't have much purpose.
4878
+	 * (Eg, without an event, datetimes have little purpose.)
4879
+	 *
4880
+	 * @return EE_Belongs_To_Relation[]
4881
+	 */
4882
+	public function belongs_to_relations()
4883
+	{
4884
+		$belongs_to_relations = array();
4885
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
4886
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
4887
+				$belongs_to_relations[ $model_name ] = $relation_obj;
4888
+			}
4889
+		}
4890
+		return $belongs_to_relations;
4891
+	}
4892
+
4893
+
4894
+
4895
+	/**
4896
+	 * Returns the specified EE_Model_Relation, or throws an exception
4897
+	 *
4898
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
4899
+	 * @throws EE_Error
4900
+	 * @return EE_Model_Relation_Base
4901
+	 */
4902
+	public function related_settings_for($relation_name)
4903
+	{
4904
+		$relatedModels = $this->relation_settings();
4905
+		if (! array_key_exists($relation_name, $relatedModels)) {
4906
+			throw new EE_Error(
4907
+				sprintf(
4908
+					esc_html__(
4909
+						'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4910
+						'event_espresso'
4911
+					),
4912
+					$relation_name,
4913
+					$this->_get_class_name(),
4914
+					implode(', ', array_keys($relatedModels))
4915
+				)
4916
+			);
4917
+		}
4918
+		return $relatedModels[ $relation_name ];
4919
+	}
4920
+
4921
+
4922
+
4923
+	/**
4924
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4925
+	 * fields
4926
+	 *
4927
+	 * @param string $fieldName
4928
+	 * @param boolean $include_db_only_fields
4929
+	 * @throws EE_Error
4930
+	 * @return EE_Model_Field_Base
4931
+	 */
4932
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
4933
+	{
4934
+		$fieldSettings = $this->field_settings($include_db_only_fields);
4935
+		if (! array_key_exists($fieldName, $fieldSettings)) {
4936
+			throw new EE_Error(sprintf(
4937
+				esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4938
+				$fieldName,
4939
+				get_class($this)
4940
+			));
4941
+		}
4942
+		return $fieldSettings[ $fieldName ];
4943
+	}
4944
+
4945
+
4946
+
4947
+	/**
4948
+	 * Checks if this field exists on this model
4949
+	 *
4950
+	 * @param string $fieldName a key in the model's _field_settings array
4951
+	 * @return boolean
4952
+	 */
4953
+	public function has_field($fieldName)
4954
+	{
4955
+		$fieldSettings = $this->field_settings(true);
4956
+		if (isset($fieldSettings[ $fieldName ])) {
4957
+			return true;
4958
+		}
4959
+		return false;
4960
+	}
4961
+
4962
+
4963
+
4964
+	/**
4965
+	 * Returns whether or not this model has a relation to the specified model
4966
+	 *
4967
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
4968
+	 * @return boolean
4969
+	 */
4970
+	public function has_relation($relation_name)
4971
+	{
4972
+		$relations = $this->relation_settings();
4973
+		if (isset($relations[ $relation_name ])) {
4974
+			return true;
4975
+		}
4976
+		return false;
4977
+	}
4978
+
4979
+
4980
+
4981
+	/**
4982
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4983
+	 * Eg, on EE_Answer that would be ANS_ID field object
4984
+	 *
4985
+	 * @param $field_obj
4986
+	 * @return boolean
4987
+	 */
4988
+	public function is_primary_key_field($field_obj)
4989
+	{
4990
+		return $field_obj instanceof EE_Primary_Key_Field_Base ? true : false;
4991
+	}
4992
+
4993
+
4994
+
4995
+	/**
4996
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4997
+	 * Eg, on EE_Answer that would be ANS_ID field object
4998
+	 *
4999
+	 * @return EE_Primary_Key_Field_Base
5000
+	 * @throws EE_Error
5001
+	 */
5002
+	public function get_primary_key_field()
5003
+	{
5004
+		if ($this->_primary_key_field === null) {
5005
+			foreach ($this->field_settings(true) as $field_obj) {
5006
+				if ($this->is_primary_key_field($field_obj)) {
5007
+					$this->_primary_key_field = $field_obj;
5008
+					break;
5009
+				}
5010
+			}
5011
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5012
+				throw new EE_Error(sprintf(
5013
+					esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5014
+					get_class($this)
5015
+				));
5016
+			}
5017
+		}
5018
+		return $this->_primary_key_field;
5019
+	}
5020
+
5021
+
5022
+
5023
+	/**
5024
+	 * Returns whether or not not there is a primary key on this model.
5025
+	 * Internally does some caching.
5026
+	 *
5027
+	 * @return boolean
5028
+	 */
5029
+	public function has_primary_key_field()
5030
+	{
5031
+		if ($this->_has_primary_key_field === null) {
5032
+			try {
5033
+				$this->get_primary_key_field();
5034
+				$this->_has_primary_key_field = true;
5035
+			} catch (EE_Error $e) {
5036
+				$this->_has_primary_key_field = false;
5037
+			}
5038
+		}
5039
+		return $this->_has_primary_key_field;
5040
+	}
5041
+
5042
+
5043
+
5044
+	/**
5045
+	 * Finds the first field of type $field_class_name.
5046
+	 *
5047
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5048
+	 *                                 EE_Foreign_Key_Field, etc
5049
+	 * @return EE_Model_Field_Base or null if none is found
5050
+	 */
5051
+	public function get_a_field_of_type($field_class_name)
5052
+	{
5053
+		foreach ($this->field_settings() as $field) {
5054
+			if ($field instanceof $field_class_name) {
5055
+				return $field;
5056
+			}
5057
+		}
5058
+		return null;
5059
+	}
5060
+
5061
+
5062
+
5063
+	/**
5064
+	 * Gets a foreign key field pointing to model.
5065
+	 *
5066
+	 * @param string $model_name eg Event, Registration, not EEM_Event
5067
+	 * @return EE_Foreign_Key_Field_Base
5068
+	 * @throws EE_Error
5069
+	 */
5070
+	public function get_foreign_key_to($model_name)
5071
+	{
5072
+		if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5073
+			foreach ($this->field_settings() as $field) {
5074
+				if (
5075
+					$field instanceof EE_Foreign_Key_Field_Base
5076
+					&& in_array($model_name, $field->get_model_names_pointed_to())
5077
+				) {
5078
+					$this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5079
+					break;
5080
+				}
5081
+			}
5082
+			if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5083
+				throw new EE_Error(sprintf(esc_html__(
5084
+					"There is no foreign key field pointing to model %s on model %s",
5085
+					'event_espresso'
5086
+				), $model_name, get_class($this)));
5087
+			}
5088
+		}
5089
+		return $this->_cache_foreign_key_to_fields[ $model_name ];
5090
+	}
5091
+
5092
+
5093
+
5094
+	/**
5095
+	 * Gets the table name (including $wpdb->prefix) for the table alias
5096
+	 *
5097
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5098
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5099
+	 *                            Either one works
5100
+	 * @return string
5101
+	 */
5102
+	public function get_table_for_alias($table_alias)
5103
+	{
5104
+		$table_alias_sans_model_relation_chain_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5105
+		return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5106
+	}
5107
+
5108
+
5109
+
5110
+	/**
5111
+	 * Returns a flat array of all field son this model, instead of organizing them
5112
+	 * by table_alias as they are in the constructor.
5113
+	 *
5114
+	 * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5115
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
5116
+	 */
5117
+	public function field_settings($include_db_only_fields = false)
5118
+	{
5119
+		if ($include_db_only_fields) {
5120
+			if ($this->_cached_fields === null) {
5121
+				$this->_cached_fields = array();
5122
+				foreach ($this->_fields as $fields_corresponding_to_table) {
5123
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5124
+						$this->_cached_fields[ $field_name ] = $field_obj;
5125
+					}
5126
+				}
5127
+			}
5128
+			return $this->_cached_fields;
5129
+		}
5130
+		if ($this->_cached_fields_non_db_only === null) {
5131
+			$this->_cached_fields_non_db_only = array();
5132
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5133
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5134
+					/** @var $field_obj EE_Model_Field_Base */
5135
+					if (! $field_obj->is_db_only_field()) {
5136
+						$this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5137
+					}
5138
+				}
5139
+			}
5140
+		}
5141
+		return $this->_cached_fields_non_db_only;
5142
+	}
5143
+
5144
+
5145
+
5146
+	/**
5147
+	 *        cycle though array of attendees and create objects out of each item
5148
+	 *
5149
+	 * @access        private
5150
+	 * @param        array $rows of results of $wpdb->get_results($query,ARRAY_A)
5151
+	 * @return \EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5152
+	 *                           numerically indexed)
5153
+	 * @throws EE_Error
5154
+	 */
5155
+	protected function _create_objects($rows = array())
5156
+	{
5157
+		$array_of_objects = array();
5158
+		if (empty($rows)) {
5159
+			return array();
5160
+		}
5161
+		$count_if_model_has_no_primary_key = 0;
5162
+		$has_primary_key = $this->has_primary_key_field();
5163
+		$primary_key_field = $has_primary_key ? $this->get_primary_key_field() : null;
5164
+		foreach ((array) $rows as $row) {
5165
+			if (empty($row)) {
5166
+				// wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5167
+				return array();
5168
+			}
5169
+			// check if we've already set this object in the results array,
5170
+			// in which case there's no need to process it further (again)
5171
+			if ($has_primary_key) {
5172
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5173
+					$row,
5174
+					$primary_key_field->get_qualified_column(),
5175
+					$primary_key_field->get_table_column()
5176
+				);
5177
+				if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5178
+					continue;
5179
+				}
5180
+			}
5181
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5182
+			if (! $classInstance) {
5183
+				throw new EE_Error(
5184
+					sprintf(
5185
+						esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5186
+						$this->get_this_model_name(),
5187
+						http_build_query($row)
5188
+					)
5189
+				);
5190
+			}
5191
+			// set the timezone on the instantiated objects
5192
+			$classInstance->set_timezone($this->_timezone);
5193
+			// make sure if there is any timezone setting present that we set the timezone for the object
5194
+			$key = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5195
+			$array_of_objects[ $key ] = $classInstance;
5196
+			// also, for all the relations of type BelongsTo, see if we can cache
5197
+			// those related models
5198
+			// (we could do this for other relations too, but if there are conditions
5199
+			// that filtered out some fo the results, then we'd be caching an incomplete set
5200
+			// so it requires a little more thought than just caching them immediately...)
5201
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5202
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5203
+					// check if this model's INFO is present. If so, cache it on the model
5204
+					$other_model = $relation_obj->get_other_model();
5205
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5206
+					// if we managed to make a model object from the results, cache it on the main model object
5207
+					if ($other_model_obj_maybe) {
5208
+						// set timezone on these other model objects if they are present
5209
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5210
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5211
+					}
5212
+				}
5213
+			}
5214
+			// also, if this was a custom select query, let's see if there are any results for the custom select fields
5215
+			// and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5216
+			// the field in the CustomSelects object
5217
+			if ($this->_custom_selections instanceof CustomSelects) {
5218
+				$classInstance->setCustomSelectsValues(
5219
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5220
+				);
5221
+			}
5222
+		}
5223
+		return $array_of_objects;
5224
+	}
5225
+
5226
+
5227
+	/**
5228
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5229
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5230
+	 *
5231
+	 * @param array $db_results_row
5232
+	 * @return array
5233
+	 */
5234
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5235
+	{
5236
+		$results = array();
5237
+		if ($this->_custom_selections instanceof CustomSelects) {
5238
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5239
+				if (isset($db_results_row[ $alias ])) {
5240
+					$results[ $alias ] = $this->convertValueToDataType(
5241
+						$db_results_row[ $alias ],
5242
+						$this->_custom_selections->getDataTypeForAlias($alias)
5243
+					);
5244
+				}
5245
+			}
5246
+		}
5247
+		return $results;
5248
+	}
5249
+
5250
+
5251
+	/**
5252
+	 * This will set the value for the given alias
5253
+	 * @param string $value
5254
+	 * @param string $datatype (one of %d, %s, %f)
5255
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5256
+	 */
5257
+	protected function convertValueToDataType($value, $datatype)
5258
+	{
5259
+		switch ($datatype) {
5260
+			case '%f':
5261
+				return (float) $value;
5262
+			case '%d':
5263
+				return (int) $value;
5264
+			default:
5265
+				return (string) $value;
5266
+		}
5267
+	}
5268
+
5269
+
5270
+	/**
5271
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5272
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5273
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5274
+	 * object (as set in the model_field!).
5275
+	 *
5276
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5277
+	 */
5278
+	public function create_default_object()
5279
+	{
5280
+		$this_model_fields_and_values = array();
5281
+		// setup the row using default values;
5282
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5283
+			$this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5284
+		}
5285
+		$className = $this->_get_class_name();
5286
+		$classInstance = EE_Registry::instance()
5287
+									->load_class($className, array($this_model_fields_and_values), false, false);
5288
+		return $classInstance;
5289
+	}
5290
+
5291
+
5292
+
5293
+	/**
5294
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5295
+	 *                             or an stdClass where each property is the name of a column,
5296
+	 * @return EE_Base_Class
5297
+	 * @throws EE_Error
5298
+	 */
5299
+	public function instantiate_class_from_array_or_object($cols_n_values)
5300
+	{
5301
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5302
+			$cols_n_values = get_object_vars($cols_n_values);
5303
+		}
5304
+		$primary_key = null;
5305
+		// make sure the array only has keys that are fields/columns on this model
5306
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5307
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5308
+			$primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5309
+		}
5310
+		$className = $this->_get_class_name();
5311
+		// check we actually found results that we can use to build our model object
5312
+		// if not, return null
5313
+		if ($this->has_primary_key_field()) {
5314
+			if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5315
+				return null;
5316
+			}
5317
+		} elseif ($this->unique_indexes()) {
5318
+			$first_column = reset($this_model_fields_n_values);
5319
+			if (empty($first_column)) {
5320
+				return null;
5321
+			}
5322
+		}
5323
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5324
+		if ($primary_key) {
5325
+			$classInstance = $this->get_from_entity_map($primary_key);
5326
+			if (! $classInstance) {
5327
+				$classInstance = EE_Registry::instance()
5328
+											->load_class(
5329
+												$className,
5330
+												array($this_model_fields_n_values, $this->_timezone),
5331
+												true,
5332
+												false
5333
+											);
5334
+				// add this new object to the entity map
5335
+				$classInstance = $this->add_to_entity_map($classInstance);
5336
+			}
5337
+		} else {
5338
+			$classInstance = EE_Registry::instance()
5339
+										->load_class(
5340
+											$className,
5341
+											array($this_model_fields_n_values, $this->_timezone),
5342
+											true,
5343
+											false
5344
+										);
5345
+		}
5346
+		return $classInstance;
5347
+	}
5348
+
5349
+
5350
+
5351
+	/**
5352
+	 * Gets the model object from the  entity map if it exists
5353
+	 *
5354
+	 * @param int|string $id the ID of the model object
5355
+	 * @return EE_Base_Class
5356
+	 */
5357
+	public function get_from_entity_map($id)
5358
+	{
5359
+		return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5360
+			? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5361
+	}
5362
+
5363
+
5364
+
5365
+	/**
5366
+	 * add_to_entity_map
5367
+	 * Adds the object to the model's entity mappings
5368
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5369
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5370
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5371
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5372
+	 *        then this method should be called immediately after the update query
5373
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5374
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5375
+	 *
5376
+	 * @param    EE_Base_Class $object
5377
+	 * @throws EE_Error
5378
+	 * @return \EE_Base_Class
5379
+	 */
5380
+	public function add_to_entity_map(EE_Base_Class $object)
5381
+	{
5382
+		$className = $this->_get_class_name();
5383
+		if (! $object instanceof $className) {
5384
+			throw new EE_Error(sprintf(
5385
+				esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5386
+				is_object($object) ? get_class($object) : $object,
5387
+				$className
5388
+			));
5389
+		}
5390
+		/** @var $object EE_Base_Class */
5391
+		if (! $object->ID()) {
5392
+			throw new EE_Error(sprintf(esc_html__(
5393
+				"You tried storing a model object with NO ID in the %s entity mapper.",
5394
+				"event_espresso"
5395
+			), get_class($this)));
5396
+		}
5397
+		// double check it's not already there
5398
+		$classInstance = $this->get_from_entity_map($object->ID());
5399
+		if ($classInstance) {
5400
+			return $classInstance;
5401
+		}
5402
+		$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5403
+		return $object;
5404
+	}
5405
+
5406
+
5407
+
5408
+	/**
5409
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5410
+	 * if no identifier is provided, then the entire entity map is emptied
5411
+	 *
5412
+	 * @param int|string $id the ID of the model object
5413
+	 * @return boolean
5414
+	 */
5415
+	public function clear_entity_map($id = null)
5416
+	{
5417
+		if (empty($id)) {
5418
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ] = array();
5419
+			return true;
5420
+		}
5421
+		if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5422
+			unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5423
+			return true;
5424
+		}
5425
+		return false;
5426
+	}
5427
+
5428
+
5429
+
5430
+	/**
5431
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5432
+	 * Given an array where keys are column (or column alias) names and values,
5433
+	 * returns an array of their corresponding field names and database values
5434
+	 *
5435
+	 * @param array $cols_n_values
5436
+	 * @return array
5437
+	 */
5438
+	public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5439
+	{
5440
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5441
+	}
5442
+
5443
+
5444
+
5445
+	/**
5446
+	 * _deduce_fields_n_values_from_cols_n_values
5447
+	 * Given an array where keys are column (or column alias) names and values,
5448
+	 * returns an array of their corresponding field names and database values
5449
+	 *
5450
+	 * @param string $cols_n_values
5451
+	 * @return array
5452
+	 */
5453
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5454
+	{
5455
+		$this_model_fields_n_values = array();
5456
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5457
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5458
+				$cols_n_values,
5459
+				$table_obj->get_fully_qualified_pk_column(),
5460
+				$table_obj->get_pk_column()
5461
+			);
5462
+			// there is a primary key on this table and its not set. Use defaults for all its columns
5463
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5464
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5465
+					if (! $field_obj->is_db_only_field()) {
5466
+						// prepare field as if its coming from db
5467
+						$prepared_value = $field_obj->prepare_for_set($field_obj->get_default_value());
5468
+						$this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5469
+					}
5470
+				}
5471
+			} else {
5472
+				// the table's rows existed. Use their values
5473
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5474
+					if (! $field_obj->is_db_only_field()) {
5475
+						$this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5476
+							$cols_n_values,
5477
+							$field_obj->get_qualified_column(),
5478
+							$field_obj->get_table_column()
5479
+						);
5480
+					}
5481
+				}
5482
+			}
5483
+		}
5484
+		return $this_model_fields_n_values;
5485
+	}
5486
+
5487
+
5488
+	/**
5489
+	 * @param $cols_n_values
5490
+	 * @param $qualified_column
5491
+	 * @param $regular_column
5492
+	 * @return null
5493
+	 * @throws EE_Error
5494
+	 * @throws ReflectionException
5495
+	 */
5496
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5497
+	{
5498
+		$value = null;
5499
+		// ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5500
+		// does the field on the model relate to this column retrieved from the db?
5501
+		// or is it a db-only field? (not relating to the model)
5502
+		if (isset($cols_n_values[ $qualified_column ])) {
5503
+			$value = $cols_n_values[ $qualified_column ];
5504
+		} elseif (isset($cols_n_values[ $regular_column ])) {
5505
+			$value = $cols_n_values[ $regular_column ];
5506
+		} elseif (! empty($this->foreign_key_aliases)) {
5507
+			// no PK?  ok check if there is a foreign key alias set for this table
5508
+			// then check if that alias exists in the incoming data
5509
+			// AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5510
+			foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5511
+				if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5512
+					$value = $cols_n_values[ $FK_alias ];
5513
+					[$pk_class] = explode('.', $PK_column);
5514
+					$pk_model_name = "EEM_{$pk_class}";
5515
+					/** @var EEM_Base $pk_model */
5516
+					$pk_model = EE_Registry::instance()->load_model($pk_model_name);
5517
+					if ($pk_model instanceof EEM_Base) {
5518
+						// make sure object is pulled from db and added to entity map
5519
+						$pk_model->get_one_by_ID($value);
5520
+					}
5521
+					break;
5522
+				}
5523
+			}
5524
+		}
5525
+		return $value;
5526
+	}
5527
+
5528
+
5529
+	/**
5530
+	 * refresh_entity_map_from_db
5531
+	 * Makes sure the model object in the entity map at $id assumes the values
5532
+	 * of the database (opposite of EE_base_Class::save())
5533
+	 *
5534
+	 * @param int|string $id
5535
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5536
+	 * @throws EE_Error
5537
+	 * @throws ReflectionException
5538
+	 */
5539
+	public function refresh_entity_map_from_db($id)
5540
+	{
5541
+		$obj_in_map = $this->get_from_entity_map($id);
5542
+		if ($obj_in_map) {
5543
+			$wpdb_results = $this->_get_all_wpdb_results(
5544
+				array(array($this->get_primary_key_field()->get_name() => $id), 'limit' => 1)
5545
+			);
5546
+			if ($wpdb_results && is_array($wpdb_results)) {
5547
+				$one_row = reset($wpdb_results);
5548
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5549
+					$obj_in_map->set_from_db($field_name, $db_value);
5550
+				}
5551
+				// clear the cache of related model objects
5552
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5553
+					$obj_in_map->clear_cache($relation_name, null, true);
5554
+				}
5555
+			}
5556
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5557
+			return $obj_in_map;
5558
+		}
5559
+		return $this->get_one_by_ID($id);
5560
+	}
5561
+
5562
+
5563
+
5564
+	/**
5565
+	 * refresh_entity_map_with
5566
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5567
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5568
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5569
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5570
+	 *
5571
+	 * @param int|string    $id
5572
+	 * @param EE_Base_Class $replacing_model_obj
5573
+	 * @return \EE_Base_Class
5574
+	 * @throws EE_Error
5575
+	 */
5576
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5577
+	{
5578
+		$obj_in_map = $this->get_from_entity_map($id);
5579
+		if ($obj_in_map) {
5580
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5581
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5582
+					$obj_in_map->set($field_name, $value);
5583
+				}
5584
+				// make the model object in the entity map's cache match the $replacing_model_obj
5585
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5586
+					$obj_in_map->clear_cache($relation_name, null, true);
5587
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5588
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5589
+					}
5590
+				}
5591
+			}
5592
+			return $obj_in_map;
5593
+		}
5594
+		$this->add_to_entity_map($replacing_model_obj);
5595
+		return $replacing_model_obj;
5596
+	}
5597
+
5598
+
5599
+
5600
+	/**
5601
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5602
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5603
+	 * require_once($this->_getClassName().".class.php");
5604
+	 *
5605
+	 * @return string
5606
+	 */
5607
+	private function _get_class_name()
5608
+	{
5609
+		return "EE_" . $this->get_this_model_name();
5610
+	}
5611
+
5612
+
5613
+
5614
+	/**
5615
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5616
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5617
+	 * it would be 'Events'.
5618
+	 *
5619
+	 * @param int|float|null $quantity
5620
+	 * @return string
5621
+	 */
5622
+	public function item_name($quantity = 1): string
5623
+	{
5624
+		$quantity = floor($quantity);
5625
+		return apply_filters(
5626
+			'FHEE__EEM_Base__item_name__plural_or_singular',
5627
+			$quantity > 1 ? $this->plural_item : $this->singular_item,
5628
+			$quantity,
5629
+			$this->plural_item,
5630
+			$this->singular_item
5631
+		);
5632
+	}
5633
+
5634
+
5635
+
5636
+	/**
5637
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5638
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5639
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5640
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5641
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5642
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5643
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5644
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5645
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5646
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5647
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5648
+	 *        return $previousReturnValue.$returnString;
5649
+	 * }
5650
+	 * require('EEM_Answer.model.php');
5651
+	 * echo EEM_Answer::instance()->my_callback('monkeys',100);
5652
+	 * // will output "you called my_callback! and passed args:monkeys,100"
5653
+	 *
5654
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5655
+	 * @param array  $args       array of original arguments passed to the function
5656
+	 * @throws EE_Error
5657
+	 * @return mixed whatever the plugin which calls add_filter decides
5658
+	 */
5659
+	public function __call($methodName, $args)
5660
+	{
5661
+		$className = get_class($this);
5662
+		$tagName = "FHEE__{$className}__{$methodName}";
5663
+		if (! has_filter($tagName)) {
5664
+			throw new EE_Error(
5665
+				sprintf(
5666
+					esc_html__(
5667
+						'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 );',
5668
+						'event_espresso'
5669
+					),
5670
+					$methodName,
5671
+					$className,
5672
+					$tagName,
5673
+					'<br />'
5674
+				)
5675
+			);
5676
+		}
5677
+		return apply_filters($tagName, null, $this, $args);
5678
+	}
5679
+
5680
+
5681
+
5682
+	/**
5683
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5684
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5685
+	 *
5686
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5687
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5688
+	 *                                                       the object's class name
5689
+	 *                                                       or object's ID
5690
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5691
+	 *                                                       exists in the database. If it does not, we add it
5692
+	 * @throws EE_Error
5693
+	 * @return EE_Base_Class
5694
+	 */
5695
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5696
+	{
5697
+		$className = $this->_get_class_name();
5698
+		if ($base_class_obj_or_id instanceof $className) {
5699
+			$model_object = $base_class_obj_or_id;
5700
+		} else {
5701
+			$primary_key_field = $this->get_primary_key_field();
5702
+			if (
5703
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5704
+				&& (
5705
+					is_int($base_class_obj_or_id)
5706
+					|| is_string($base_class_obj_or_id)
5707
+				)
5708
+			) {
5709
+				// assume it's an ID.
5710
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5711
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5712
+			} elseif (
5713
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5714
+				&& is_string($base_class_obj_or_id)
5715
+			) {
5716
+				// assume its a string representation of the object
5717
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5718
+			} else {
5719
+				throw new EE_Error(
5720
+					sprintf(
5721
+						esc_html__(
5722
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5723
+							'event_espresso'
5724
+						),
5725
+						$base_class_obj_or_id,
5726
+						$this->_get_class_name(),
5727
+						print_r($base_class_obj_or_id, true)
5728
+					)
5729
+				);
5730
+			}
5731
+		}
5732
+		if ($ensure_is_in_db && $model_object->ID() !== null) {
5733
+			$model_object->save();
5734
+		}
5735
+		return $model_object;
5736
+	}
5737
+
5738
+
5739
+
5740
+	/**
5741
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5742
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5743
+	 * returns it ID.
5744
+	 *
5745
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5746
+	 * @return int|string depending on the type of this model object's ID
5747
+	 * @throws EE_Error
5748
+	 */
5749
+	public function ensure_is_ID($base_class_obj_or_id)
5750
+	{
5751
+		$className = $this->_get_class_name();
5752
+		if ($base_class_obj_or_id instanceof $className) {
5753
+			/** @var $base_class_obj_or_id EE_Base_Class */
5754
+			$id = $base_class_obj_or_id->ID();
5755
+		} elseif (is_int($base_class_obj_or_id)) {
5756
+			// assume it's an ID
5757
+			$id = $base_class_obj_or_id;
5758
+		} elseif (is_string($base_class_obj_or_id)) {
5759
+			// assume its a string representation of the object
5760
+			$id = $base_class_obj_or_id;
5761
+		} else {
5762
+			throw new EE_Error(sprintf(
5763
+				esc_html__(
5764
+					"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5765
+					'event_espresso'
5766
+				),
5767
+				$base_class_obj_or_id,
5768
+				$this->_get_class_name(),
5769
+				print_r($base_class_obj_or_id, true)
5770
+			));
5771
+		}
5772
+		return $id;
5773
+	}
5774
+
5775
+
5776
+
5777
+	/**
5778
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5779
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5780
+	 * been sanitized and converted into the appropriate domain.
5781
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5782
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5783
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5784
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5785
+	 * $EVT = EEM_Event::instance(); $old_setting =
5786
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5787
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5788
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5789
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5790
+	 *
5791
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5792
+	 * @return void
5793
+	 */
5794
+	public function assume_values_already_prepared_by_model_object(
5795
+		$values_already_prepared = self::not_prepared_by_model_object
5796
+	) {
5797
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5798
+	}
5799
+
5800
+
5801
+
5802
+	/**
5803
+	 * Read comments for assume_values_already_prepared_by_model_object()
5804
+	 *
5805
+	 * @return int
5806
+	 */
5807
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5808
+	{
5809
+		return $this->_values_already_prepared_by_model_object;
5810
+	}
5811
+
5812
+
5813
+
5814
+	/**
5815
+	 * Gets all the indexes on this model
5816
+	 *
5817
+	 * @return EE_Index[]
5818
+	 */
5819
+	public function indexes()
5820
+	{
5821
+		return $this->_indexes;
5822
+	}
5823
+
5824
+
5825
+
5826
+	/**
5827
+	 * Gets all the Unique Indexes on this model
5828
+	 *
5829
+	 * @return EE_Unique_Index[]
5830
+	 */
5831
+	public function unique_indexes()
5832
+	{
5833
+		$unique_indexes = array();
5834
+		foreach ($this->_indexes as $name => $index) {
5835
+			if ($index instanceof EE_Unique_Index) {
5836
+				$unique_indexes [ $name ] = $index;
5837
+			}
5838
+		}
5839
+		return $unique_indexes;
5840
+	}
5841
+
5842
+
5843
+
5844
+	/**
5845
+	 * Gets all the fields which, when combined, make the primary key.
5846
+	 * This is usually just an array with 1 element (the primary key), but in cases
5847
+	 * where there is no primary key, it's a combination of fields as defined
5848
+	 * on a primary index
5849
+	 *
5850
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5851
+	 * @throws EE_Error
5852
+	 */
5853
+	public function get_combined_primary_key_fields()
5854
+	{
5855
+		foreach ($this->indexes() as $index) {
5856
+			if ($index instanceof EE_Primary_Key_Index) {
5857
+				return $index->fields();
5858
+			}
5859
+		}
5860
+		return array($this->primary_key_name() => $this->get_primary_key_field());
5861
+	}
5862
+
5863
+
5864
+
5865
+	/**
5866
+	 * Used to build a primary key string (when the model has no primary key),
5867
+	 * which can be used a unique string to identify this model object.
5868
+	 *
5869
+	 * @param array $fields_n_values keys are field names, values are their values.
5870
+	 *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5871
+	 *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5872
+	 *                               before passing it to this function (that will convert it from columns-n-values
5873
+	 *                               to field-names-n-values).
5874
+	 * @return string
5875
+	 * @throws EE_Error
5876
+	 */
5877
+	public function get_index_primary_key_string($fields_n_values)
5878
+	{
5879
+		$cols_n_values_for_primary_key_index = array_intersect_key(
5880
+			$fields_n_values,
5881
+			$this->get_combined_primary_key_fields()
5882
+		);
5883
+		return http_build_query($cols_n_values_for_primary_key_index);
5884
+	}
5885
+
5886
+
5887
+
5888
+	/**
5889
+	 * Gets the field values from the primary key string
5890
+	 *
5891
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5892
+	 * @param string $index_primary_key_string
5893
+	 * @return null|array
5894
+	 * @throws EE_Error
5895
+	 */
5896
+	public function parse_index_primary_key_string($index_primary_key_string)
5897
+	{
5898
+		$key_fields = $this->get_combined_primary_key_fields();
5899
+		// check all of them are in the $id
5900
+		$key_vals_in_combined_pk = array();
5901
+		parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5902
+		foreach ($key_fields as $key_field_name => $field_obj) {
5903
+			if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
5904
+				return null;
5905
+			}
5906
+		}
5907
+		return $key_vals_in_combined_pk;
5908
+	}
5909
+
5910
+
5911
+
5912
+	/**
5913
+	 * verifies that an array of key-value pairs for model fields has a key
5914
+	 * for each field comprising the primary key index
5915
+	 *
5916
+	 * @param array $key_vals
5917
+	 * @return boolean
5918
+	 * @throws EE_Error
5919
+	 */
5920
+	public function has_all_combined_primary_key_fields($key_vals)
5921
+	{
5922
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5923
+		foreach ($keys_it_should_have as $key) {
5924
+			if (! isset($key_vals[ $key ])) {
5925
+				return false;
5926
+			}
5927
+		}
5928
+		return true;
5929
+	}
5930
+
5931
+
5932
+
5933
+	/**
5934
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5935
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
5936
+	 *
5937
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5938
+	 * @param array               $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5939
+	 * @throws EE_Error
5940
+	 * @return \EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5941
+	 *                                                              indexed)
5942
+	 */
5943
+	public function get_all_copies($model_object_or_attributes_array, $query_params = array())
5944
+	{
5945
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5946
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
5947
+		} elseif (is_array($model_object_or_attributes_array)) {
5948
+			$attributes_array = $model_object_or_attributes_array;
5949
+		} else {
5950
+			throw new EE_Error(sprintf(esc_html__(
5951
+				"get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5952
+				"event_espresso"
5953
+			), $model_object_or_attributes_array));
5954
+		}
5955
+		// even copies obviously won't have the same ID, so remove the primary key
5956
+		// from the WHERE conditions for finding copies (if there is a primary key, of course)
5957
+		if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5958
+			unset($attributes_array[ $this->primary_key_name() ]);
5959
+		}
5960
+		if (isset($query_params[0])) {
5961
+			$query_params[0] = array_merge($attributes_array, $query_params);
5962
+		} else {
5963
+			$query_params[0] = $attributes_array;
5964
+		}
5965
+		return $this->get_all($query_params);
5966
+	}
5967
+
5968
+
5969
+
5970
+	/**
5971
+	 * Gets the first copy we find. See get_all_copies for more details
5972
+	 *
5973
+	 * @param       mixed EE_Base_Class | array        $model_object_or_attributes_array
5974
+	 * @param array $query_params
5975
+	 * @return EE_Base_Class
5976
+	 * @throws EE_Error
5977
+	 */
5978
+	public function get_one_copy($model_object_or_attributes_array, $query_params = array())
5979
+	{
5980
+		if (! is_array($query_params)) {
5981
+			EE_Error::doing_it_wrong(
5982
+				'EEM_Base::get_one_copy',
5983
+				sprintf(
5984
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5985
+					gettype($query_params)
5986
+				),
5987
+				'4.6.0'
5988
+			);
5989
+			$query_params = array();
5990
+		}
5991
+		$query_params['limit'] = 1;
5992
+		$copies = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5993
+		if (is_array($copies)) {
5994
+			return array_shift($copies);
5995
+		}
5996
+		return null;
5997
+	}
5998
+
5999
+
6000
+
6001
+	/**
6002
+	 * Updates the item with the specified id. Ignores default query parameters because
6003
+	 * we have specified the ID, and its assumed we KNOW what we're doing
6004
+	 *
6005
+	 * @param array      $fields_n_values keys are field names, values are their new values
6006
+	 * @param int|string $id              the value of the primary key to update
6007
+	 * @return int number of rows updated
6008
+	 * @throws EE_Error
6009
+	 */
6010
+	public function update_by_ID($fields_n_values, $id)
6011
+	{
6012
+		$query_params = array(
6013
+			0                          => array($this->get_primary_key_field()->get_name() => $id),
6014
+			'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6015
+		);
6016
+		return $this->update($fields_n_values, $query_params);
6017
+	}
6018
+
6019
+
6020
+
6021
+	/**
6022
+	 * Changes an operator which was supplied to the models into one usable in SQL
6023
+	 *
6024
+	 * @param string $operator_supplied
6025
+	 * @return string an operator which can be used in SQL
6026
+	 * @throws EE_Error
6027
+	 */
6028
+	private function _prepare_operator_for_sql($operator_supplied)
6029
+	{
6030
+		$sql_operator = isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6031
+			: null;
6032
+		if ($sql_operator) {
6033
+			return $sql_operator;
6034
+		}
6035
+		throw new EE_Error(
6036
+			sprintf(
6037
+				esc_html__(
6038
+					"The operator '%s' is not in the list of valid operators: %s",
6039
+					"event_espresso"
6040
+				),
6041
+				$operator_supplied,
6042
+				implode(",", array_keys($this->_valid_operators))
6043
+			)
6044
+		);
6045
+	}
6046
+
6047
+
6048
+
6049
+	/**
6050
+	 * Gets the valid operators
6051
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6052
+	 */
6053
+	public function valid_operators()
6054
+	{
6055
+		return $this->_valid_operators;
6056
+	}
6057
+
6058
+
6059
+
6060
+	/**
6061
+	 * Gets the between-style operators (take 2 arguments).
6062
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6063
+	 */
6064
+	public function valid_between_style_operators()
6065
+	{
6066
+		return array_intersect(
6067
+			$this->valid_operators(),
6068
+			$this->_between_style_operators
6069
+		);
6070
+	}
6071
+
6072
+	/**
6073
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6074
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6075
+	 */
6076
+	public function valid_like_style_operators()
6077
+	{
6078
+		return array_intersect(
6079
+			$this->valid_operators(),
6080
+			$this->_like_style_operators
6081
+		);
6082
+	}
6083
+
6084
+	/**
6085
+	 * Gets the "in"-style operators
6086
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6087
+	 */
6088
+	public function valid_in_style_operators()
6089
+	{
6090
+		return array_intersect(
6091
+			$this->valid_operators(),
6092
+			$this->_in_style_operators
6093
+		);
6094
+	}
6095
+
6096
+	/**
6097
+	 * Gets the "null"-style operators (accept no arguments)
6098
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6099
+	 */
6100
+	public function valid_null_style_operators()
6101
+	{
6102
+		return array_intersect(
6103
+			$this->valid_operators(),
6104
+			$this->_null_style_operators
6105
+		);
6106
+	}
6107
+
6108
+	/**
6109
+	 * Gets an array where keys are the primary keys and values are their 'names'
6110
+	 * (as determined by the model object's name() function, which is often overridden)
6111
+	 *
6112
+	 * @param array $query_params like get_all's
6113
+	 * @return string[]
6114
+	 * @throws EE_Error
6115
+	 */
6116
+	public function get_all_names($query_params = array())
6117
+	{
6118
+		$objs = $this->get_all($query_params);
6119
+		$names = array();
6120
+		foreach ($objs as $obj) {
6121
+			$names[ $obj->ID() ] = $obj->name();
6122
+		}
6123
+		return $names;
6124
+	}
6125
+
6126
+
6127
+
6128
+	/**
6129
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
6130
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6131
+	 * this is duplicated effort and reduces efficiency) you would be better to use
6132
+	 * array_keys() on $model_objects.
6133
+	 *
6134
+	 * @param \EE_Base_Class[] $model_objects
6135
+	 * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6136
+	 *                                               in the returned array
6137
+	 * @return array
6138
+	 * @throws EE_Error
6139
+	 */
6140
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
6141
+	{
6142
+		if (! $this->has_primary_key_field()) {
6143
+			if (WP_DEBUG) {
6144
+				EE_Error::add_error(
6145
+					esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6146
+					__FILE__,
6147
+					__FUNCTION__,
6148
+					__LINE__
6149
+				);
6150
+			}
6151
+		}
6152
+		$IDs = array();
6153
+		foreach ($model_objects as $model_object) {
6154
+			$id = $model_object->ID();
6155
+			if (! $id) {
6156
+				if ($filter_out_empty_ids) {
6157
+					continue;
6158
+				}
6159
+				if (WP_DEBUG) {
6160
+					EE_Error::add_error(
6161
+						esc_html__(
6162
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6163
+							'event_espresso'
6164
+						),
6165
+						__FILE__,
6166
+						__FUNCTION__,
6167
+						__LINE__
6168
+					);
6169
+				}
6170
+			}
6171
+			$IDs[] = $id;
6172
+		}
6173
+		return $IDs;
6174
+	}
6175
+
6176
+
6177
+
6178
+	/**
6179
+	 * Returns the string used in capabilities relating to this model. If there
6180
+	 * are no capabilities that relate to this model returns false
6181
+	 *
6182
+	 * @return string|false
6183
+	 */
6184
+	public function cap_slug()
6185
+	{
6186
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6187
+	}
6188
+
6189
+
6190
+
6191
+	/**
6192
+	 * Returns the capability-restrictions array (@see EEM_Base::_cap_restrictions).
6193
+	 * If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6194
+	 * only returns the cap restrictions array in that context (ie, the array
6195
+	 * at that key)
6196
+	 *
6197
+	 * @param string $context
6198
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6199
+	 * @throws EE_Error
6200
+	 */
6201
+	public function cap_restrictions($context = EEM_Base::caps_read)
6202
+	{
6203
+		EEM_Base::verify_is_valid_cap_context($context);
6204
+		// check if we ought to run the restriction generator first
6205
+		if (
6206
+			isset($this->_cap_restriction_generators[ $context ])
6207
+			&& $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6208
+			&& ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6209
+		) {
6210
+			$this->_cap_restrictions[ $context ] = array_merge(
6211
+				$this->_cap_restrictions[ $context ],
6212
+				$this->_cap_restriction_generators[ $context ]->generate_restrictions()
6213
+			);
6214
+		}
6215
+		// and make sure we've finalized the construction of each restriction
6216
+		foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6217
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6218
+				$where_conditions_obj->_finalize_construct($this);
6219
+			}
6220
+		}
6221
+		return $this->_cap_restrictions[ $context ];
6222
+	}
6223
+
6224
+
6225
+
6226
+	/**
6227
+	 * Indicating whether or not this model thinks its a wp core model
6228
+	 *
6229
+	 * @return boolean
6230
+	 */
6231
+	public function is_wp_core_model()
6232
+	{
6233
+		return $this->_wp_core_model;
6234
+	}
6235
+
6236
+
6237
+
6238
+	/**
6239
+	 * Gets all the caps that are missing which impose a restriction on
6240
+	 * queries made in this context
6241
+	 *
6242
+	 * @param string $context one of EEM_Base::caps_ constants
6243
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6244
+	 * @throws EE_Error
6245
+	 */
6246
+	public function caps_missing($context = EEM_Base::caps_read)
6247
+	{
6248
+		$missing_caps = array();
6249
+		$cap_restrictions = $this->cap_restrictions($context);
6250
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6251
+			if (
6252
+				! EE_Capabilities::instance()
6253
+								 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6254
+			) {
6255
+				$missing_caps[ $cap ] = $restriction_if_no_cap;
6256
+			}
6257
+		}
6258
+		return $missing_caps;
6259
+	}
6260
+
6261
+
6262
+
6263
+	/**
6264
+	 * Gets the mapping from capability contexts to action strings used in capability names
6265
+	 *
6266
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6267
+	 * one of 'read', 'edit', or 'delete'
6268
+	 */
6269
+	public function cap_contexts_to_cap_action_map()
6270
+	{
6271
+		return apply_filters(
6272
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6273
+			$this->_cap_contexts_to_cap_action_map,
6274
+			$this
6275
+		);
6276
+	}
6277
+
6278
+
6279
+
6280
+	/**
6281
+	 * Gets the action string for the specified capability context
6282
+	 *
6283
+	 * @param string $context
6284
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6285
+	 * @throws EE_Error
6286
+	 */
6287
+	public function cap_action_for_context($context)
6288
+	{
6289
+		$mapping = $this->cap_contexts_to_cap_action_map();
6290
+		if (isset($mapping[ $context ])) {
6291
+			return $mapping[ $context ];
6292
+		}
6293
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6294
+			return $action;
6295
+		}
6296
+		throw new EE_Error(
6297
+			sprintf(
6298
+				esc_html__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6299
+				$context,
6300
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6301
+			)
6302
+		);
6303
+	}
6304
+
6305
+
6306
+
6307
+	/**
6308
+	 * Returns all the capability contexts which are valid when querying models
6309
+	 *
6310
+	 * @return array
6311
+	 */
6312
+	public static function valid_cap_contexts()
6313
+	{
6314
+		return apply_filters('FHEE__EEM_Base__valid_cap_contexts', array(
6315
+			self::caps_read,
6316
+			self::caps_read_admin,
6317
+			self::caps_edit,
6318
+			self::caps_delete,
6319
+		));
6320
+	}
6321
+
6322
+
6323
+
6324
+	/**
6325
+	 * Returns all valid options for 'default_where_conditions'
6326
+	 *
6327
+	 * @return array
6328
+	 */
6329
+	public static function valid_default_where_conditions()
6330
+	{
6331
+		return array(
6332
+			EEM_Base::default_where_conditions_all,
6333
+			EEM_Base::default_where_conditions_this_only,
6334
+			EEM_Base::default_where_conditions_others_only,
6335
+			EEM_Base::default_where_conditions_minimum_all,
6336
+			EEM_Base::default_where_conditions_minimum_others,
6337
+			EEM_Base::default_where_conditions_none
6338
+		);
6339
+	}
6340
+
6341
+	// public static function default_where_conditions_full
6342
+	/**
6343
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6344
+	 *
6345
+	 * @param string $context
6346
+	 * @return bool
6347
+	 * @throws EE_Error
6348
+	 */
6349
+	public static function verify_is_valid_cap_context($context)
6350
+	{
6351
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6352
+		if (in_array($context, $valid_cap_contexts)) {
6353
+			return true;
6354
+		}
6355
+		throw new EE_Error(
6356
+			sprintf(
6357
+				esc_html__(
6358
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6359
+					'event_espresso'
6360
+				),
6361
+				$context,
6362
+				'EEM_Base',
6363
+				implode(',', $valid_cap_contexts)
6364
+			)
6365
+		);
6366
+	}
6367
+
6368
+
6369
+
6370
+	/**
6371
+	 * Clears all the models field caches. This is only useful when a sub-class
6372
+	 * might have added a field or something and these caches might be invalidated
6373
+	 */
6374
+	protected function _invalidate_field_caches()
6375
+	{
6376
+		$this->_cache_foreign_key_to_fields = array();
6377
+		$this->_cached_fields = null;
6378
+		$this->_cached_fields_non_db_only = null;
6379
+	}
6380
+
6381
+
6382
+
6383
+	/**
6384
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6385
+	 * (eg "and", "or", "not").
6386
+	 *
6387
+	 * @return array
6388
+	 */
6389
+	public function logic_query_param_keys()
6390
+	{
6391
+		return $this->_logic_query_param_keys;
6392
+	}
6393
+
6394
+
6395
+
6396
+	/**
6397
+	 * Determines whether or not the where query param array key is for a logic query param.
6398
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6399
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6400
+	 *
6401
+	 * @param $query_param_key
6402
+	 * @return bool
6403
+	 */
6404
+	public function is_logic_query_param_key($query_param_key)
6405
+	{
6406
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6407
+			if (
6408
+				$query_param_key === $logic_query_param_key
6409
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6410
+			) {
6411
+				return true;
6412
+			}
6413
+		}
6414
+		return false;
6415
+	}
6416
+
6417
+	/**
6418
+	 * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6419
+	 * @since 4.9.74.p
6420
+	 * @return boolean
6421
+	 */
6422
+	public function hasPassword()
6423
+	{
6424
+		// if we don't yet know if there's a password field, find out and remember it for next time.
6425
+		if ($this->has_password_field === null) {
6426
+			$password_field = $this->getPasswordField();
6427
+			$this->has_password_field = $password_field instanceof EE_Password_Field ? true : false;
6428
+		}
6429
+		return $this->has_password_field;
6430
+	}
6431
+
6432
+	/**
6433
+	 * Returns the password field on this model, if there is one
6434
+	 * @since 4.9.74.p
6435
+	 * @return EE_Password_Field|null
6436
+	 */
6437
+	public function getPasswordField()
6438
+	{
6439
+		// if we definetely already know there is a password field or not (because has_password_field is true or false)
6440
+		// there's no need to search for it. If we don't know yet, then find out
6441
+		if ($this->has_password_field === null && $this->password_field === null) {
6442
+			$this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6443
+		}
6444
+		// don't bother setting has_password_field because that's hasPassword()'s job.
6445
+		return $this->password_field;
6446
+	}
6447
+
6448
+
6449
+	/**
6450
+	 * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6451
+	 * @since 4.9.74.p
6452
+	 * @return EE_Model_Field_Base[]
6453
+	 * @throws EE_Error
6454
+	 */
6455
+	public function getPasswordProtectedFields()
6456
+	{
6457
+		$password_field = $this->getPasswordField();
6458
+		$fields = array();
6459
+		if ($password_field instanceof EE_Password_Field) {
6460
+			$field_names = $password_field->protectedFields();
6461
+			foreach ($field_names as $field_name) {
6462
+				$fields[ $field_name ] = $this->field_settings_for($field_name);
6463
+			}
6464
+		}
6465
+		return $fields;
6466
+	}
6467
+
6468
+
6469
+	/**
6470
+	 * Checks if the current user can perform the requested action on this model
6471
+	 * @since 4.9.74.p
6472
+	 * @param string $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6473
+	 * @param EE_Base_Class|array $model_obj_or_fields_n_values
6474
+	 * @return bool
6475
+	 * @throws EE_Error
6476
+	 * @throws InvalidArgumentException
6477
+	 * @throws InvalidDataTypeException
6478
+	 * @throws InvalidInterfaceException
6479
+	 * @throws ReflectionException
6480
+	 * @throws UnexpectedEntityException
6481
+	 */
6482
+	public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6483
+	{
6484
+		if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6485
+			$model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6486
+		}
6487
+		if (!is_array($model_obj_or_fields_n_values)) {
6488
+			throw new UnexpectedEntityException(
6489
+				$model_obj_or_fields_n_values,
6490
+				'EE_Base_Class',
6491
+				sprintf(
6492
+					esc_html__('%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.', 'event_espresso'),
6493
+					__FUNCTION__
6494
+				)
6495
+			);
6496
+		}
6497
+		return $this->exists(
6498
+			$this->alter_query_params_to_restrict_by_ID(
6499
+				$this->get_index_primary_key_string($model_obj_or_fields_n_values),
6500
+				array(
6501
+					'default_where_conditions' => 'none',
6502
+					'caps'                     => $cap_to_check,
6503
+				)
6504
+			)
6505
+		);
6506
+	}
6507
+
6508
+	/**
6509
+	 * Returns the query param where conditions key to the password affecting this model.
6510
+	 * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6511
+	 * @since 4.9.74.p
6512
+	 * @return null|string
6513
+	 * @throws EE_Error
6514
+	 * @throws InvalidArgumentException
6515
+	 * @throws InvalidDataTypeException
6516
+	 * @throws InvalidInterfaceException
6517
+	 * @throws ModelConfigurationException
6518
+	 * @throws ReflectionException
6519
+	 */
6520
+	public function modelChainAndPassword()
6521
+	{
6522
+		if ($this->model_chain_to_password === null) {
6523
+			throw new ModelConfigurationException(
6524
+				$this,
6525
+				esc_html_x(
6526
+				// @codingStandardsIgnoreStart
6527
+					'Cannot exclude protected data because the model has not specified which model has the password.',
6528
+					// @codingStandardsIgnoreEnd
6529
+					'1: model name',
6530
+					'event_espresso'
6531
+				)
6532
+			);
6533
+		}
6534
+		if ($this->model_chain_to_password === '') {
6535
+			$model_with_password = $this;
6536
+		} else {
6537
+			if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6538
+				$last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6539
+			} else {
6540
+				$last_model_in_chain = $this->model_chain_to_password;
6541
+			}
6542
+			$model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6543
+		}
6544
+
6545
+		$password_field = $model_with_password->getPasswordField();
6546
+		if ($password_field instanceof EE_Password_Field) {
6547
+			$password_field_name = $password_field->get_name();
6548
+		} else {
6549
+			throw new ModelConfigurationException(
6550
+				$this,
6551
+				sprintf(
6552
+					esc_html_x(
6553
+						'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"',
6554
+						'1: model name, 2: special string',
6555
+						'event_espresso'
6556
+					),
6557
+					$model_with_password->get_this_model_name(),
6558
+					$this->model_chain_to_password
6559
+				)
6560
+			);
6561
+		}
6562
+		return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6563
+	}
6564
+
6565
+	/**
6566
+	 * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6567
+	 * or if this model itself has a password affecting access to some of its other fields.
6568
+	 * @since 4.9.74.p
6569
+	 * @return boolean
6570
+	 */
6571
+	public function restrictedByRelatedModelPassword()
6572
+	{
6573
+		return $this->model_chain_to_password !== null;
6574
+	}
6575 6575
 }
Please login to merge, or discard this patch.
core/EE_Front_Controller.core.php 1 patch
Indentation   +509 added lines, -509 removed lines patch added patch discarded remove patch
@@ -17,513 +17,513 @@
 block discarded – undo
17 17
  */
18 18
 final class EE_Front_Controller
19 19
 {
20
-    /**
21
-     * @var string
22
-     */
23
-    private $_template_path;
24
-
25
-    /**
26
-     * @var string
27
-     */
28
-    private $_template;
29
-
30
-    /**
31
-     * @type EE_Registry
32
-     */
33
-    protected $Registry;
34
-
35
-    /**
36
-     * @type EE_Request_Handler
37
-     */
38
-    protected $Request_Handler;
39
-
40
-    /**
41
-     * @type EE_Module_Request_Router
42
-     */
43
-    protected $Module_Request_Router;
44
-
45
-    /**
46
-     * @type CurrentPage
47
-     */
48
-    protected $current_page;
49
-
50
-
51
-    /**
52
-     *    class constructor
53
-     *    should fire after shortcode, module, addon, or other plugin's default priority init phases have run
54
-     *
55
-     * @access    public
56
-     * @param EE_Registry              $Registry
57
-     * @param CurrentPage              $EspressoPage
58
-     * @param EE_Module_Request_Router $Module_Request_Router
59
-     */
60
-    public function __construct(
61
-        EE_Registry $Registry,
62
-        CurrentPage $EspressoPage,
63
-        EE_Module_Request_Router $Module_Request_Router
64
-    ) {
65
-        $this->Registry              = $Registry;
66
-        $this->current_page          = $EspressoPage;
67
-        $this->Module_Request_Router = $Module_Request_Router;
68
-        // load other resources and begin to actually run shortcodes and modules
69
-        // analyse the incoming WP request
70
-        add_action('parse_request', array($this, 'get_request'), 1, 1);
71
-        // process request with module factory
72
-        add_action('pre_get_posts', array($this, 'pre_get_posts'), 10, 1);
73
-        // before headers sent
74
-        add_action('wp', array($this, 'wp'), 5);
75
-        // primarily used to process any content shortcodes
76
-        add_action('template_redirect', array($this, 'templateRedirect'), 999);
77
-        // header
78
-        add_action('wp_head', array($this, 'header_meta_tag'), 5);
79
-        add_action('wp_print_scripts', array($this, 'wp_print_scripts'), 10);
80
-        add_filter('template_include', array($this, 'template_include'), 1);
81
-        // display errors
82
-        add_action('loop_start', array($this, 'display_errors'), 2);
83
-        // the content
84
-        // add_filter( 'the_content', array( $this, 'the_content' ), 5, 1 );
85
-        // exclude our private cpt comments
86
-        add_filter('comments_clauses', array($this, 'filter_wp_comments'), 10, 1);
87
-        // make sure any ajax requests will respect the url schema when requests are made against admin-ajax.php (http:// or https://)
88
-        add_filter('admin_url', array($this, 'maybe_force_admin_ajax_ssl'), 200, 1);
89
-        // action hook EE
90
-        do_action('AHEE__EE_Front_Controller__construct__done', $this);
91
-    }
92
-
93
-
94
-    /**
95
-     * @return EE_Request_Handler
96
-     * @deprecated 4.10.14.p
97
-     */
98
-    public function Request_Handler()
99
-    {
100
-        if (! $this->Request_Handler instanceof EE_Request_Handler) {
101
-            $this->Request_Handler = LoaderFactory::getLoader()->getShared('EE_Request_Handler');
102
-        }
103
-        return $this->Request_Handler;
104
-    }
105
-
106
-
107
-    /**
108
-     * @return EE_Module_Request_Router
109
-     */
110
-    public function Module_Request_Router()
111
-    {
112
-        return $this->Module_Request_Router;
113
-    }
114
-
115
-
116
-    /**
117
-     * @return LegacyShortcodesManager
118
-     * @deprecated 4.10.14.p
119
-     */
120
-    public function getLegacyShortcodesManager()
121
-    {
122
-        return EE_Config::getLegacyShortcodesManager();
123
-    }
124
-
125
-
126
-
127
-
128
-
129
-    /***********************************************        INIT ACTION HOOK         ***********************************************/
130
-    /**
131
-     * filter_wp_comments
132
-     * This simply makes sure that any "private" EE CPTs do not have their comments show up in any wp comment
133
-     * widgets/queries done on frontend
134
-     *
135
-     * @param  array $clauses array of comment clauses setup by WP_Comment_Query
136
-     * @return array array of comment clauses with modifications.
137
-     * @throws InvalidArgumentException
138
-     * @throws InvalidDataTypeException
139
-     * @throws InvalidInterfaceException
140
-     */
141
-    public function filter_wp_comments($clauses)
142
-    {
143
-        global $wpdb;
144
-        if (strpos($clauses['join'], $wpdb->posts) !== false) {
145
-            /** @var EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions $custom_post_types */
146
-            $custom_post_types = LoaderFactory::getLoader()->getShared(
147
-                'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'
148
-            );
149
-            $cpts = $custom_post_types->getPrivateCustomPostTypes();
150
-            foreach ($cpts as $cpt => $details) {
151
-                $clauses['where'] .= $wpdb->prepare(" AND $wpdb->posts.post_type != %s", $cpt);
152
-            }
153
-        }
154
-        return $clauses;
155
-    }
156
-
157
-
158
-    /**
159
-     * this just makes sure that if the site is using ssl that we force that for any admin ajax calls from frontend
160
-     *
161
-     * @param  string $url incoming url
162
-     * @return string         final assembled url
163
-     */
164
-    public function maybe_force_admin_ajax_ssl($url)
165
-    {
166
-        if (is_ssl() && preg_match('/admin-ajax.php/', $url)) {
167
-            $url = str_replace('http://', 'https://', $url);
168
-        }
169
-        return $url;
170
-    }
171
-
172
-
173
-
174
-
175
-
176
-
177
-    /***********************************************        WP_LOADED ACTION HOOK         ***********************************************/
178
-
179
-
180
-    /**
181
-     *    wp_loaded - should fire after shortcode, module, addon, or other plugin's have been registered and their
182
-     *    default priority init phases have run
183
-     *
184
-     * @access    public
185
-     * @return    void
186
-     */
187
-    public function wp_loaded()
188
-    {
189
-    }
190
-
191
-
192
-
193
-
194
-
195
-    /***********************************************        PARSE_REQUEST HOOK         ***********************************************/
196
-    /**
197
-     *    _get_request
198
-     *
199
-     * @access public
200
-     * @param WP $WP
201
-     * @return void
202
-     */
203
-    public function get_request(WP $WP)
204
-    {
205
-        do_action('AHEE__EE_Front_Controller__get_request__start');
206
-        $this->current_page->parseQueryVars($WP);
207
-        do_action('AHEE__EE_Front_Controller__get_request__complete');
208
-        remove_action('parse_request', [$this, 'get_request'], 1);
209
-    }
210
-
211
-
212
-    /**
213
-     *    pre_get_posts - basically a module factory for instantiating modules and selecting the final view template
214
-     *
215
-     * @access    public
216
-     * @param WP_Query $WP_Query
217
-     * @return    void
218
-     * @throws EE_Error
219
-     * @throws ReflectionException
220
-     */
221
-    public function pre_get_posts($WP_Query)
222
-    {
223
-        // only load Module_Request_Router if this is the main query
224
-        if (
225
-            $this->Module_Request_Router instanceof EE_Module_Request_Router
226
-            && $WP_Query->is_main_query()
227
-        ) {
228
-            // cycle thru module routes
229
-            while ($route = $this->Module_Request_Router->get_route($WP_Query)) {
230
-                // determine module and method for route
231
-                $module = $this->Module_Request_Router->resolve_route($route[0], $route[1]);
232
-                if ($module instanceof EED_Module) {
233
-                    // get registered view for route
234
-                    $this->_template_path = $this->Module_Request_Router->get_view($route);
235
-                    // grab module name
236
-                    $module_name = $module->module_name();
237
-                    // map the module to the module objects
238
-                    $this->Registry->modules->{$module_name} = $module;
239
-                }
240
-            }
241
-        }
242
-    }
243
-
244
-
245
-
246
-
247
-
248
-    /***********************************************        WP HOOK         ***********************************************/
249
-
250
-
251
-    /**
252
-     *    wp - basically last chance to do stuff before headers sent
253
-     *
254
-     * @access    public
255
-     * @return    void
256
-     */
257
-    public function wp()
258
-    {
259
-    }
260
-
261
-
262
-
263
-    /***********************     GET_HEADER && WP_HEAD HOOK     ***********************/
264
-
265
-
266
-    /**
267
-     * callback for the "template_redirect" hook point
268
-     * checks sidebars for EE widgets
269
-     * loads resources and assets accordingly
270
-     *
271
-     * @return void
272
-     */
273
-    public function templateRedirect()
274
-    {
275
-        global $wp_query;
276
-        if (empty($wp_query->posts)) {
277
-            return;
278
-        }
279
-        // if we already know this is an espresso page, then load assets
280
-        $load_assets = $this->current_page->isEspressoPage();
281
-        // if we are already loading assets then just move along, otherwise check for widgets
282
-        $load_assets = $load_assets || $this->espresso_widgets_in_active_sidebars();
283
-        if ($load_assets) {
284
-            add_action('wp_enqueue_scripts', array($this, 'enqueueStyle'), 10);
285
-            add_action('wp_enqueue_scripts', array($this, 'enqueueScripts'), 10);
286
-        }
287
-    }
288
-
289
-
290
-    /**
291
-     * builds list of active widgets then scans active sidebars looking for them
292
-     * returns true is an EE widget is found in an active sidebar
293
-     * Please Note: this does NOT mean that the sidebar or widget
294
-     * is actually in use in a given template, as that is unfortunately not known
295
-     * until a sidebar and it's widgets are actually loaded
296
-     *
297
-     * @return boolean
298
-     */
299
-    private function espresso_widgets_in_active_sidebars()
300
-    {
301
-        $espresso_widgets = array();
302
-        foreach ($this->Registry->widgets as $widget_class => $widget) {
303
-            $id_base = EspressoWidget::getIdBase($widget_class);
304
-            if (is_active_widget(false, false, $id_base)) {
305
-                $espresso_widgets[] = $id_base;
306
-            }
307
-        }
308
-        $all_sidebar_widgets = wp_get_sidebars_widgets();
309
-        foreach ($all_sidebar_widgets as $sidebar_widgets) {
310
-            if (is_array($sidebar_widgets) && ! empty($sidebar_widgets)) {
311
-                foreach ($sidebar_widgets as $sidebar_widget) {
312
-                    foreach ($espresso_widgets as $espresso_widget) {
313
-                        if (strpos($sidebar_widget, $espresso_widget) !== false) {
314
-                            return true;
315
-                        }
316
-                    }
317
-                }
318
-            }
319
-        }
320
-        return false;
321
-    }
322
-
323
-
324
-    /**
325
-     *    header_meta_tag
326
-     *
327
-     * @access    public
328
-     * @return    void
329
-     */
330
-    public function header_meta_tag()
331
-    {
332
-        print(
333
-        apply_filters(
334
-            'FHEE__EE_Front_Controller__header_meta_tag',
335
-            '<meta name="generator" content="Event Espresso Version ' . EVENT_ESPRESSO_VERSION . "\" />\n"
336
-        )
337
-        );
338
-
339
-        // let's exclude all event type taxonomy term archive pages from search engine indexing
340
-        // @see https://events.codebasehq.com/projects/event-espresso/tickets/10249
341
-        // also exclude all critical pages from indexing
342
-        if (
343
-            (
344
-                is_tax('espresso_event_type')
345
-                && get_option('blog_public') !== '0'
346
-            )
347
-            || is_page(EE_Registry::instance()->CFG->core->get_critical_pages_array())
348
-        ) {
349
-            print(
350
-            apply_filters(
351
-                'FHEE__EE_Front_Controller__header_meta_tag__noindex_for_event_type',
352
-                '<meta name="robots" content="noindex,follow" />' . "\n"
353
-            )
354
-            );
355
-        }
356
-    }
357
-
358
-
359
-    /**
360
-     * wp_print_scripts
361
-     *
362
-     * @return void
363
-     * @throws EE_Error
364
-     */
365
-    public function wp_print_scripts()
366
-    {
367
-        global $post;
368
-        if (
369
-            isset($post->EE_Event)
370
-            && $post->EE_Event instanceof EE_Event
371
-            && get_post_type() === 'espresso_events'
372
-            && is_singular()
373
-        ) {
374
-            EEH_Schema::add_json_linked_data_for_event($post->EE_Event);
375
-        }
376
-    }
377
-
378
-
379
-    public function enqueueStyle()
380
-    {
381
-        wp_enqueue_style('espresso_default');
382
-        wp_enqueue_style('espresso_custom_css');
383
-    }
384
-
385
-
386
-
387
-    /***********************************************        WP_FOOTER         ***********************************************/
388
-
389
-
390
-    public function enqueueScripts()
391
-    {
392
-        wp_enqueue_script('espresso_core');
393
-    }
394
-
395
-
396
-    /**
397
-     * display_errors
398
-     *
399
-     * @access public
400
-     * @return void
401
-     * @throws DomainException
402
-     */
403
-    public function display_errors()
404
-    {
405
-        static $shown_already = false;
406
-        do_action('AHEE__EE_Front_Controller__display_errors__begin');
407
-        if (
408
-            ! $shown_already
409
-            && apply_filters('FHEE__EE_Front_Controller__display_errors', true)
410
-            && is_main_query()
411
-            && ! is_feed()
412
-            && in_the_loop()
413
-            && $this->current_page->isEspressoPage()
414
-        ) {
415
-            $shown_already = true;
416
-            if (did_action('wp_head')) {
417
-                echo wp_kses($this->printNotices(), AllowedTags::getAllowedTags());
418
-            } else {
419
-                // block enabled themes run their query loop before headers are sent
420
-                // so we need to add our notices onto the beginning of the content
421
-                add_filter('the_content', [$this, 'prependNotices'], 1, 1);
422
-            }
423
-        }
424
-        do_action('AHEE__EE_Front_Controller__display_errors__end');
425
-    }
426
-
427
-
428
-    /**
429
-     * @param string $the_content
430
-     * @return string
431
-     * @since 4.10.30.p
432
-     */
433
-    public function prependNotices($the_content)
434
-    {
435
-        $notices = $this->printNotices();
436
-        return $notices ? $notices . $the_content : $the_content;
437
-    }
438
-
439
-
440
-    /**
441
-     * @return false|string
442
-     * @since 4.10.30.p
443
-     */
444
-    public function printNotices()
445
-    {
446
-        ob_start();
447
-        echo wp_kses(EE_Error::get_notices(), AllowedTags::getWithFormTags());
448
-        EEH_Template::display_template(EE_TEMPLATES . 'espresso-ajax-notices.template.php');
449
-        return ob_get_clean();
450
-    }
451
-
452
-
453
-
454
-    /***********************************************        UTILITIES         ***********************************************/
455
-
456
-
457
-    /**
458
-     * @param string $template_include_path
459
-     * @return string
460
-     * @throws EE_Error
461
-     * @throws ReflectionException
462
-     */
463
-    public function template_include($template_include_path = null)
464
-    {
465
-        if ($this->current_page->isEspressoPage()) {
466
-            // despite all helpers having autoloaders set, we need to manually load the template loader
467
-            // because there are some side effects in that class for triggering template tag functions
468
-            $this->Registry->load_helper('EEH_Template');
469
-            $this->_template_path = ! empty($this->_template_path)
470
-                ? basename($this->_template_path)
471
-                : basename(
472
-                    $template_include_path
473
-                );
474
-            $template_path = EEH_Template::locate_template($this->_template_path, array(), false);
475
-            $this->_template_path = ! empty($template_path) ? $template_path : $template_include_path;
476
-            $this->_template = basename($this->_template_path);
477
-            return $this->_template_path;
478
-        }
479
-        return $template_include_path;
480
-    }
481
-
482
-
483
-    /**
484
-     * @param bool $with_path
485
-     * @return    string
486
-     */
487
-    public function get_selected_template($with_path = false)
488
-    {
489
-        return $with_path ? $this->_template_path : $this->_template;
490
-    }
491
-
492
-
493
-    /**
494
-     * @param string $shortcode_class
495
-     * @param WP     $wp
496
-     * @throws ReflectionException
497
-     * @deprecated 4.9.26
498
-     */
499
-    public function initialize_shortcode($shortcode_class = '', WP $wp = null)
500
-    {
501
-        EE_Error::doing_it_wrong(
502
-            __METHOD__,
503
-            esc_html__(
504
-                'Usage is deprecated. Please use \EventEspresso\core\services\shortcodes\LegacyShortcodesManager::initializeShortcode() instead.',
505
-                'event_espresso'
506
-            ),
507
-            '4.9.26'
508
-        );
509
-        $this->getLegacyShortcodesManager()->initializeShortcode($shortcode_class, $wp);
510
-    }
511
-
512
-
513
-    /**
514
-     * @return void
515
-     * @deprecated 4.9.57.p
516
-     */
517
-    public function loadPersistentAdminNoticeManager()
518
-    {
519
-    }
520
-
521
-
522
-    /**
523
-     * @return void
524
-     * @deprecated 4.9.64.p
525
-     */
526
-    public function employ_CPT_Strategy()
527
-    {
528
-    }
20
+	/**
21
+	 * @var string
22
+	 */
23
+	private $_template_path;
24
+
25
+	/**
26
+	 * @var string
27
+	 */
28
+	private $_template;
29
+
30
+	/**
31
+	 * @type EE_Registry
32
+	 */
33
+	protected $Registry;
34
+
35
+	/**
36
+	 * @type EE_Request_Handler
37
+	 */
38
+	protected $Request_Handler;
39
+
40
+	/**
41
+	 * @type EE_Module_Request_Router
42
+	 */
43
+	protected $Module_Request_Router;
44
+
45
+	/**
46
+	 * @type CurrentPage
47
+	 */
48
+	protected $current_page;
49
+
50
+
51
+	/**
52
+	 *    class constructor
53
+	 *    should fire after shortcode, module, addon, or other plugin's default priority init phases have run
54
+	 *
55
+	 * @access    public
56
+	 * @param EE_Registry              $Registry
57
+	 * @param CurrentPage              $EspressoPage
58
+	 * @param EE_Module_Request_Router $Module_Request_Router
59
+	 */
60
+	public function __construct(
61
+		EE_Registry $Registry,
62
+		CurrentPage $EspressoPage,
63
+		EE_Module_Request_Router $Module_Request_Router
64
+	) {
65
+		$this->Registry              = $Registry;
66
+		$this->current_page          = $EspressoPage;
67
+		$this->Module_Request_Router = $Module_Request_Router;
68
+		// load other resources and begin to actually run shortcodes and modules
69
+		// analyse the incoming WP request
70
+		add_action('parse_request', array($this, 'get_request'), 1, 1);
71
+		// process request with module factory
72
+		add_action('pre_get_posts', array($this, 'pre_get_posts'), 10, 1);
73
+		// before headers sent
74
+		add_action('wp', array($this, 'wp'), 5);
75
+		// primarily used to process any content shortcodes
76
+		add_action('template_redirect', array($this, 'templateRedirect'), 999);
77
+		// header
78
+		add_action('wp_head', array($this, 'header_meta_tag'), 5);
79
+		add_action('wp_print_scripts', array($this, 'wp_print_scripts'), 10);
80
+		add_filter('template_include', array($this, 'template_include'), 1);
81
+		// display errors
82
+		add_action('loop_start', array($this, 'display_errors'), 2);
83
+		// the content
84
+		// add_filter( 'the_content', array( $this, 'the_content' ), 5, 1 );
85
+		// exclude our private cpt comments
86
+		add_filter('comments_clauses', array($this, 'filter_wp_comments'), 10, 1);
87
+		// make sure any ajax requests will respect the url schema when requests are made against admin-ajax.php (http:// or https://)
88
+		add_filter('admin_url', array($this, 'maybe_force_admin_ajax_ssl'), 200, 1);
89
+		// action hook EE
90
+		do_action('AHEE__EE_Front_Controller__construct__done', $this);
91
+	}
92
+
93
+
94
+	/**
95
+	 * @return EE_Request_Handler
96
+	 * @deprecated 4.10.14.p
97
+	 */
98
+	public function Request_Handler()
99
+	{
100
+		if (! $this->Request_Handler instanceof EE_Request_Handler) {
101
+			$this->Request_Handler = LoaderFactory::getLoader()->getShared('EE_Request_Handler');
102
+		}
103
+		return $this->Request_Handler;
104
+	}
105
+
106
+
107
+	/**
108
+	 * @return EE_Module_Request_Router
109
+	 */
110
+	public function Module_Request_Router()
111
+	{
112
+		return $this->Module_Request_Router;
113
+	}
114
+
115
+
116
+	/**
117
+	 * @return LegacyShortcodesManager
118
+	 * @deprecated 4.10.14.p
119
+	 */
120
+	public function getLegacyShortcodesManager()
121
+	{
122
+		return EE_Config::getLegacyShortcodesManager();
123
+	}
124
+
125
+
126
+
127
+
128
+
129
+	/***********************************************        INIT ACTION HOOK         ***********************************************/
130
+	/**
131
+	 * filter_wp_comments
132
+	 * This simply makes sure that any "private" EE CPTs do not have their comments show up in any wp comment
133
+	 * widgets/queries done on frontend
134
+	 *
135
+	 * @param  array $clauses array of comment clauses setup by WP_Comment_Query
136
+	 * @return array array of comment clauses with modifications.
137
+	 * @throws InvalidArgumentException
138
+	 * @throws InvalidDataTypeException
139
+	 * @throws InvalidInterfaceException
140
+	 */
141
+	public function filter_wp_comments($clauses)
142
+	{
143
+		global $wpdb;
144
+		if (strpos($clauses['join'], $wpdb->posts) !== false) {
145
+			/** @var EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions $custom_post_types */
146
+			$custom_post_types = LoaderFactory::getLoader()->getShared(
147
+				'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'
148
+			);
149
+			$cpts = $custom_post_types->getPrivateCustomPostTypes();
150
+			foreach ($cpts as $cpt => $details) {
151
+				$clauses['where'] .= $wpdb->prepare(" AND $wpdb->posts.post_type != %s", $cpt);
152
+			}
153
+		}
154
+		return $clauses;
155
+	}
156
+
157
+
158
+	/**
159
+	 * this just makes sure that if the site is using ssl that we force that for any admin ajax calls from frontend
160
+	 *
161
+	 * @param  string $url incoming url
162
+	 * @return string         final assembled url
163
+	 */
164
+	public function maybe_force_admin_ajax_ssl($url)
165
+	{
166
+		if (is_ssl() && preg_match('/admin-ajax.php/', $url)) {
167
+			$url = str_replace('http://', 'https://', $url);
168
+		}
169
+		return $url;
170
+	}
171
+
172
+
173
+
174
+
175
+
176
+
177
+	/***********************************************        WP_LOADED ACTION HOOK         ***********************************************/
178
+
179
+
180
+	/**
181
+	 *    wp_loaded - should fire after shortcode, module, addon, or other plugin's have been registered and their
182
+	 *    default priority init phases have run
183
+	 *
184
+	 * @access    public
185
+	 * @return    void
186
+	 */
187
+	public function wp_loaded()
188
+	{
189
+	}
190
+
191
+
192
+
193
+
194
+
195
+	/***********************************************        PARSE_REQUEST HOOK         ***********************************************/
196
+	/**
197
+	 *    _get_request
198
+	 *
199
+	 * @access public
200
+	 * @param WP $WP
201
+	 * @return void
202
+	 */
203
+	public function get_request(WP $WP)
204
+	{
205
+		do_action('AHEE__EE_Front_Controller__get_request__start');
206
+		$this->current_page->parseQueryVars($WP);
207
+		do_action('AHEE__EE_Front_Controller__get_request__complete');
208
+		remove_action('parse_request', [$this, 'get_request'], 1);
209
+	}
210
+
211
+
212
+	/**
213
+	 *    pre_get_posts - basically a module factory for instantiating modules and selecting the final view template
214
+	 *
215
+	 * @access    public
216
+	 * @param WP_Query $WP_Query
217
+	 * @return    void
218
+	 * @throws EE_Error
219
+	 * @throws ReflectionException
220
+	 */
221
+	public function pre_get_posts($WP_Query)
222
+	{
223
+		// only load Module_Request_Router if this is the main query
224
+		if (
225
+			$this->Module_Request_Router instanceof EE_Module_Request_Router
226
+			&& $WP_Query->is_main_query()
227
+		) {
228
+			// cycle thru module routes
229
+			while ($route = $this->Module_Request_Router->get_route($WP_Query)) {
230
+				// determine module and method for route
231
+				$module = $this->Module_Request_Router->resolve_route($route[0], $route[1]);
232
+				if ($module instanceof EED_Module) {
233
+					// get registered view for route
234
+					$this->_template_path = $this->Module_Request_Router->get_view($route);
235
+					// grab module name
236
+					$module_name = $module->module_name();
237
+					// map the module to the module objects
238
+					$this->Registry->modules->{$module_name} = $module;
239
+				}
240
+			}
241
+		}
242
+	}
243
+
244
+
245
+
246
+
247
+
248
+	/***********************************************        WP HOOK         ***********************************************/
249
+
250
+
251
+	/**
252
+	 *    wp - basically last chance to do stuff before headers sent
253
+	 *
254
+	 * @access    public
255
+	 * @return    void
256
+	 */
257
+	public function wp()
258
+	{
259
+	}
260
+
261
+
262
+
263
+	/***********************     GET_HEADER && WP_HEAD HOOK     ***********************/
264
+
265
+
266
+	/**
267
+	 * callback for the "template_redirect" hook point
268
+	 * checks sidebars for EE widgets
269
+	 * loads resources and assets accordingly
270
+	 *
271
+	 * @return void
272
+	 */
273
+	public function templateRedirect()
274
+	{
275
+		global $wp_query;
276
+		if (empty($wp_query->posts)) {
277
+			return;
278
+		}
279
+		// if we already know this is an espresso page, then load assets
280
+		$load_assets = $this->current_page->isEspressoPage();
281
+		// if we are already loading assets then just move along, otherwise check for widgets
282
+		$load_assets = $load_assets || $this->espresso_widgets_in_active_sidebars();
283
+		if ($load_assets) {
284
+			add_action('wp_enqueue_scripts', array($this, 'enqueueStyle'), 10);
285
+			add_action('wp_enqueue_scripts', array($this, 'enqueueScripts'), 10);
286
+		}
287
+	}
288
+
289
+
290
+	/**
291
+	 * builds list of active widgets then scans active sidebars looking for them
292
+	 * returns true is an EE widget is found in an active sidebar
293
+	 * Please Note: this does NOT mean that the sidebar or widget
294
+	 * is actually in use in a given template, as that is unfortunately not known
295
+	 * until a sidebar and it's widgets are actually loaded
296
+	 *
297
+	 * @return boolean
298
+	 */
299
+	private function espresso_widgets_in_active_sidebars()
300
+	{
301
+		$espresso_widgets = array();
302
+		foreach ($this->Registry->widgets as $widget_class => $widget) {
303
+			$id_base = EspressoWidget::getIdBase($widget_class);
304
+			if (is_active_widget(false, false, $id_base)) {
305
+				$espresso_widgets[] = $id_base;
306
+			}
307
+		}
308
+		$all_sidebar_widgets = wp_get_sidebars_widgets();
309
+		foreach ($all_sidebar_widgets as $sidebar_widgets) {
310
+			if (is_array($sidebar_widgets) && ! empty($sidebar_widgets)) {
311
+				foreach ($sidebar_widgets as $sidebar_widget) {
312
+					foreach ($espresso_widgets as $espresso_widget) {
313
+						if (strpos($sidebar_widget, $espresso_widget) !== false) {
314
+							return true;
315
+						}
316
+					}
317
+				}
318
+			}
319
+		}
320
+		return false;
321
+	}
322
+
323
+
324
+	/**
325
+	 *    header_meta_tag
326
+	 *
327
+	 * @access    public
328
+	 * @return    void
329
+	 */
330
+	public function header_meta_tag()
331
+	{
332
+		print(
333
+		apply_filters(
334
+			'FHEE__EE_Front_Controller__header_meta_tag',
335
+			'<meta name="generator" content="Event Espresso Version ' . EVENT_ESPRESSO_VERSION . "\" />\n"
336
+		)
337
+		);
338
+
339
+		// let's exclude all event type taxonomy term archive pages from search engine indexing
340
+		// @see https://events.codebasehq.com/projects/event-espresso/tickets/10249
341
+		// also exclude all critical pages from indexing
342
+		if (
343
+			(
344
+				is_tax('espresso_event_type')
345
+				&& get_option('blog_public') !== '0'
346
+			)
347
+			|| is_page(EE_Registry::instance()->CFG->core->get_critical_pages_array())
348
+		) {
349
+			print(
350
+			apply_filters(
351
+				'FHEE__EE_Front_Controller__header_meta_tag__noindex_for_event_type',
352
+				'<meta name="robots" content="noindex,follow" />' . "\n"
353
+			)
354
+			);
355
+		}
356
+	}
357
+
358
+
359
+	/**
360
+	 * wp_print_scripts
361
+	 *
362
+	 * @return void
363
+	 * @throws EE_Error
364
+	 */
365
+	public function wp_print_scripts()
366
+	{
367
+		global $post;
368
+		if (
369
+			isset($post->EE_Event)
370
+			&& $post->EE_Event instanceof EE_Event
371
+			&& get_post_type() === 'espresso_events'
372
+			&& is_singular()
373
+		) {
374
+			EEH_Schema::add_json_linked_data_for_event($post->EE_Event);
375
+		}
376
+	}
377
+
378
+
379
+	public function enqueueStyle()
380
+	{
381
+		wp_enqueue_style('espresso_default');
382
+		wp_enqueue_style('espresso_custom_css');
383
+	}
384
+
385
+
386
+
387
+	/***********************************************        WP_FOOTER         ***********************************************/
388
+
389
+
390
+	public function enqueueScripts()
391
+	{
392
+		wp_enqueue_script('espresso_core');
393
+	}
394
+
395
+
396
+	/**
397
+	 * display_errors
398
+	 *
399
+	 * @access public
400
+	 * @return void
401
+	 * @throws DomainException
402
+	 */
403
+	public function display_errors()
404
+	{
405
+		static $shown_already = false;
406
+		do_action('AHEE__EE_Front_Controller__display_errors__begin');
407
+		if (
408
+			! $shown_already
409
+			&& apply_filters('FHEE__EE_Front_Controller__display_errors', true)
410
+			&& is_main_query()
411
+			&& ! is_feed()
412
+			&& in_the_loop()
413
+			&& $this->current_page->isEspressoPage()
414
+		) {
415
+			$shown_already = true;
416
+			if (did_action('wp_head')) {
417
+				echo wp_kses($this->printNotices(), AllowedTags::getAllowedTags());
418
+			} else {
419
+				// block enabled themes run their query loop before headers are sent
420
+				// so we need to add our notices onto the beginning of the content
421
+				add_filter('the_content', [$this, 'prependNotices'], 1, 1);
422
+			}
423
+		}
424
+		do_action('AHEE__EE_Front_Controller__display_errors__end');
425
+	}
426
+
427
+
428
+	/**
429
+	 * @param string $the_content
430
+	 * @return string
431
+	 * @since 4.10.30.p
432
+	 */
433
+	public function prependNotices($the_content)
434
+	{
435
+		$notices = $this->printNotices();
436
+		return $notices ? $notices . $the_content : $the_content;
437
+	}
438
+
439
+
440
+	/**
441
+	 * @return false|string
442
+	 * @since 4.10.30.p
443
+	 */
444
+	public function printNotices()
445
+	{
446
+		ob_start();
447
+		echo wp_kses(EE_Error::get_notices(), AllowedTags::getWithFormTags());
448
+		EEH_Template::display_template(EE_TEMPLATES . 'espresso-ajax-notices.template.php');
449
+		return ob_get_clean();
450
+	}
451
+
452
+
453
+
454
+	/***********************************************        UTILITIES         ***********************************************/
455
+
456
+
457
+	/**
458
+	 * @param string $template_include_path
459
+	 * @return string
460
+	 * @throws EE_Error
461
+	 * @throws ReflectionException
462
+	 */
463
+	public function template_include($template_include_path = null)
464
+	{
465
+		if ($this->current_page->isEspressoPage()) {
466
+			// despite all helpers having autoloaders set, we need to manually load the template loader
467
+			// because there are some side effects in that class for triggering template tag functions
468
+			$this->Registry->load_helper('EEH_Template');
469
+			$this->_template_path = ! empty($this->_template_path)
470
+				? basename($this->_template_path)
471
+				: basename(
472
+					$template_include_path
473
+				);
474
+			$template_path = EEH_Template::locate_template($this->_template_path, array(), false);
475
+			$this->_template_path = ! empty($template_path) ? $template_path : $template_include_path;
476
+			$this->_template = basename($this->_template_path);
477
+			return $this->_template_path;
478
+		}
479
+		return $template_include_path;
480
+	}
481
+
482
+
483
+	/**
484
+	 * @param bool $with_path
485
+	 * @return    string
486
+	 */
487
+	public function get_selected_template($with_path = false)
488
+	{
489
+		return $with_path ? $this->_template_path : $this->_template;
490
+	}
491
+
492
+
493
+	/**
494
+	 * @param string $shortcode_class
495
+	 * @param WP     $wp
496
+	 * @throws ReflectionException
497
+	 * @deprecated 4.9.26
498
+	 */
499
+	public function initialize_shortcode($shortcode_class = '', WP $wp = null)
500
+	{
501
+		EE_Error::doing_it_wrong(
502
+			__METHOD__,
503
+			esc_html__(
504
+				'Usage is deprecated. Please use \EventEspresso\core\services\shortcodes\LegacyShortcodesManager::initializeShortcode() instead.',
505
+				'event_espresso'
506
+			),
507
+			'4.9.26'
508
+		);
509
+		$this->getLegacyShortcodesManager()->initializeShortcode($shortcode_class, $wp);
510
+	}
511
+
512
+
513
+	/**
514
+	 * @return void
515
+	 * @deprecated 4.9.57.p
516
+	 */
517
+	public function loadPersistentAdminNoticeManager()
518
+	{
519
+	}
520
+
521
+
522
+	/**
523
+	 * @return void
524
+	 * @deprecated 4.9.64.p
525
+	 */
526
+	public function employ_CPT_Strategy()
527
+	{
528
+	}
529 529
 }
Please login to merge, or discard this patch.
core/EE_Addon.core.php 1 patch
Indentation   +859 added lines, -859 removed lines patch added patch discarded remove patch
@@ -18,870 +18,870 @@
 block discarded – undo
18 18
  */
19 19
 abstract class EE_Addon extends EE_Configurable implements RequiresDependencyMapInterface, RequiresDomainInterface
20 20
 {
21
-    /**
22
-     * prefix to be added onto an addon's plugin slug to make a wp option name
23
-     * which will be used to store the plugin's activation history
24
-     */
25
-    const ee_addon_version_history_option_prefix = 'ee_version_history_';
26
-
27
-    /**
28
-     * @var $_version
29
-     * @type string
30
-     */
31
-    protected $_version = '';
32
-
33
-    /**
34
-     * @var $_min_core_version
35
-     * @type string
36
-     */
37
-    protected $_min_core_version = '';
38
-
39
-    /**
40
-     * derived from plugin 'main_file_path using plugin_basename()
41
-     *
42
-     * @type string $_plugin_basename
43
-     */
44
-    protected $_plugin_basename = '';
45
-
46
-    /**
47
-     * A non-internationalized name to identify this addon for use in URLs, etc
48
-     *
49
-     * @type string $_plugin_slug
50
-     */
51
-    protected $_plugin_slug = '';
52
-
53
-    /**
54
-     * A non-internationalized name to identify this addon. Eg 'Calendar','MailChimp',etc/
55
-     *
56
-     * @type string _addon_name
57
-     */
58
-    protected $_addon_name = '';
59
-
60
-    /**
61
-     * one of the EE_System::req_type_* constants
62
-     *
63
-     * @type int $_req_type
64
-     */
65
-    protected $_req_type;
66
-
67
-    /**
68
-     * page slug to be used when generating the "Settings" link on the WP plugin page
69
-     *
70
-     * @type string $_plugin_action_slug
71
-     */
72
-    protected $_plugin_action_slug = '';
73
-
74
-    /**
75
-     * if not empty, inserts a new table row after this plugin's row on the WP Plugins page
76
-     * that can be used for adding upgrading/marketing info
77
-     *
78
-     * @type array $_plugins_page_row
79
-     */
80
-    protected $_plugins_page_row = [];
81
-
82
-
83
-    /**
84
-     *    filepath to the main file, which can be used for register_activation_hook, register_deactivation_hook, etc.
85
-     *
86
-     * @type string
87
-     */
88
-    protected $_main_plugin_file;
89
-
90
-    /**
91
-     *    This is the slug used to identify this add-on within the plugin update engine.
92
-     *
93
-     * @type string
94
-     */
95
-    protected $pue_slug = '';
96
-
97
-
98
-    /**
99
-     * @var EE_Dependency_Map $dependency_map
100
-     */
101
-    private $dependency_map;
102
-
103
-
104
-    /**
105
-     * @var DomainInterface $domain
106
-     */
107
-    private $domain;
108
-
109
-
110
-    /**
111
-     * @param EE_Dependency_Map|null $dependency_map [optional]
112
-     * @param DomainInterface|null   $domain         [optional]
113
-     */
114
-    public function __construct(EE_Dependency_Map $dependency_map = null, DomainInterface $domain = null)
115
-    {
116
-        if ($dependency_map instanceof EE_Dependency_Map) {
117
-            $this->setDependencyMap($dependency_map);
118
-        }
119
-        if ($domain instanceof DomainInterface) {
120
-            $this->setDomain($domain);
121
-        }
122
-        add_action('AHEE__EE_System__load_controllers__load_admin_controllers', [$this, 'admin_init']);
123
-    }
124
-
125
-
126
-    /**
127
-     * @param EE_Dependency_Map $dependency_map
128
-     */
129
-    public function setDependencyMap($dependency_map)
130
-    {
131
-        $this->dependency_map = $dependency_map;
132
-    }
133
-
134
-
135
-    /**
136
-     * @return EE_Dependency_Map
137
-     */
138
-    public function dependencyMap(): ?EE_Dependency_Map
139
-    {
140
-        return $this->dependency_map;
141
-    }
142
-
143
-
144
-    /**
145
-     * @param DomainInterface $domain
146
-     */
147
-    public function setDomain(DomainInterface $domain)
148
-    {
149
-        $this->domain = $domain;
150
-    }
151
-
152
-
153
-    /**
154
-     * @return DomainInterface
155
-     */
156
-    public function domain(): ?DomainInterface
157
-    {
158
-        return $this->domain;
159
-    }
160
-
161
-
162
-    /**
163
-     * @param string $version
164
-     */
165
-    public function set_version(string $version = '')
166
-    {
167
-        $this->_version = $version;
168
-    }
169
-
170
-
171
-    /**
172
-     * get__version
173
-     *
174
-     * @return string
175
-     */
176
-    public function version(): string
177
-    {
178
-        return $this->_version;
179
-    }
180
-
181
-
182
-    /**
183
-     * @param mixed $min_core_version
184
-     */
185
-    public function set_min_core_version($min_core_version = null)
186
-    {
187
-        $this->_min_core_version = $min_core_version;
188
-    }
189
-
190
-
191
-    /**
192
-     * get__min_core_version
193
-     *
194
-     * @return string
195
-     */
196
-    public function min_core_version(): string
197
-    {
198
-        return $this->_min_core_version;
199
-    }
200
-
201
-
202
-    /**
203
-     * Sets addon_name
204
-     *
205
-     * @param string $addon_name
206
-     */
207
-    public function set_name(string $addon_name)
208
-    {
209
-        $this->_addon_name = $addon_name;
210
-    }
211
-
212
-
213
-    /**
214
-     * Gets addon_name
215
-     *
216
-     * @return string
217
-     */
218
-    public function name()
219
-    {
220
-        return $this->_addon_name;
221
-    }
222
-
223
-
224
-    /**
225
-     * @return string
226
-     */
227
-    public function plugin_basename(): string
228
-    {
229
-
230
-        return $this->_plugin_basename;
231
-    }
232
-
233
-
234
-    /**
235
-     * @param string $plugin_basename
236
-     */
237
-    public function set_plugin_basename(string $plugin_basename)
238
-    {
239
-
240
-        $this->_plugin_basename = $plugin_basename;
241
-    }
242
-
243
-
244
-    /**
245
-     * @return string
246
-     */
247
-    public function plugin_slug(): string
248
-    {
249
-
250
-        return $this->_plugin_slug;
251
-    }
252
-
253
-
254
-    /**
255
-     * @param string $plugin_slug
256
-     */
257
-    public function set_plugin_slug(string $plugin_slug)
258
-    {
259
-
260
-        $this->_plugin_slug = $plugin_slug;
261
-    }
262
-
263
-
264
-    /**
265
-     * @return string
266
-     */
267
-    public function plugin_action_slug(): string
268
-    {
269
-
270
-        return $this->_plugin_action_slug;
271
-    }
272
-
273
-
274
-    /**
275
-     * @param string $plugin_action_slug
276
-     */
277
-    public function set_plugin_action_slug(string $plugin_action_slug)
278
-    {
279
-
280
-        $this->_plugin_action_slug = $plugin_action_slug;
281
-    }
282
-
283
-
284
-    /**
285
-     * @return array
286
-     */
287
-    public function get_plugins_page_row(): array
288
-    {
289
-
290
-        return $this->_plugins_page_row;
291
-    }
292
-
293
-
294
-    /**
295
-     * @param array|string $plugins_page_row
296
-     */
297
-    public function set_plugins_page_row(array $plugins_page_row = [])
298
-    {
299
-        // sigh.... check for example content that I stupidly merged to master and remove it if found
300
-        if (
301
-            ! is_array($plugins_page_row)
302
-            && strpos($plugins_page_row, '<h3>Promotions Addon Upsell Info</h3>') !== false
303
-        ) {
304
-            $plugins_page_row = [];
305
-        }
306
-        $this->_plugins_page_row = (array) $plugins_page_row;
307
-    }
308
-
309
-
310
-    /**
311
-     * Called when EE core detects this addon has been activated for the first time.
312
-     * If the site isn't in maintenance mode, should setup the addon's database
313
-     *
314
-     * @return void
315
-     * @throws EE_Error
316
-     */
317
-    public function new_install()
318
-    {
319
-        $classname = get_class($this);
320
-        do_action("AHEE__{$classname}__new_install");
321
-        do_action('AHEE__EE_Addon__new_install', $this);
322
-        EE_Maintenance_Mode::instance()->set_maintenance_mode_if_db_old();
323
-        add_action(
324
-            'AHEE__EE_System__perform_activations_upgrades_and_migrations',
325
-            [$this, 'initialize_db_if_no_migrations_required']
326
-        );
327
-    }
328
-
329
-
330
-    /**
331
-     * Called when EE core detects this addon has been reactivated. When this happens,
332
-     * it's good to just check that your data is still intact
333
-     *
334
-     * @return void
335
-     * @throws EE_Error
336
-     */
337
-    public function reactivation()
338
-    {
339
-        $classname = get_class($this);
340
-        do_action("AHEE__{$classname}__reactivation");
341
-        do_action('AHEE__EE_Addon__reactivation', $this);
342
-        EE_Maintenance_Mode::instance()->set_maintenance_mode_if_db_old();
343
-        add_action(
344
-            'AHEE__EE_System__perform_activations_upgrades_and_migrations',
345
-            [$this, 'initialize_db_if_no_migrations_required']
346
-        );
347
-    }
348
-
349
-
350
-    /**
351
-     * Called when the registered deactivation hook for this addon fires.
352
-     *
353
-     * @throws EE_Error
354
-     */
355
-    public function deactivation()
356
-    {
357
-        $classname = get_class($this);
358
-        do_action("AHEE__{$classname}__deactivation");
359
-        do_action('AHEE__EE_Addon__deactivation', $this);
360
-        // check if the site no longer needs to be in maintenance mode
361
-        EE_Register_Addon::deregister($this->name());
362
-        EE_Maintenance_Mode::instance()->set_maintenance_mode_if_db_old();
363
-    }
364
-
365
-
366
-    /**
367
-     * Takes care of double-checking that we're not in maintenance mode, and then
368
-     * initializing this addon's necessary initial data. This is called by default on new activations
369
-     * and reactivations.
370
-     *
371
-     * @param bool $verify_schema whether to verify the database's schema for this addon, or just its data.
372
-     *                               This is a resource-intensive job so we prefer to only do it when necessary
373
-     * @return void
374
-     * @throws EE_Error
375
-     * @throws InvalidInterfaceException
376
-     * @throws InvalidDataTypeException
377
-     * @throws InvalidArgumentException
378
-     * @throws ReflectionException
379
-     */
380
-    public function initialize_db_if_no_migrations_required($verify_schema = true)
381
-    {
382
-        if ($verify_schema === '') {
383
-            // wp core bug imo: if no args are passed to `do_action('some_hook_name')` besides the hook's name
384
-            // (ie, no 2nd or 3rd arguments), instead of calling the registered callbacks with no arguments, it
385
-            // calls them with an argument of an empty string (ie ""), which evaluates to false
386
-            // so we need to treat the empty string as if nothing had been passed, and should instead use the default
387
-            $verify_schema = true;
388
-        }
389
-        if (EE_Maintenance_Mode::instance()->level() !== EE_Maintenance_Mode::level_2_complete_maintenance) {
390
-            if ($verify_schema) {
391
-                $this->initialize_db();
392
-            }
393
-            $this->initialize_default_data();
394
-            // @todo: this will probably need to be adjusted in 4.4 as the array changed formats I believe
395
-            EE_Data_Migration_Manager::instance()->update_current_database_state_to(
396
-                [
397
-                    'slug'    => $this->name(),
398
-                    'version' => $this->version(),
399
-                ]
400
-            );
401
-            /* make sure core's data is a-ok
21
+	/**
22
+	 * prefix to be added onto an addon's plugin slug to make a wp option name
23
+	 * which will be used to store the plugin's activation history
24
+	 */
25
+	const ee_addon_version_history_option_prefix = 'ee_version_history_';
26
+
27
+	/**
28
+	 * @var $_version
29
+	 * @type string
30
+	 */
31
+	protected $_version = '';
32
+
33
+	/**
34
+	 * @var $_min_core_version
35
+	 * @type string
36
+	 */
37
+	protected $_min_core_version = '';
38
+
39
+	/**
40
+	 * derived from plugin 'main_file_path using plugin_basename()
41
+	 *
42
+	 * @type string $_plugin_basename
43
+	 */
44
+	protected $_plugin_basename = '';
45
+
46
+	/**
47
+	 * A non-internationalized name to identify this addon for use in URLs, etc
48
+	 *
49
+	 * @type string $_plugin_slug
50
+	 */
51
+	protected $_plugin_slug = '';
52
+
53
+	/**
54
+	 * A non-internationalized name to identify this addon. Eg 'Calendar','MailChimp',etc/
55
+	 *
56
+	 * @type string _addon_name
57
+	 */
58
+	protected $_addon_name = '';
59
+
60
+	/**
61
+	 * one of the EE_System::req_type_* constants
62
+	 *
63
+	 * @type int $_req_type
64
+	 */
65
+	protected $_req_type;
66
+
67
+	/**
68
+	 * page slug to be used when generating the "Settings" link on the WP plugin page
69
+	 *
70
+	 * @type string $_plugin_action_slug
71
+	 */
72
+	protected $_plugin_action_slug = '';
73
+
74
+	/**
75
+	 * if not empty, inserts a new table row after this plugin's row on the WP Plugins page
76
+	 * that can be used for adding upgrading/marketing info
77
+	 *
78
+	 * @type array $_plugins_page_row
79
+	 */
80
+	protected $_plugins_page_row = [];
81
+
82
+
83
+	/**
84
+	 *    filepath to the main file, which can be used for register_activation_hook, register_deactivation_hook, etc.
85
+	 *
86
+	 * @type string
87
+	 */
88
+	protected $_main_plugin_file;
89
+
90
+	/**
91
+	 *    This is the slug used to identify this add-on within the plugin update engine.
92
+	 *
93
+	 * @type string
94
+	 */
95
+	protected $pue_slug = '';
96
+
97
+
98
+	/**
99
+	 * @var EE_Dependency_Map $dependency_map
100
+	 */
101
+	private $dependency_map;
102
+
103
+
104
+	/**
105
+	 * @var DomainInterface $domain
106
+	 */
107
+	private $domain;
108
+
109
+
110
+	/**
111
+	 * @param EE_Dependency_Map|null $dependency_map [optional]
112
+	 * @param DomainInterface|null   $domain         [optional]
113
+	 */
114
+	public function __construct(EE_Dependency_Map $dependency_map = null, DomainInterface $domain = null)
115
+	{
116
+		if ($dependency_map instanceof EE_Dependency_Map) {
117
+			$this->setDependencyMap($dependency_map);
118
+		}
119
+		if ($domain instanceof DomainInterface) {
120
+			$this->setDomain($domain);
121
+		}
122
+		add_action('AHEE__EE_System__load_controllers__load_admin_controllers', [$this, 'admin_init']);
123
+	}
124
+
125
+
126
+	/**
127
+	 * @param EE_Dependency_Map $dependency_map
128
+	 */
129
+	public function setDependencyMap($dependency_map)
130
+	{
131
+		$this->dependency_map = $dependency_map;
132
+	}
133
+
134
+
135
+	/**
136
+	 * @return EE_Dependency_Map
137
+	 */
138
+	public function dependencyMap(): ?EE_Dependency_Map
139
+	{
140
+		return $this->dependency_map;
141
+	}
142
+
143
+
144
+	/**
145
+	 * @param DomainInterface $domain
146
+	 */
147
+	public function setDomain(DomainInterface $domain)
148
+	{
149
+		$this->domain = $domain;
150
+	}
151
+
152
+
153
+	/**
154
+	 * @return DomainInterface
155
+	 */
156
+	public function domain(): ?DomainInterface
157
+	{
158
+		return $this->domain;
159
+	}
160
+
161
+
162
+	/**
163
+	 * @param string $version
164
+	 */
165
+	public function set_version(string $version = '')
166
+	{
167
+		$this->_version = $version;
168
+	}
169
+
170
+
171
+	/**
172
+	 * get__version
173
+	 *
174
+	 * @return string
175
+	 */
176
+	public function version(): string
177
+	{
178
+		return $this->_version;
179
+	}
180
+
181
+
182
+	/**
183
+	 * @param mixed $min_core_version
184
+	 */
185
+	public function set_min_core_version($min_core_version = null)
186
+	{
187
+		$this->_min_core_version = $min_core_version;
188
+	}
189
+
190
+
191
+	/**
192
+	 * get__min_core_version
193
+	 *
194
+	 * @return string
195
+	 */
196
+	public function min_core_version(): string
197
+	{
198
+		return $this->_min_core_version;
199
+	}
200
+
201
+
202
+	/**
203
+	 * Sets addon_name
204
+	 *
205
+	 * @param string $addon_name
206
+	 */
207
+	public function set_name(string $addon_name)
208
+	{
209
+		$this->_addon_name = $addon_name;
210
+	}
211
+
212
+
213
+	/**
214
+	 * Gets addon_name
215
+	 *
216
+	 * @return string
217
+	 */
218
+	public function name()
219
+	{
220
+		return $this->_addon_name;
221
+	}
222
+
223
+
224
+	/**
225
+	 * @return string
226
+	 */
227
+	public function plugin_basename(): string
228
+	{
229
+
230
+		return $this->_plugin_basename;
231
+	}
232
+
233
+
234
+	/**
235
+	 * @param string $plugin_basename
236
+	 */
237
+	public function set_plugin_basename(string $plugin_basename)
238
+	{
239
+
240
+		$this->_plugin_basename = $plugin_basename;
241
+	}
242
+
243
+
244
+	/**
245
+	 * @return string
246
+	 */
247
+	public function plugin_slug(): string
248
+	{
249
+
250
+		return $this->_plugin_slug;
251
+	}
252
+
253
+
254
+	/**
255
+	 * @param string $plugin_slug
256
+	 */
257
+	public function set_plugin_slug(string $plugin_slug)
258
+	{
259
+
260
+		$this->_plugin_slug = $plugin_slug;
261
+	}
262
+
263
+
264
+	/**
265
+	 * @return string
266
+	 */
267
+	public function plugin_action_slug(): string
268
+	{
269
+
270
+		return $this->_plugin_action_slug;
271
+	}
272
+
273
+
274
+	/**
275
+	 * @param string $plugin_action_slug
276
+	 */
277
+	public function set_plugin_action_slug(string $plugin_action_slug)
278
+	{
279
+
280
+		$this->_plugin_action_slug = $plugin_action_slug;
281
+	}
282
+
283
+
284
+	/**
285
+	 * @return array
286
+	 */
287
+	public function get_plugins_page_row(): array
288
+	{
289
+
290
+		return $this->_plugins_page_row;
291
+	}
292
+
293
+
294
+	/**
295
+	 * @param array|string $plugins_page_row
296
+	 */
297
+	public function set_plugins_page_row(array $plugins_page_row = [])
298
+	{
299
+		// sigh.... check for example content that I stupidly merged to master and remove it if found
300
+		if (
301
+			! is_array($plugins_page_row)
302
+			&& strpos($plugins_page_row, '<h3>Promotions Addon Upsell Info</h3>') !== false
303
+		) {
304
+			$plugins_page_row = [];
305
+		}
306
+		$this->_plugins_page_row = (array) $plugins_page_row;
307
+	}
308
+
309
+
310
+	/**
311
+	 * Called when EE core detects this addon has been activated for the first time.
312
+	 * If the site isn't in maintenance mode, should setup the addon's database
313
+	 *
314
+	 * @return void
315
+	 * @throws EE_Error
316
+	 */
317
+	public function new_install()
318
+	{
319
+		$classname = get_class($this);
320
+		do_action("AHEE__{$classname}__new_install");
321
+		do_action('AHEE__EE_Addon__new_install', $this);
322
+		EE_Maintenance_Mode::instance()->set_maintenance_mode_if_db_old();
323
+		add_action(
324
+			'AHEE__EE_System__perform_activations_upgrades_and_migrations',
325
+			[$this, 'initialize_db_if_no_migrations_required']
326
+		);
327
+	}
328
+
329
+
330
+	/**
331
+	 * Called when EE core detects this addon has been reactivated. When this happens,
332
+	 * it's good to just check that your data is still intact
333
+	 *
334
+	 * @return void
335
+	 * @throws EE_Error
336
+	 */
337
+	public function reactivation()
338
+	{
339
+		$classname = get_class($this);
340
+		do_action("AHEE__{$classname}__reactivation");
341
+		do_action('AHEE__EE_Addon__reactivation', $this);
342
+		EE_Maintenance_Mode::instance()->set_maintenance_mode_if_db_old();
343
+		add_action(
344
+			'AHEE__EE_System__perform_activations_upgrades_and_migrations',
345
+			[$this, 'initialize_db_if_no_migrations_required']
346
+		);
347
+	}
348
+
349
+
350
+	/**
351
+	 * Called when the registered deactivation hook for this addon fires.
352
+	 *
353
+	 * @throws EE_Error
354
+	 */
355
+	public function deactivation()
356
+	{
357
+		$classname = get_class($this);
358
+		do_action("AHEE__{$classname}__deactivation");
359
+		do_action('AHEE__EE_Addon__deactivation', $this);
360
+		// check if the site no longer needs to be in maintenance mode
361
+		EE_Register_Addon::deregister($this->name());
362
+		EE_Maintenance_Mode::instance()->set_maintenance_mode_if_db_old();
363
+	}
364
+
365
+
366
+	/**
367
+	 * Takes care of double-checking that we're not in maintenance mode, and then
368
+	 * initializing this addon's necessary initial data. This is called by default on new activations
369
+	 * and reactivations.
370
+	 *
371
+	 * @param bool $verify_schema whether to verify the database's schema for this addon, or just its data.
372
+	 *                               This is a resource-intensive job so we prefer to only do it when necessary
373
+	 * @return void
374
+	 * @throws EE_Error
375
+	 * @throws InvalidInterfaceException
376
+	 * @throws InvalidDataTypeException
377
+	 * @throws InvalidArgumentException
378
+	 * @throws ReflectionException
379
+	 */
380
+	public function initialize_db_if_no_migrations_required($verify_schema = true)
381
+	{
382
+		if ($verify_schema === '') {
383
+			// wp core bug imo: if no args are passed to `do_action('some_hook_name')` besides the hook's name
384
+			// (ie, no 2nd or 3rd arguments), instead of calling the registered callbacks with no arguments, it
385
+			// calls them with an argument of an empty string (ie ""), which evaluates to false
386
+			// so we need to treat the empty string as if nothing had been passed, and should instead use the default
387
+			$verify_schema = true;
388
+		}
389
+		if (EE_Maintenance_Mode::instance()->level() !== EE_Maintenance_Mode::level_2_complete_maintenance) {
390
+			if ($verify_schema) {
391
+				$this->initialize_db();
392
+			}
393
+			$this->initialize_default_data();
394
+			// @todo: this will probably need to be adjusted in 4.4 as the array changed formats I believe
395
+			EE_Data_Migration_Manager::instance()->update_current_database_state_to(
396
+				[
397
+					'slug'    => $this->name(),
398
+					'version' => $this->version(),
399
+				]
400
+			);
401
+			/* make sure core's data is a-ok
402 402
              * (at the time of writing, we especially want to verify all the caps are present
403 403
              * because payment method type capabilities are added dynamically, and it's
404 404
              * possible this addon added a payment method. But it's also possible
405 405
              * other data needs to be verified)
406 406
              */
407
-            EEH_Activation::initialize_db_content();
408
-            /** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
409
-            $rewrite_rules = LoaderFactory::getLoader()->getShared(
410
-                'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
411
-            );
412
-            $rewrite_rules->flushRewriteRules();
413
-            // in case there are lots of addons being activated at once, let's force garbage collection
414
-            // to help avoid memory limit errors
415
-            // EEH_Debug_Tools::instance()->measure_memory( 'db content initialized for ' . get_class( $this), true );
416
-            gc_collect_cycles();
417
-        } else {
418
-            // ask the data migration manager to init this addon's data
419
-            // when migrations are finished because we can't do it now
420
-            EE_Data_Migration_Manager::instance()->enqueue_db_initialization_for($this->name());
421
-        }
422
-    }
423
-
424
-
425
-    /**
426
-     * Used to setup this addon's database tables, but not necessarily any default
427
-     * data in them. The default is to actually use the most up-to-date data migration script
428
-     * for this addon, and just use its schema_changes_before_migration() and schema_changes_after_migration()
429
-     * methods to setup the db.
430
-     *
431
-     * @throws EE_Error
432
-     * @throws ReflectionException
433
-     */
434
-    public function initialize_db()
435
-    {
436
-        // find the migration script that sets the database to be compatible with the code
437
-        $current_dms_name = EE_Data_Migration_Manager::instance()->get_most_up_to_date_dms($this->name());
438
-        if ($current_dms_name) {
439
-            $current_data_migration_script = EE_Registry::instance()->load_dms($current_dms_name);
440
-            $current_data_migration_script->set_migrating(false);
441
-            $current_data_migration_script->schema_changes_before_migration();
442
-            $current_data_migration_script->schema_changes_after_migration();
443
-            if ($current_data_migration_script->get_errors()) {
444
-                foreach ($current_data_migration_script->get_errors() as $error) {
445
-                    EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
446
-                }
447
-            }
448
-        }
449
-        // if not DMS was found that should be ok. This addon just doesn't require any database changes
450
-        EE_Data_Migration_Manager::instance()->update_current_database_state_to(
451
-            [
452
-                'slug'    => $this->name(),
453
-                'version' => $this->version(),
454
-            ]
455
-        );
456
-    }
457
-
458
-
459
-    /**
460
-     * If you want to setup default data for the addon, override this method, and call
461
-     * parent::initialize_default_data() from within it. This is normally called
462
-     * from EE_Addon::initialize_db_if_no_migrations_required(), just after EE_Addon::initialize_db()
463
-     * and should verify default data is present (but this is also called
464
-     * on reactivations and just after migrations, so please verify you actually want
465
-     * to ADD default data, because it may already be present).
466
-     * However, please call this parent (currently it just fires a hook which other
467
-     * addons may be depending on)
468
-     */
469
-    public function initialize_default_data()
470
-    {
471
-        /**
472
-         * Called when an addon is ensuring its default data is set (possibly called
473
-         * on a reactivation, so first check for the absence of other data before setting
474
-         * default data)
475
-         *
476
-         * @param EE_Addon $addon the addon that called this
477
-         */
478
-        do_action('AHEE__EE_Addon__initialize_default_data__begin', $this);
479
-        // override to insert default data. It is safe to use the models here
480
-        // because the site should not be in maintenance mode
481
-    }
482
-
483
-
484
-    /**
485
-     * EE Core detected that this addon has been upgraded. We should check if there
486
-     * are any new migration scripts, and if so put the site into maintenance mode until
487
-     * they're ran
488
-     *
489
-     * @return void
490
-     * @throws EE_Error
491
-     */
492
-    public function upgrade()
493
-    {
494
-        $classname = get_class($this);
495
-        do_action("AHEE__{$classname}__upgrade");
496
-        do_action('AHEE__EE_Addon__upgrade', $this);
497
-        EE_Maintenance_Mode::instance()->set_maintenance_mode_if_db_old();
498
-        // also it's possible there is new default data that needs to be added
499
-        add_action(
500
-            'AHEE__EE_System__perform_activations_upgrades_and_migrations',
501
-            [$this, 'initialize_db_if_no_migrations_required']
502
-        );
503
-    }
504
-
505
-
506
-    /**
507
-     * If Core detects this addon has been downgraded, you may want to invoke some special logic here.
508
-     */
509
-    public function downgrade()
510
-    {
511
-        $classname = get_class($this);
512
-        do_action("AHEE__{$classname}__downgrade");
513
-        do_action('AHEE__EE_Addon__downgrade', $this);
514
-        // it's possible there's old default data that needs to be double-checked
515
-        add_action(
516
-            'AHEE__EE_System__perform_activations_upgrades_and_migrations',
517
-            [$this, 'initialize_db_if_no_migrations_required']
518
-        );
519
-    }
520
-
521
-
522
-    /**
523
-     * set_db_update_option_name
524
-     * Until we do something better, we'll just check for migration scripts upon
525
-     * plugin activation only. In the future, we'll want to do it on plugin updates too
526
-     *
527
-     * @return bool
528
-     */
529
-    public function set_db_update_option_name(): bool
530
-    {
531
-        EE_Error::doing_it_wrong(
532
-            __FUNCTION__,
533
-            esc_html__(
534
-                'EE_Addon::set_db_update_option_name was renamed to EE_Addon::set_activation_indicator_option',
535
-                'event_espresso'
536
-            ),
537
-            '4.3.0.alpha.016'
538
-        );
539
-        // let's just handle this on the next request, ok? right now we're just not really ready
540
-        return $this->set_activation_indicator_option();
541
-    }
542
-
543
-
544
-    /**
545
-     * Returns the name of the activation indicator option
546
-     * (an option which is set temporarily to indicate that this addon was just activated)
547
-     *
548
-     * @return string
549
-     * @deprecated since version 4.3.0.alpha.016
550
-     */
551
-    public function get_db_update_option_name(): string
552
-    {
553
-        EE_Error::doing_it_wrong(
554
-            __FUNCTION__,
555
-            esc_html__(
556
-                'EE_Addon::get_db_update_option was renamed to EE_Addon::get_activation_indicator_option_name',
557
-                'event_espresso'
558
-            ),
559
-            '4.3.0.alpha.016'
560
-        );
561
-        return $this->get_activation_indicator_option_name();
562
-    }
563
-
564
-
565
-    /**
566
-     * When the addon is activated, this should be called to set a wordpress option that
567
-     * indicates it was activated. This is especially useful for detecting reactivations.
568
-     *
569
-     * @return bool
570
-     */
571
-    public function set_activation_indicator_option(): bool
572
-    {
573
-        // let's just handle this on the next request, ok? right now we're just not really ready
574
-        return update_option($this->get_activation_indicator_option_name(), true);
575
-    }
576
-
577
-
578
-    /**
579
-     * Gets the name of the wp option which is used to temporarily indicate that this addon was activated
580
-     *
581
-     * @return string
582
-     */
583
-    public function get_activation_indicator_option_name(): string
584
-    {
585
-        return 'ee_activation_' . $this->name();
586
-    }
587
-
588
-
589
-    /**
590
-     * Used by EE_System to set the request type of this addon. Should not be used by addon developers
591
-     *
592
-     * @param int $req_type
593
-     */
594
-    public function set_req_type(int $req_type)
595
-    {
596
-        $this->_req_type = $req_type;
597
-    }
598
-
599
-
600
-    /**
601
-     * Returns the request type of this addon (ie, EE_System::req_type_normal, EE_System::req_type_new_activation,
602
-     * EE_System::req_type_reactivation, EE_System::req_type_upgrade, or EE_System::req_type_downgrade). This is set by
603
-     * EE_System when it is checking for new install or upgrades of addons
604
-     */
605
-    public function detect_req_type($redetect = false): int
606
-    {
607
-        if ($this->_req_type === null || $redetect) {
608
-            $this->detect_activation_or_upgrade();
609
-        }
610
-        return $this->_req_type;
611
-    }
612
-
613
-
614
-    /**
615
-     * Detects the request type for this addon (whether it was just activated, upgrades, a normal request, etc.)
616
-     * Should only be called once per request
617
-     *
618
-     * @return void
619
-     * @throws EE_Error
620
-     */
621
-    public function detect_activation_or_upgrade()
622
-    {
623
-        $activation_history_for_addon = $this->get_activation_history();
624
-        $request_type                 = EE_System::detect_req_type_given_activation_history(
625
-            $activation_history_for_addon,
626
-            $this->get_activation_indicator_option_name(),
627
-            $this->version()
628
-        );
629
-        $this->set_req_type($request_type);
630
-        $classname = get_class($this);
631
-        switch ($request_type) {
632
-            case EE_System::req_type_new_activation:
633
-                do_action("AHEE__{$classname}__detect_activations_or_upgrades__new_activation");
634
-                do_action('AHEE__EE_Addon__detect_activations_or_upgrades__new_activation', $this);
635
-                $this->new_install();
636
-                $this->update_list_of_installed_versions($activation_history_for_addon);
637
-                break;
638
-            case EE_System::req_type_reactivation:
639
-                do_action("AHEE__{$classname}__detect_activations_or_upgrades__reactivation");
640
-                do_action('AHEE__EE_Addon__detect_activations_or_upgrades__reactivation', $this);
641
-                $this->reactivation();
642
-                $this->update_list_of_installed_versions($activation_history_for_addon);
643
-                break;
644
-            case EE_System::req_type_upgrade:
645
-                do_action("AHEE__{$classname}__detect_activations_or_upgrades__upgrade");
646
-                do_action('AHEE__EE_Addon__detect_activations_or_upgrades__upgrade', $this);
647
-                $this->upgrade();
648
-                $this->update_list_of_installed_versions($activation_history_for_addon);
649
-                break;
650
-            case EE_System::req_type_downgrade:
651
-                do_action("AHEE__{$classname}__detect_activations_or_upgrades__downgrade");
652
-                do_action('AHEE__EE_Addon__detect_activations_or_upgrades__downgrade', $this);
653
-                $this->downgrade();
654
-                $this->update_list_of_installed_versions($activation_history_for_addon);
655
-                break;
656
-            case EE_System::req_type_normal:
657
-            default:
658
-                break;
659
-        }
660
-
661
-        do_action("AHEE__{$classname}__detect_if_activation_or_upgrade__complete");
662
-    }
663
-
664
-
665
-    /**
666
-     * Updates the version history for this addon
667
-     *
668
-     * @param array  $version_history
669
-     * @param string $current_version_to_add
670
-     * @return bool success
671
-     */
672
-    public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null): bool
673
-    {
674
-        if (! $version_history) {
675
-            $version_history = $this->get_activation_history();
676
-        }
677
-        if ($current_version_to_add === null) {
678
-            $current_version_to_add = $this->version();
679
-        }
680
-        $version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
681
-        return update_option($this->get_activation_history_option_name(), $version_history);
682
-    }
683
-
684
-
685
-    /**
686
-     * Gets the name of the wp option that stores the activation history
687
-     * of this addon
688
-     *
689
-     * @return string
690
-     */
691
-    public function get_activation_history_option_name(): string
692
-    {
693
-        return self::ee_addon_version_history_option_prefix . $this->name();
694
-    }
695
-
696
-
697
-    /**
698
-     * Gets the wp option which stores the activation history for this addon
699
-     *
700
-     * @return array
701
-     */
702
-    public function get_activation_history(): array
703
-    {
704
-        return get_option($this->get_activation_history_option_name(), []);
705
-    }
706
-
707
-
708
-    /**
709
-     * @param string $config_section
710
-     */
711
-    public function set_config_section($config_section = '')
712
-    {
713
-        $this->_config_section = ! empty($config_section) ? $config_section : 'addons';
714
-    }
715
-
716
-
717
-    /**
718
-     * Sets the filepath to the main plugin file
719
-     *
720
-     * @param string $filepath
721
-     */
722
-    public function set_main_plugin_file(string $filepath)
723
-    {
724
-        $this->_main_plugin_file = $filepath;
725
-    }
726
-
727
-
728
-    /**
729
-     * gets the filepath to teh main file
730
-     *
731
-     * @return string
732
-     */
733
-    public function get_main_plugin_file(): string
734
-    {
735
-        return $this->_main_plugin_file;
736
-    }
737
-
738
-
739
-    /**
740
-     * Gets the filename (no path) of the main file (the main file loaded
741
-     * by WP)
742
-     *
743
-     * @return string
744
-     */
745
-    public function get_main_plugin_file_basename(): string
746
-    {
747
-        return plugin_basename($this->get_main_plugin_file());
748
-    }
749
-
750
-
751
-    /**
752
-     * Gets the folder name which contains the main plugin file
753
-     *
754
-     * @return string
755
-     */
756
-    public function get_main_plugin_file_dirname(): string
757
-    {
758
-        return dirname($this->get_main_plugin_file());
759
-    }
760
-
761
-
762
-    /**
763
-     * sets hooks used in the admin
764
-     *
765
-     * @return void
766
-     */
767
-    public function admin_init()
768
-    {
769
-        // is admin and not in M-Mode ?
770
-        if (is_admin() && ! EE_Maintenance_Mode::instance()->level()) {
771
-            add_filter('plugin_action_links', [$this, 'plugin_action_links'], 10, 2);
772
-            add_filter('after_plugin_row_' . $this->_plugin_basename, [$this, 'after_plugin_row'], 10, 3);
773
-        }
774
-    }
775
-
776
-
777
-    /**
778
-     * plugin_actions
779
-     * Add a settings link to the Plugins page, so people can go straight from the plugin page to the settings page.
780
-     *
781
-     * @param array  $links
782
-     * @param string $file
783
-     * @return array
784
-     */
785
-    public function plugin_action_links(array $links, string $file): array
786
-    {
787
-        if ($file === $this->plugin_basename() && $this->plugin_action_slug() !== '') {
788
-            // before other links
789
-            array_unshift(
790
-                $links,
791
-                '<a href="admin.php?page=' . $this->plugin_action_slug() . '">'
792
-                . esc_html__('Settings', 'event_espresso')
793
-                . '</a>'
794
-            );
795
-        }
796
-        return $links;
797
-    }
798
-
799
-
800
-    /**
801
-     * after_plugin_row
802
-     * Add additional content to the plugins page plugin row
803
-     * Inserts another row
804
-     *
805
-     * @param string $plugin_file
806
-     * @param array  $plugin_data
807
-     * @param string $status
808
-     * @return void
809
-     */
810
-    public function after_plugin_row(string $plugin_file, array $plugin_data, string $status)
811
-    {
812
-        $after_plugin_row = '';
813
-        $plugins_page_row = $this->get_plugins_page_row();
814
-        if (! empty($plugins_page_row) && $plugin_file === $this->plugin_basename()) {
815
-            $class       = $status ? 'active' : 'inactive';
816
-            $link_text   = isset($plugins_page_row['link_text']) ? $plugins_page_row['link_text'] : '';
817
-            $link_url    = isset($plugins_page_row['link_url']) ? $plugins_page_row['link_url'] : '';
818
-            $description = isset($plugins_page_row['description'])
819
-                ? $plugins_page_row['description']
820
-                : '';
821
-            if (! empty($link_text) && ! empty($link_url) && ! empty($description)) {
822
-                $after_plugin_row .= '<tr id="' . sanitize_title($plugin_file) . '-ee-addon" class="' . $class . '">';
823
-                $after_plugin_row .= '<th class="check-column" scope="row"></th>';
824
-                $after_plugin_row .= '<td class="ee-addon-upsell-info-title-td plugin-title column-primary">';
825
-                $after_plugin_row .= '<p class="ee-addon-upsell-info-dv">
407
+			EEH_Activation::initialize_db_content();
408
+			/** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
409
+			$rewrite_rules = LoaderFactory::getLoader()->getShared(
410
+				'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
411
+			);
412
+			$rewrite_rules->flushRewriteRules();
413
+			// in case there are lots of addons being activated at once, let's force garbage collection
414
+			// to help avoid memory limit errors
415
+			// EEH_Debug_Tools::instance()->measure_memory( 'db content initialized for ' . get_class( $this), true );
416
+			gc_collect_cycles();
417
+		} else {
418
+			// ask the data migration manager to init this addon's data
419
+			// when migrations are finished because we can't do it now
420
+			EE_Data_Migration_Manager::instance()->enqueue_db_initialization_for($this->name());
421
+		}
422
+	}
423
+
424
+
425
+	/**
426
+	 * Used to setup this addon's database tables, but not necessarily any default
427
+	 * data in them. The default is to actually use the most up-to-date data migration script
428
+	 * for this addon, and just use its schema_changes_before_migration() and schema_changes_after_migration()
429
+	 * methods to setup the db.
430
+	 *
431
+	 * @throws EE_Error
432
+	 * @throws ReflectionException
433
+	 */
434
+	public function initialize_db()
435
+	{
436
+		// find the migration script that sets the database to be compatible with the code
437
+		$current_dms_name = EE_Data_Migration_Manager::instance()->get_most_up_to_date_dms($this->name());
438
+		if ($current_dms_name) {
439
+			$current_data_migration_script = EE_Registry::instance()->load_dms($current_dms_name);
440
+			$current_data_migration_script->set_migrating(false);
441
+			$current_data_migration_script->schema_changes_before_migration();
442
+			$current_data_migration_script->schema_changes_after_migration();
443
+			if ($current_data_migration_script->get_errors()) {
444
+				foreach ($current_data_migration_script->get_errors() as $error) {
445
+					EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
446
+				}
447
+			}
448
+		}
449
+		// if not DMS was found that should be ok. This addon just doesn't require any database changes
450
+		EE_Data_Migration_Manager::instance()->update_current_database_state_to(
451
+			[
452
+				'slug'    => $this->name(),
453
+				'version' => $this->version(),
454
+			]
455
+		);
456
+	}
457
+
458
+
459
+	/**
460
+	 * If you want to setup default data for the addon, override this method, and call
461
+	 * parent::initialize_default_data() from within it. This is normally called
462
+	 * from EE_Addon::initialize_db_if_no_migrations_required(), just after EE_Addon::initialize_db()
463
+	 * and should verify default data is present (but this is also called
464
+	 * on reactivations and just after migrations, so please verify you actually want
465
+	 * to ADD default data, because it may already be present).
466
+	 * However, please call this parent (currently it just fires a hook which other
467
+	 * addons may be depending on)
468
+	 */
469
+	public function initialize_default_data()
470
+	{
471
+		/**
472
+		 * Called when an addon is ensuring its default data is set (possibly called
473
+		 * on a reactivation, so first check for the absence of other data before setting
474
+		 * default data)
475
+		 *
476
+		 * @param EE_Addon $addon the addon that called this
477
+		 */
478
+		do_action('AHEE__EE_Addon__initialize_default_data__begin', $this);
479
+		// override to insert default data. It is safe to use the models here
480
+		// because the site should not be in maintenance mode
481
+	}
482
+
483
+
484
+	/**
485
+	 * EE Core detected that this addon has been upgraded. We should check if there
486
+	 * are any new migration scripts, and if so put the site into maintenance mode until
487
+	 * they're ran
488
+	 *
489
+	 * @return void
490
+	 * @throws EE_Error
491
+	 */
492
+	public function upgrade()
493
+	{
494
+		$classname = get_class($this);
495
+		do_action("AHEE__{$classname}__upgrade");
496
+		do_action('AHEE__EE_Addon__upgrade', $this);
497
+		EE_Maintenance_Mode::instance()->set_maintenance_mode_if_db_old();
498
+		// also it's possible there is new default data that needs to be added
499
+		add_action(
500
+			'AHEE__EE_System__perform_activations_upgrades_and_migrations',
501
+			[$this, 'initialize_db_if_no_migrations_required']
502
+		);
503
+	}
504
+
505
+
506
+	/**
507
+	 * If Core detects this addon has been downgraded, you may want to invoke some special logic here.
508
+	 */
509
+	public function downgrade()
510
+	{
511
+		$classname = get_class($this);
512
+		do_action("AHEE__{$classname}__downgrade");
513
+		do_action('AHEE__EE_Addon__downgrade', $this);
514
+		// it's possible there's old default data that needs to be double-checked
515
+		add_action(
516
+			'AHEE__EE_System__perform_activations_upgrades_and_migrations',
517
+			[$this, 'initialize_db_if_no_migrations_required']
518
+		);
519
+	}
520
+
521
+
522
+	/**
523
+	 * set_db_update_option_name
524
+	 * Until we do something better, we'll just check for migration scripts upon
525
+	 * plugin activation only. In the future, we'll want to do it on plugin updates too
526
+	 *
527
+	 * @return bool
528
+	 */
529
+	public function set_db_update_option_name(): bool
530
+	{
531
+		EE_Error::doing_it_wrong(
532
+			__FUNCTION__,
533
+			esc_html__(
534
+				'EE_Addon::set_db_update_option_name was renamed to EE_Addon::set_activation_indicator_option',
535
+				'event_espresso'
536
+			),
537
+			'4.3.0.alpha.016'
538
+		);
539
+		// let's just handle this on the next request, ok? right now we're just not really ready
540
+		return $this->set_activation_indicator_option();
541
+	}
542
+
543
+
544
+	/**
545
+	 * Returns the name of the activation indicator option
546
+	 * (an option which is set temporarily to indicate that this addon was just activated)
547
+	 *
548
+	 * @return string
549
+	 * @deprecated since version 4.3.0.alpha.016
550
+	 */
551
+	public function get_db_update_option_name(): string
552
+	{
553
+		EE_Error::doing_it_wrong(
554
+			__FUNCTION__,
555
+			esc_html__(
556
+				'EE_Addon::get_db_update_option was renamed to EE_Addon::get_activation_indicator_option_name',
557
+				'event_espresso'
558
+			),
559
+			'4.3.0.alpha.016'
560
+		);
561
+		return $this->get_activation_indicator_option_name();
562
+	}
563
+
564
+
565
+	/**
566
+	 * When the addon is activated, this should be called to set a wordpress option that
567
+	 * indicates it was activated. This is especially useful for detecting reactivations.
568
+	 *
569
+	 * @return bool
570
+	 */
571
+	public function set_activation_indicator_option(): bool
572
+	{
573
+		// let's just handle this on the next request, ok? right now we're just not really ready
574
+		return update_option($this->get_activation_indicator_option_name(), true);
575
+	}
576
+
577
+
578
+	/**
579
+	 * Gets the name of the wp option which is used to temporarily indicate that this addon was activated
580
+	 *
581
+	 * @return string
582
+	 */
583
+	public function get_activation_indicator_option_name(): string
584
+	{
585
+		return 'ee_activation_' . $this->name();
586
+	}
587
+
588
+
589
+	/**
590
+	 * Used by EE_System to set the request type of this addon. Should not be used by addon developers
591
+	 *
592
+	 * @param int $req_type
593
+	 */
594
+	public function set_req_type(int $req_type)
595
+	{
596
+		$this->_req_type = $req_type;
597
+	}
598
+
599
+
600
+	/**
601
+	 * Returns the request type of this addon (ie, EE_System::req_type_normal, EE_System::req_type_new_activation,
602
+	 * EE_System::req_type_reactivation, EE_System::req_type_upgrade, or EE_System::req_type_downgrade). This is set by
603
+	 * EE_System when it is checking for new install or upgrades of addons
604
+	 */
605
+	public function detect_req_type($redetect = false): int
606
+	{
607
+		if ($this->_req_type === null || $redetect) {
608
+			$this->detect_activation_or_upgrade();
609
+		}
610
+		return $this->_req_type;
611
+	}
612
+
613
+
614
+	/**
615
+	 * Detects the request type for this addon (whether it was just activated, upgrades, a normal request, etc.)
616
+	 * Should only be called once per request
617
+	 *
618
+	 * @return void
619
+	 * @throws EE_Error
620
+	 */
621
+	public function detect_activation_or_upgrade()
622
+	{
623
+		$activation_history_for_addon = $this->get_activation_history();
624
+		$request_type                 = EE_System::detect_req_type_given_activation_history(
625
+			$activation_history_for_addon,
626
+			$this->get_activation_indicator_option_name(),
627
+			$this->version()
628
+		);
629
+		$this->set_req_type($request_type);
630
+		$classname = get_class($this);
631
+		switch ($request_type) {
632
+			case EE_System::req_type_new_activation:
633
+				do_action("AHEE__{$classname}__detect_activations_or_upgrades__new_activation");
634
+				do_action('AHEE__EE_Addon__detect_activations_or_upgrades__new_activation', $this);
635
+				$this->new_install();
636
+				$this->update_list_of_installed_versions($activation_history_for_addon);
637
+				break;
638
+			case EE_System::req_type_reactivation:
639
+				do_action("AHEE__{$classname}__detect_activations_or_upgrades__reactivation");
640
+				do_action('AHEE__EE_Addon__detect_activations_or_upgrades__reactivation', $this);
641
+				$this->reactivation();
642
+				$this->update_list_of_installed_versions($activation_history_for_addon);
643
+				break;
644
+			case EE_System::req_type_upgrade:
645
+				do_action("AHEE__{$classname}__detect_activations_or_upgrades__upgrade");
646
+				do_action('AHEE__EE_Addon__detect_activations_or_upgrades__upgrade', $this);
647
+				$this->upgrade();
648
+				$this->update_list_of_installed_versions($activation_history_for_addon);
649
+				break;
650
+			case EE_System::req_type_downgrade:
651
+				do_action("AHEE__{$classname}__detect_activations_or_upgrades__downgrade");
652
+				do_action('AHEE__EE_Addon__detect_activations_or_upgrades__downgrade', $this);
653
+				$this->downgrade();
654
+				$this->update_list_of_installed_versions($activation_history_for_addon);
655
+				break;
656
+			case EE_System::req_type_normal:
657
+			default:
658
+				break;
659
+		}
660
+
661
+		do_action("AHEE__{$classname}__detect_if_activation_or_upgrade__complete");
662
+	}
663
+
664
+
665
+	/**
666
+	 * Updates the version history for this addon
667
+	 *
668
+	 * @param array  $version_history
669
+	 * @param string $current_version_to_add
670
+	 * @return bool success
671
+	 */
672
+	public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null): bool
673
+	{
674
+		if (! $version_history) {
675
+			$version_history = $this->get_activation_history();
676
+		}
677
+		if ($current_version_to_add === null) {
678
+			$current_version_to_add = $this->version();
679
+		}
680
+		$version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
681
+		return update_option($this->get_activation_history_option_name(), $version_history);
682
+	}
683
+
684
+
685
+	/**
686
+	 * Gets the name of the wp option that stores the activation history
687
+	 * of this addon
688
+	 *
689
+	 * @return string
690
+	 */
691
+	public function get_activation_history_option_name(): string
692
+	{
693
+		return self::ee_addon_version_history_option_prefix . $this->name();
694
+	}
695
+
696
+
697
+	/**
698
+	 * Gets the wp option which stores the activation history for this addon
699
+	 *
700
+	 * @return array
701
+	 */
702
+	public function get_activation_history(): array
703
+	{
704
+		return get_option($this->get_activation_history_option_name(), []);
705
+	}
706
+
707
+
708
+	/**
709
+	 * @param string $config_section
710
+	 */
711
+	public function set_config_section($config_section = '')
712
+	{
713
+		$this->_config_section = ! empty($config_section) ? $config_section : 'addons';
714
+	}
715
+
716
+
717
+	/**
718
+	 * Sets the filepath to the main plugin file
719
+	 *
720
+	 * @param string $filepath
721
+	 */
722
+	public function set_main_plugin_file(string $filepath)
723
+	{
724
+		$this->_main_plugin_file = $filepath;
725
+	}
726
+
727
+
728
+	/**
729
+	 * gets the filepath to teh main file
730
+	 *
731
+	 * @return string
732
+	 */
733
+	public function get_main_plugin_file(): string
734
+	{
735
+		return $this->_main_plugin_file;
736
+	}
737
+
738
+
739
+	/**
740
+	 * Gets the filename (no path) of the main file (the main file loaded
741
+	 * by WP)
742
+	 *
743
+	 * @return string
744
+	 */
745
+	public function get_main_plugin_file_basename(): string
746
+	{
747
+		return plugin_basename($this->get_main_plugin_file());
748
+	}
749
+
750
+
751
+	/**
752
+	 * Gets the folder name which contains the main plugin file
753
+	 *
754
+	 * @return string
755
+	 */
756
+	public function get_main_plugin_file_dirname(): string
757
+	{
758
+		return dirname($this->get_main_plugin_file());
759
+	}
760
+
761
+
762
+	/**
763
+	 * sets hooks used in the admin
764
+	 *
765
+	 * @return void
766
+	 */
767
+	public function admin_init()
768
+	{
769
+		// is admin and not in M-Mode ?
770
+		if (is_admin() && ! EE_Maintenance_Mode::instance()->level()) {
771
+			add_filter('plugin_action_links', [$this, 'plugin_action_links'], 10, 2);
772
+			add_filter('after_plugin_row_' . $this->_plugin_basename, [$this, 'after_plugin_row'], 10, 3);
773
+		}
774
+	}
775
+
776
+
777
+	/**
778
+	 * plugin_actions
779
+	 * Add a settings link to the Plugins page, so people can go straight from the plugin page to the settings page.
780
+	 *
781
+	 * @param array  $links
782
+	 * @param string $file
783
+	 * @return array
784
+	 */
785
+	public function plugin_action_links(array $links, string $file): array
786
+	{
787
+		if ($file === $this->plugin_basename() && $this->plugin_action_slug() !== '') {
788
+			// before other links
789
+			array_unshift(
790
+				$links,
791
+				'<a href="admin.php?page=' . $this->plugin_action_slug() . '">'
792
+				. esc_html__('Settings', 'event_espresso')
793
+				. '</a>'
794
+			);
795
+		}
796
+		return $links;
797
+	}
798
+
799
+
800
+	/**
801
+	 * after_plugin_row
802
+	 * Add additional content to the plugins page plugin row
803
+	 * Inserts another row
804
+	 *
805
+	 * @param string $plugin_file
806
+	 * @param array  $plugin_data
807
+	 * @param string $status
808
+	 * @return void
809
+	 */
810
+	public function after_plugin_row(string $plugin_file, array $plugin_data, string $status)
811
+	{
812
+		$after_plugin_row = '';
813
+		$plugins_page_row = $this->get_plugins_page_row();
814
+		if (! empty($plugins_page_row) && $plugin_file === $this->plugin_basename()) {
815
+			$class       = $status ? 'active' : 'inactive';
816
+			$link_text   = isset($plugins_page_row['link_text']) ? $plugins_page_row['link_text'] : '';
817
+			$link_url    = isset($plugins_page_row['link_url']) ? $plugins_page_row['link_url'] : '';
818
+			$description = isset($plugins_page_row['description'])
819
+				? $plugins_page_row['description']
820
+				: '';
821
+			if (! empty($link_text) && ! empty($link_url) && ! empty($description)) {
822
+				$after_plugin_row .= '<tr id="' . sanitize_title($plugin_file) . '-ee-addon" class="' . $class . '">';
823
+				$after_plugin_row .= '<th class="check-column" scope="row"></th>';
824
+				$after_plugin_row .= '<td class="ee-addon-upsell-info-title-td plugin-title column-primary">';
825
+				$after_plugin_row .= '<p class="ee-addon-upsell-info-dv">
826 826
 	                <a class="ee-button" href="' . esc_url_raw($link_url) . '">'
827
-                    . $link_text
828
-                    . ' &nbsp;<span class="dashicons dashicons-arrow-right-alt2" style="margin:0;"></span>'
829
-                    . '</a>
827
+					. $link_text
828
+					. ' &nbsp;<span class="dashicons dashicons-arrow-right-alt2" style="margin:0;"></span>'
829
+					. '</a>
830 830
                 </p>';
831
-                $after_plugin_row .= '</td>';
832
-                $after_plugin_row .= '<td class="ee-addon-upsell-info-desc-td column-description desc">';
833
-                $after_plugin_row .= $description;
834
-                $after_plugin_row .= '</td>';
835
-                $after_plugin_row .= '</tr>';
836
-            } else {
837
-                $after_plugin_row .= $description;
838
-            }
839
-        }
840
-
841
-        echo wp_kses($after_plugin_row, AllowedTags::getAllowedTags());
842
-    }
843
-
844
-
845
-    /**
846
-     * A safe space for addons to add additional logic like setting hooks that need to be set early in the request.
847
-     * Child classes that have logic like that to run can override this method declaration.  This was not made abstract
848
-     * for back compat reasons.
849
-     *
850
-     * This will fire on the `AHEE__EE_System__load_espresso_addons__complete` hook at priority 999.
851
-     *
852
-     * It is recommended, if client code is `de-registering` an add-on, then do it on the
853
-     * `AHEE__EE_System__load_espresso_addons__complete` hook before priority 999 so as to ensure any code logic in this
854
-     * callback does not get run/set in that request.
855
-     *
856
-     * Also, keep in mind that if a registered add-on happens to be deactivated via
857
-     * EE_System::_deactivate_incompatible_addons() because its incompatible, any code executed in this method
858
-     * (including setting hooks etc) will have executed before the plugin was deactivated.  If you use
859
-     * `after_registration` to set any filter and/or action hooks and want to ensure they are removed on this add-on's
860
-     * deactivation, you can override `EE_Addon::deactivation` and unset your hooks and filters there.  Just remember
861
-     * to call `parent::deactivation`.
862
-     *
863
-     * @since 4.9.26
864
-     */
865
-    public function after_registration()
866
-    {
867
-        // cricket chirp... cricket chirp...
868
-    }
869
-
870
-
871
-    /**
872
-     * @return string
873
-     */
874
-    public function getPueSlug(): string
875
-    {
876
-        return $this->pue_slug;
877
-    }
878
-
879
-
880
-    /**
881
-     * @param string $pue_slug
882
-     */
883
-    public function setPueSlug(string $pue_slug)
884
-    {
885
-        $this->pue_slug = $pue_slug;
886
-    }
831
+				$after_plugin_row .= '</td>';
832
+				$after_plugin_row .= '<td class="ee-addon-upsell-info-desc-td column-description desc">';
833
+				$after_plugin_row .= $description;
834
+				$after_plugin_row .= '</td>';
835
+				$after_plugin_row .= '</tr>';
836
+			} else {
837
+				$after_plugin_row .= $description;
838
+			}
839
+		}
840
+
841
+		echo wp_kses($after_plugin_row, AllowedTags::getAllowedTags());
842
+	}
843
+
844
+
845
+	/**
846
+	 * A safe space for addons to add additional logic like setting hooks that need to be set early in the request.
847
+	 * Child classes that have logic like that to run can override this method declaration.  This was not made abstract
848
+	 * for back compat reasons.
849
+	 *
850
+	 * This will fire on the `AHEE__EE_System__load_espresso_addons__complete` hook at priority 999.
851
+	 *
852
+	 * It is recommended, if client code is `de-registering` an add-on, then do it on the
853
+	 * `AHEE__EE_System__load_espresso_addons__complete` hook before priority 999 so as to ensure any code logic in this
854
+	 * callback does not get run/set in that request.
855
+	 *
856
+	 * Also, keep in mind that if a registered add-on happens to be deactivated via
857
+	 * EE_System::_deactivate_incompatible_addons() because its incompatible, any code executed in this method
858
+	 * (including setting hooks etc) will have executed before the plugin was deactivated.  If you use
859
+	 * `after_registration` to set any filter and/or action hooks and want to ensure they are removed on this add-on's
860
+	 * deactivation, you can override `EE_Addon::deactivation` and unset your hooks and filters there.  Just remember
861
+	 * to call `parent::deactivation`.
862
+	 *
863
+	 * @since 4.9.26
864
+	 */
865
+	public function after_registration()
866
+	{
867
+		// cricket chirp... cricket chirp...
868
+	}
869
+
870
+
871
+	/**
872
+	 * @return string
873
+	 */
874
+	public function getPueSlug(): string
875
+	{
876
+		return $this->pue_slug;
877
+	}
878
+
879
+
880
+	/**
881
+	 * @param string $pue_slug
882
+	 */
883
+	public function setPueSlug(string $pue_slug)
884
+	{
885
+		$this->pue_slug = $pue_slug;
886
+	}
887 887
 }
Please login to merge, or discard this patch.
core/libraries/form_sections/base/EE_Form_Section_Proper.form.php 1 patch
Indentation   +1532 added lines, -1532 removed lines patch added patch discarded remove patch
@@ -15,1536 +15,1536 @@
 block discarded – undo
15 15
  */
16 16
 class EE_Form_Section_Proper extends EE_Form_Section_Validatable
17 17
 {
18
-    const SUBMITTED_FORM_DATA_SSN_KEY = 'submitted_form_data';
19
-
20
-    /**
21
-     * Subsections
22
-     *
23
-     * @var EE_Form_Section_Validatable[]
24
-     */
25
-    protected $_subsections = array();
26
-
27
-    /**
28
-     * Strategy for laying out the form
29
-     *
30
-     * @var EE_Form_Section_Layout_Base
31
-     */
32
-    protected $_layout_strategy;
33
-
34
-    /**
35
-     * Whether or not this form has received and validated a form submission yet
36
-     *
37
-     * @var boolean
38
-     */
39
-    protected $_received_submission = false;
40
-
41
-    /**
42
-     * message displayed to users upon successful form submission
43
-     *
44
-     * @var string
45
-     */
46
-    protected $_form_submission_success_message = '';
47
-
48
-    /**
49
-     * message displayed to users upon unsuccessful form submission
50
-     *
51
-     * @var string
52
-     */
53
-    protected $_form_submission_error_message = '';
54
-
55
-    /**
56
-     * @var array like post / request
57
-     */
58
-    protected $cached_request_data;
59
-
60
-    /**
61
-     * Stores whether this form (and its sub-sections) were found to be valid or not.
62
-     * Starts off as null, but once the form is validated, it set to either true or false
63
-     * @var boolean|null
64
-     */
65
-    protected $is_valid;
66
-
67
-    /**
68
-     * Stores all the data that will localized for form validation
69
-     *
70
-     * @var array
71
-     */
72
-    protected static $_js_localization = array();
73
-
74
-    /**
75
-     * whether or not the form's localized validation JS vars have been set
76
-     *
77
-     * @type boolean
78
-     */
79
-    protected static $_scripts_localized = false;
80
-
81
-
82
-    /**
83
-     * when constructing a proper form section, calls _construct_finalize on children
84
-     * so that they know who their parent is, and what name they've been given.
85
-     *
86
-     * @param array[] $options_array   {
87
-     * @type          $subsections     EE_Form_Section_Validatable[] where keys are the section's name
88
-     * @type          $include         string[] numerically-indexed where values are section names to be included,
89
-     *                                 and in that order. This is handy if you want
90
-     *                                 the subsections to be ordered differently than the default, and if you override
91
-     *                                 which fields are shown
92
-     * @type          $exclude         string[] values are subsections to be excluded. This is handy if you want
93
-     *                                 to remove certain default subsections (note: if you specify BOTH 'include' AND
94
-     *                                 'exclude', the inclusions will be applied first, and the exclusions will exclude
95
-     *                                 items from that list of inclusions)
96
-     * @type          $layout_strategy EE_Form_Section_Layout_Base strategy for laying out the form
97
-     *                                 } @see EE_Form_Section_Validatable::__construct()
98
-     * @throws EE_Error
99
-     */
100
-    public function __construct($options_array = array())
101
-    {
102
-        $options_array = (array) apply_filters(
103
-            'FHEE__EE_Form_Section_Proper___construct__options_array',
104
-            $options_array,
105
-            $this
106
-        );
107
-        // call parent first, as it may be setting the name
108
-        parent::__construct($options_array);
109
-        // if they've included subsections in the constructor, add them now
110
-        if (isset($options_array['include'])) {
111
-            // we are going to make sure we ONLY have those subsections to include
112
-            // AND we are going to make sure they're in that specified order
113
-            $reordered_subsections = array();
114
-            foreach ($options_array['include'] as $input_name) {
115
-                if (isset($this->_subsections[ $input_name ])) {
116
-                    $reordered_subsections[ $input_name ] = $this->_subsections[ $input_name ];
117
-                }
118
-            }
119
-            $this->_subsections = $reordered_subsections;
120
-        }
121
-        if (isset($options_array['exclude'])) {
122
-            $exclude            = $options_array['exclude'];
123
-            $this->_subsections = array_diff_key($this->_subsections, array_flip($exclude));
124
-        }
125
-        if (isset($options_array['layout_strategy'])) {
126
-            $this->_layout_strategy = $options_array['layout_strategy'];
127
-        }
128
-        if (! $this->_layout_strategy) {
129
-            $this->_layout_strategy = is_admin() ? new EE_Admin_Two_Column_Layout() : new EE_Two_Column_Layout();
130
-        }
131
-        $this->_layout_strategy->_construct_finalize($this);
132
-        // ok so we are definitely going to want the forms JS,
133
-        // so enqueue it or remember to enqueue it during wp_enqueue_scripts
134
-        if (did_action('wp_enqueue_scripts') || did_action('admin_enqueue_scripts')) {
135
-            // ok so they've constructed this object after when they should have.
136
-            // just enqueue the generic form scripts and initialize the form immediately in the JS
137
-            EE_Form_Section_Proper::wp_enqueue_scripts(true);
138
-        } else {
139
-            add_action('wp_enqueue_scripts', array('EE_Form_Section_Proper', 'wp_enqueue_scripts'));
140
-            add_action('admin_enqueue_scripts', array('EE_Form_Section_Proper', 'wp_enqueue_scripts'));
141
-        }
142
-        add_action('wp_footer', array($this, 'ensure_scripts_localized'), 1);
143
-        /**
144
-         * Gives other plugins a chance to hook in before construct finalize is called.
145
-         * The form probably doesn't yet have a parent form section.
146
-         * Since 4.9.32, when this action was introduced, this is the best place to add a subsection onto a form,
147
-         * assuming you don't care what the form section's name, HTML ID, or HTML name etc are.
148
-         * Also see AHEE__EE_Form_Section_Proper___construct_finalize__end
149
-         *
150
-         * @since 4.9.32
151
-         * @param EE_Form_Section_Proper $this          before __construct is done, but all of its logic,
152
-         *                                              except maybe calling _construct_finalize has been done
153
-         * @param array                  $options_array options passed into the constructor
154
-         */
155
-        do_action(
156
-            'AHEE__EE_Form_Input_Base___construct__before_construct_finalize_called',
157
-            $this,
158
-            $options_array
159
-        );
160
-        if (isset($options_array['name'])) {
161
-            $this->_construct_finalize(null, $options_array['name']);
162
-        }
163
-    }
164
-
165
-
166
-    /**
167
-     * Finishes construction given the parent form section and this form section's name
168
-     *
169
-     * @param EE_Form_Section_Proper $parent_form_section
170
-     * @param string                 $name
171
-     * @throws EE_Error
172
-     */
173
-    public function _construct_finalize($parent_form_section, $name)
174
-    {
175
-        parent::_construct_finalize($parent_form_section, $name);
176
-        $this->_set_default_name_if_empty();
177
-        $this->_set_default_html_id_if_empty();
178
-        foreach ($this->_subsections as $subsection_name => $subsection) {
179
-            if ($subsection instanceof EE_Form_Section_Base) {
180
-                $subsection->_construct_finalize($this, $subsection_name);
181
-            } else {
182
-                throw new EE_Error(
183
-                    sprintf(
184
-                        esc_html__(
185
-                            'Subsection "%s" is not an instanceof EE_Form_Section_Base on form "%s". It is a "%s"',
186
-                            'event_espresso'
187
-                        ),
188
-                        $subsection_name,
189
-                        get_class($this),
190
-                        $subsection ? get_class($subsection) : esc_html__('NULL', 'event_espresso')
191
-                    )
192
-                );
193
-            }
194
-        }
195
-        /**
196
-         * Action performed just after form has been given a name (and HTML ID etc) and is fully constructed.
197
-         * If you have code that should modify the form and needs it and its subsections to have a name, HTML ID
198
-         * (or other attributes derived from the name like the HTML label id, etc), this is where it should be done.
199
-         * This might only happen just before displaying the form, or just before it receives form submission data.
200
-         * If you need to modify the form or its subsections before _construct_finalize is called on it (and we've
201
-         * ensured it has a name, HTML IDs, etc
202
-         *
203
-         * @param EE_Form_Section_Proper      $this
204
-         * @param EE_Form_Section_Proper|null $parent_form_section
205
-         * @param string                      $name
206
-         */
207
-        do_action(
208
-            'AHEE__EE_Form_Section_Proper___construct_finalize__end',
209
-            $this,
210
-            $parent_form_section,
211
-            $name
212
-        );
213
-    }
214
-
215
-
216
-    /**
217
-     * Gets the layout strategy for this form section
218
-     *
219
-     * @return EE_Form_Section_Layout_Base
220
-     */
221
-    public function get_layout_strategy()
222
-    {
223
-        return $this->_layout_strategy;
224
-    }
225
-
226
-
227
-    /**
228
-     * Gets the HTML for a single input for this form section according
229
-     * to the layout strategy
230
-     *
231
-     * @param EE_Form_Input_Base $input
232
-     * @return string
233
-     */
234
-    public function get_html_for_input($input)
235
-    {
236
-        return $this->_layout_strategy->layout_input($input);
237
-    }
238
-
239
-
240
-    /**
241
-     * was_submitted - checks if form inputs are present in request data
242
-     * Basically an alias for form_data_present_in() (which is used by both
243
-     * proper form sections and form inputs)
244
-     *
245
-     * @param null $form_data
246
-     * @return boolean
247
-     * @throws EE_Error
248
-     */
249
-    public function was_submitted($form_data = null)
250
-    {
251
-        return $this->form_data_present_in($form_data);
252
-    }
253
-
254
-    /**
255
-     * Gets the cached request data; but if there is none, or $req_data was set with
256
-     * something different, refresh the cache, and then return it
257
-     * @param null $req_data
258
-     * @return array
259
-     */
260
-    protected function getCachedRequest($req_data = null)
261
-    {
262
-        if (
263
-            $this->cached_request_data === null
264
-            || (
265
-                $req_data !== null
266
-                && $req_data !== $this->cached_request_data
267
-            )
268
-        ) {
269
-            $req_data = apply_filters(
270
-                'FHEE__EE_Form_Section_Proper__receive_form_submission__req_data',
271
-                $req_data,
272
-                $this
273
-            );
274
-            if ($req_data === null) {
275
-                /** @var RequestInterface $request */
276
-                $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
277
-                $req_data = $request->requestParams();
278
-            }
279
-            $req_data = apply_filters(
280
-                'FHEE__EE_Form_Section_Proper__receive_form_submission__request_data',
281
-                $req_data,
282
-                $this
283
-            );
284
-            $this->cached_request_data = (array) $req_data;
285
-        }
286
-        return $this->cached_request_data;
287
-    }
288
-
289
-
290
-    /**
291
-     * After the form section is initially created, call this to sanitize the data in the submission
292
-     * which relates to this form section, validate it, and set it as properties on the form.
293
-     *
294
-     * @param array|null $req_data should usually be post data (the default).
295
-     *                             However, you CAN supply a different array.
296
-     *                             Consider using set_defaults() instead however.
297
-     *                             (If you rendered the form in the page using $form_x->get_html()
298
-     *                             the inputs will have the correct name in the request data for this function
299
-     *                             to find them and populate the form with them.
300
-     *                             If you have a flat form (with only input subsections),
301
-     *                             you can supply a flat array where keys
302
-     *                             are the form input names and values are their values)
303
-     * @param boolean    $validate whether or not to perform validation on this data. Default is,
304
-     *                             of course, to validate that data, and set errors on the invalid values.
305
-     *                             But if the data has already been validated
306
-     *                             (eg you validated the data then stored it in the DB)
307
-     *                             you may want to skip this step.
308
-     * @throws InvalidArgumentException
309
-     * @throws InvalidInterfaceException
310
-     * @throws InvalidDataTypeException
311
-     * @throws EE_Error
312
-     */
313
-    public function receive_form_submission($req_data = null, $validate = true)
314
-    {
315
-        $req_data = $this->getCachedRequest($req_data);
316
-        $this->_normalize($req_data);
317
-        if ($validate) {
318
-            $this->_validate();
319
-            // if it's invalid, we're going to want to re-display so remember what they submitted
320
-            if (! $this->is_valid()) {
321
-                $this->store_submitted_form_data_in_session();
322
-            }
323
-        }
324
-        if ($this->submission_error_message() === '' && ! $this->is_valid()) {
325
-            $this->set_submission_error_message();
326
-        }
327
-        do_action(
328
-            'AHEE__EE_Form_Section_Proper__receive_form_submission__end',
329
-            $req_data,
330
-            $this,
331
-            $validate
332
-        );
333
-    }
334
-
335
-
336
-    /**
337
-     * caches the originally submitted input values in the session
338
-     * so that they can be used to repopulate the form if it failed validation
339
-     *
340
-     * @return boolean whether or not the data was successfully stored in the session
341
-     * @throws InvalidArgumentException
342
-     * @throws InvalidInterfaceException
343
-     * @throws InvalidDataTypeException
344
-     * @throws EE_Error
345
-     */
346
-    protected function store_submitted_form_data_in_session()
347
-    {
348
-        $session = EE_Registry::instance()->SSN;
349
-        if ($session instanceof EE_Session) {
350
-            return EE_Registry::instance()->SSN->set_session_data(
351
-                [
352
-                    EE_Form_Section_Proper::SUBMITTED_FORM_DATA_SSN_KEY => $this->submitted_values(true),
353
-                ]
354
-            );
355
-        }
356
-        return false;
357
-    }
358
-
359
-
360
-    /**
361
-     * retrieves the originally submitted input values in the session
362
-     * so that they can be used to repopulate the form if it failed validation
363
-     *
364
-     * @return array
365
-     * @throws InvalidArgumentException
366
-     * @throws InvalidInterfaceException
367
-     * @throws InvalidDataTypeException
368
-     */
369
-    protected function get_submitted_form_data_from_session()
370
-    {
371
-        $session = EE_Registry::instance()->SSN;
372
-        if ($session instanceof EE_Session) {
373
-            return $session->get_session_data(
374
-                EE_Form_Section_Proper::SUBMITTED_FORM_DATA_SSN_KEY
375
-            );
376
-        }
377
-        return array();
378
-    }
379
-
380
-
381
-    /**
382
-     * flushed the originally submitted input values from the session
383
-     *
384
-     * @return boolean whether or not the data was successfully removed from the session
385
-     * @throws InvalidArgumentException
386
-     * @throws InvalidInterfaceException
387
-     * @throws InvalidDataTypeException
388
-     */
389
-    public static function flush_submitted_form_data_from_session()
390
-    {
391
-        return EE_Registry::instance()->SSN->reset_data(
392
-            array(EE_Form_Section_Proper::SUBMITTED_FORM_DATA_SSN_KEY)
393
-        );
394
-    }
395
-
396
-
397
-    /**
398
-     * Populates this form and its subsections with data from the session.
399
-     * (Wrapper for EE_Form_Section_Proper::receive_form_submission, so it shows
400
-     * validation errors when displaying too)
401
-     * Returns true if the form was populated from the session, false otherwise
402
-     *
403
-     * @return boolean
404
-     * @throws InvalidArgumentException
405
-     * @throws InvalidInterfaceException
406
-     * @throws InvalidDataTypeException
407
-     * @throws EE_Error
408
-     */
409
-    public function populate_from_session()
410
-    {
411
-        $form_data_in_session = $this->get_submitted_form_data_from_session();
412
-        if (empty($form_data_in_session)) {
413
-            return false;
414
-        }
415
-        $this->receive_form_submission($form_data_in_session);
416
-        add_action('shutdown', array('EE_Form_Section_Proper', 'flush_submitted_form_data_from_session'));
417
-        if ($this->form_data_present_in($form_data_in_session)) {
418
-            return true;
419
-        }
420
-        return false;
421
-    }
422
-
423
-
424
-    /**
425
-     * Populates the default data for the form, given an array where keys are
426
-     * the input names, and values are their values (preferably normalized to be their
427
-     * proper PHP types, not all strings... although that should be ok too).
428
-     * Proper subsections are sub-arrays, the key being the subsection's name, and
429
-     * the value being an array formatted in teh same way
430
-     *
431
-     * @param array $default_data
432
-     * @throws EE_Error
433
-     */
434
-    public function populate_defaults($default_data)
435
-    {
436
-        foreach ($this->subsections(false) as $subsection_name => $subsection) {
437
-            if (isset($default_data[ $subsection_name ])) {
438
-                if ($subsection instanceof EE_Form_Input_Base) {
439
-                    $subsection->set_default($default_data[ $subsection_name ]);
440
-                } elseif ($subsection instanceof EE_Form_Section_Proper) {
441
-                    $subsection->populate_defaults($default_data[ $subsection_name ]);
442
-                }
443
-            }
444
-        }
445
-    }
446
-
447
-
448
-    /**
449
-     * returns true if subsection exists
450
-     *
451
-     * @param string $name
452
-     * @return boolean
453
-     */
454
-    public function subsection_exists($name)
455
-    {
456
-        return isset($this->_subsections[ $name ]) ? true : false;
457
-    }
458
-
459
-
460
-    /**
461
-     * Gets the subsection specified by its name
462
-     *
463
-     * @param string  $name
464
-     * @param boolean $require_construction_to_be_finalized most client code should leave this as TRUE
465
-     *                                                      so that the inputs will be properly configured.
466
-     *                                                      However, some client code may be ok
467
-     *                                                      with construction finalize being called later
468
-     *                                                      (realizing that the subsections' html names
469
-     *                                                      might not be set yet, etc.)
470
-     * @return EE_Form_Section_Base
471
-     * @throws EE_Error
472
-     */
473
-    public function get_subsection($name, $require_construction_to_be_finalized = true)
474
-    {
475
-        if ($require_construction_to_be_finalized) {
476
-            $this->ensure_construct_finalized_called();
477
-        }
478
-        return $this->subsection_exists($name) ? $this->_subsections[ $name ] : null;
479
-    }
480
-
481
-
482
-    /**
483
-     * Gets all the validatable subsections of this form section
484
-     *
485
-     * @return EE_Form_Section_Validatable[]
486
-     * @throws EE_Error
487
-     */
488
-    public function get_validatable_subsections()
489
-    {
490
-        $validatable_subsections = array();
491
-        foreach ($this->subsections() as $name => $obj) {
492
-            if ($obj instanceof EE_Form_Section_Validatable) {
493
-                $validatable_subsections[ $name ] = $obj;
494
-            }
495
-        }
496
-        return $validatable_subsections;
497
-    }
498
-
499
-
500
-    /**
501
-     * Gets an input by the given name. If not found, or if its not an EE_FOrm_Input_Base child,
502
-     * throw an EE_Error.
503
-     *
504
-     * @param string  $name
505
-     * @param boolean $require_construction_to_be_finalized most client code should
506
-     *                                                      leave this as TRUE so that the inputs will be properly
507
-     *                                                      configured. However, some client code may be ok with
508
-     *                                                      construction finalize being called later
509
-     *                                                      (realizing that the subsections' html names might not be
510
-     *                                                      set yet, etc.)
511
-     * @return EE_Form_Input_Base
512
-     * @throws EE_Error
513
-     */
514
-    public function get_input($name, $require_construction_to_be_finalized = true)
515
-    {
516
-        $subsection = $this->get_subsection(
517
-            $name,
518
-            $require_construction_to_be_finalized
519
-        );
520
-        if (! $subsection instanceof EE_Form_Input_Base) {
521
-            throw new EE_Error(
522
-                sprintf(
523
-                    esc_html__(
524
-                        "Subsection '%s' is not an instanceof EE_Form_Input_Base on form '%s'. It is a '%s'",
525
-                        'event_espresso'
526
-                    ),
527
-                    $name,
528
-                    get_class($this),
529
-                    $subsection ? get_class($subsection) : esc_html__('NULL', 'event_espresso')
530
-                )
531
-            );
532
-        }
533
-        return $subsection;
534
-    }
535
-
536
-
537
-    /**
538
-     * Like get_input(), gets the proper subsection of the form given the name,
539
-     * otherwise throws an EE_Error
540
-     *
541
-     * @param string  $name
542
-     * @param boolean $require_construction_to_be_finalized most client code should
543
-     *                                                      leave this as TRUE so that the inputs will be properly
544
-     *                                                      configured. However, some client code may be ok with
545
-     *                                                      construction finalize being called later
546
-     *                                                      (realizing that the subsections' html names might not be
547
-     *                                                      set yet, etc.)
548
-     * @return EE_Form_Section_Proper
549
-     * @throws EE_Error
550
-     */
551
-    public function get_proper_subsection($name, $require_construction_to_be_finalized = true)
552
-    {
553
-        $subsection = $this->get_subsection(
554
-            $name,
555
-            $require_construction_to_be_finalized
556
-        );
557
-        if (! $subsection instanceof EE_Form_Section_Proper) {
558
-            throw new EE_Error(
559
-                sprintf(
560
-                    esc_html__(
561
-                        "Subsection '%'s is not an instanceof EE_Form_Section_Proper on form '%s'",
562
-                        'event_espresso'
563
-                    ),
564
-                    $name,
565
-                    get_class($this)
566
-                )
567
-            );
568
-        }
569
-        return $subsection;
570
-    }
571
-
572
-
573
-    /**
574
-     * Gets the value of the specified input. Should be called after receive_form_submission()
575
-     * or populate_defaults() on the form, where the normalized value on the input is set.
576
-     *
577
-     * @param string $name
578
-     * @return mixed depending on the input's type and its normalization strategy
579
-     * @throws EE_Error
580
-     */
581
-    public function get_input_value($name)
582
-    {
583
-        $input = $this->get_input($name);
584
-        return $input->normalized_value();
585
-    }
586
-
587
-
588
-    /**
589
-     * Checks if this form section itself is valid, and then checks its subsections
590
-     *
591
-     * @throws EE_Error
592
-     * @return boolean
593
-     */
594
-    public function is_valid()
595
-    {
596
-        if ($this->is_valid === null) {
597
-            if (! $this->has_received_submission()) {
598
-                throw new EE_Error(
599
-                    sprintf(
600
-                        esc_html__(
601
-                            'You cannot check if a form is valid before receiving the form submission using receive_form_submission',
602
-                            'event_espresso'
603
-                        )
604
-                    )
605
-                );
606
-            }
607
-            if (! parent::is_valid()) {
608
-                $this->is_valid = false;
609
-            } else {
610
-                // ok so no general errors to this entire form section.
611
-                // so let's check the subsections, but only set errors if that hasn't been done yet
612
-                $this->is_valid = true;
613
-                foreach ($this->get_validatable_subsections() as $subsection) {
614
-                    if (! $subsection->is_valid()) {
615
-                        $this->is_valid = false;
616
-                    }
617
-                }
618
-            }
619
-        }
620
-        return $this->is_valid;
621
-    }
622
-
623
-
624
-    /**
625
-     * gets the default name of this form section if none is specified
626
-     *
627
-     * @return void
628
-     */
629
-    protected function _set_default_name_if_empty()
630
-    {
631
-        if (! $this->_name) {
632
-            $classname    = get_class($this);
633
-            $default_name = str_replace('EE_', '', $classname);
634
-            $this->_name  = $default_name;
635
-        }
636
-    }
637
-
638
-
639
-    /**
640
-     * Returns the HTML for the form, except for the form opening and closing tags
641
-     * (as the form section doesn't know where you necessarily want to send the information to),
642
-     * and except for a submit button. Enqueues JS and CSS; if called early enough we will
643
-     * try to enqueue them in the header, otherwise they'll be enqueued in the footer.
644
-     * Not doing_it_wrong because theoretically this CAN be used properly,
645
-     * provided its used during "wp_enqueue_scripts", or it doesn't need to enqueue
646
-     * any CSS.
647
-     *
648
-     * @throws InvalidArgumentException
649
-     * @throws InvalidInterfaceException
650
-     * @throws InvalidDataTypeException
651
-     * @throws EE_Error
652
-     */
653
-    public function get_html_and_js()
654
-    {
655
-        $this->enqueue_js();
656
-        return $this->get_html();
657
-    }
658
-
659
-
660
-    /**
661
-     * returns HTML for displaying this form section. recursively calls display_section() on all subsections
662
-     *
663
-     * @param bool $display_previously_submitted_data
664
-     * @return string
665
-     * @throws InvalidArgumentException
666
-     * @throws InvalidInterfaceException
667
-     * @throws InvalidDataTypeException
668
-     * @throws EE_Error
669
-     * @throws EE_Error
670
-     * @throws EE_Error
671
-     */
672
-    public function get_html($display_previously_submitted_data = true)
673
-    {
674
-        $this->ensure_construct_finalized_called();
675
-        if ($display_previously_submitted_data) {
676
-            $this->populate_from_session();
677
-        }
678
-        return $this->_form_html_filter
679
-            ? $this->_form_html_filter->filterHtml($this->_layout_strategy->layout_form(), $this)
680
-            : $this->_layout_strategy->layout_form();
681
-    }
682
-
683
-
684
-    /**
685
-     * enqueues JS and CSS for the form.
686
-     * It is preferred to call this before wp_enqueue_scripts so the
687
-     * scripts and styles can be put in the header, but if called later
688
-     * they will be put in the footer (which is OK for JS, but in HTML4 CSS should
689
-     * only be in the header; but in HTML5 its ok in the body.
690
-     * See http://stackoverflow.com/questions/4957446/load-external-css-file-in-body-tag.
691
-     * So if your form enqueues CSS, it's preferred to call this before wp_enqueue_scripts.)
692
-     *
693
-     * @return void
694
-     * @throws EE_Error
695
-     */
696
-    public function enqueue_js()
697
-    {
698
-        $this->_enqueue_and_localize_form_js();
699
-        foreach ($this->subsections() as $subsection) {
700
-            $subsection->enqueue_js();
701
-        }
702
-    }
703
-
704
-
705
-    /**
706
-     * adds a filter so that jquery validate gets enqueued in EE_System::wp_enqueue_scripts().
707
-     * This must be done BEFORE wp_enqueue_scripts() gets called, which is on
708
-     * the wp_enqueue_scripts hook.
709
-     * However, registering the form js and localizing it can happen when we
710
-     * actually output the form (which is preferred, seeing how teh form's fields
711
-     * could change until it's actually outputted)
712
-     *
713
-     * @param boolean $init_form_validation_automatically whether or not we want the form validation
714
-     *                                                    to be triggered automatically or not
715
-     * @return void
716
-     */
717
-    public static function wp_enqueue_scripts($init_form_validation_automatically = true)
718
-    {
719
-        wp_register_script(
720
-            'ee_form_section_validation',
721
-            EE_GLOBAL_ASSETS_URL . 'scripts' . '/form_section_validation.js',
722
-            array('jquery-validate', 'jquery-ui-datepicker', 'jquery-validate-extra-methods'),
723
-            EVENT_ESPRESSO_VERSION,
724
-            true
725
-        );
726
-        wp_localize_script(
727
-            'ee_form_section_validation',
728
-            'ee_form_section_validation_init',
729
-            array('init' => $init_form_validation_automatically ? '1' : '0')
730
-        );
731
-    }
732
-
733
-
734
-    /**
735
-     * gets the variables used by form_section_validation.js.
736
-     * This needs to be called AFTER we've called $this->_enqueue_jquery_validate_script,
737
-     * but before the wordpress hook wp_loaded
738
-     *
739
-     * @throws EE_Error
740
-     */
741
-    public function _enqueue_and_localize_form_js()
742
-    {
743
-        $this->ensure_construct_finalized_called();
744
-        // actually, we don't want to localize just yet. There may be other forms on the page.
745
-        // so we need to add our form section data to a static variable accessible by all form sections
746
-        // and localize it just before the footer
747
-        $this->localize_validation_rules();
748
-        add_action('wp_footer', array('EE_Form_Section_Proper', 'localize_script_for_all_forms'), 2);
749
-        add_action('admin_footer', array('EE_Form_Section_Proper', 'localize_script_for_all_forms'));
750
-    }
751
-
752
-
753
-    /**
754
-     * add our form section data to a static variable accessible by all form sections
755
-     *
756
-     * @param bool $return_for_subsection
757
-     * @return void
758
-     * @throws EE_Error
759
-     */
760
-    public function localize_validation_rules($return_for_subsection = false)
761
-    {
762
-        // we only want to localize vars ONCE for the entire form,
763
-        // so if the form section doesn't have a parent, then it must be the top dog
764
-        if ($return_for_subsection || ! $this->parent_section()) {
765
-            EE_Form_Section_Proper::$_js_localization['form_data'][ $this->html_id() ] = array(
766
-                'form_section_id'  => $this->html_id(true),
767
-                'validation_rules' => $this->get_jquery_validation_rules(),
768
-                'other_data'       => $this->get_other_js_data(),
769
-                'errors'           => $this->subsection_validation_errors_by_html_name(),
770
-            );
771
-            EE_Form_Section_Proper::$_scripts_localized                                = true;
772
-        }
773
-    }
774
-
775
-
776
-    /**
777
-     * Gets an array of extra data that will be useful for client-side javascript.
778
-     * This is primarily data added by inputs and forms in addition to any
779
-     * scripts they might enqueue
780
-     *
781
-     * @param array $form_other_js_data
782
-     * @return array
783
-     * @throws EE_Error
784
-     */
785
-    public function get_other_js_data($form_other_js_data = array())
786
-    {
787
-        foreach ($this->subsections() as $subsection) {
788
-            $form_other_js_data = $subsection->get_other_js_data($form_other_js_data);
789
-        }
790
-        return $form_other_js_data;
791
-    }
792
-
793
-
794
-    /**
795
-     * Gets a flat array of inputs for this form section and its subsections.
796
-     * Keys are their form names, and values are the inputs themselves
797
-     *
798
-     * @return EE_Form_Input_Base
799
-     * @throws EE_Error
800
-     */
801
-    public function inputs_in_subsections()
802
-    {
803
-        $inputs = array();
804
-        foreach ($this->subsections() as $subsection) {
805
-            if ($subsection instanceof EE_Form_Input_Base) {
806
-                $inputs[ $subsection->html_name() ] = $subsection;
807
-            } elseif ($subsection instanceof EE_Form_Section_Proper) {
808
-                $inputs += $subsection->inputs_in_subsections();
809
-            }
810
-        }
811
-        return $inputs;
812
-    }
813
-
814
-
815
-    /**
816
-     * Gets a flat array of all the validation errors.
817
-     * Keys are html names (because those should be unique)
818
-     * and values are a string of all their validation errors
819
-     *
820
-     * @return string[]
821
-     * @throws EE_Error
822
-     */
823
-    public function subsection_validation_errors_by_html_name()
824
-    {
825
-        $inputs = $this->inputs();
826
-        $errors = array();
827
-        foreach ($inputs as $form_input) {
828
-            if ($form_input instanceof EE_Form_Input_Base && $form_input->get_validation_errors()) {
829
-                $errors[ $form_input->html_name() ] = $form_input->get_validation_error_string();
830
-            }
831
-        }
832
-        return $errors;
833
-    }
834
-
835
-
836
-    /**
837
-     * passes all the form data required by the JS to the JS, and enqueues the few required JS files.
838
-     * Should be setup by each form during the _enqueues_and_localize_form_js
839
-     *
840
-     * @throws InvalidArgumentException
841
-     * @throws InvalidInterfaceException
842
-     * @throws InvalidDataTypeException
843
-     */
844
-    public static function localize_script_for_all_forms()
845
-    {
846
-        // allow inputs and stuff to hook in their JS and stuff here
847
-        do_action('AHEE__EE_Form_Section_Proper__localize_script_for_all_forms__begin');
848
-        EE_Form_Section_Proper::$_js_localization['localized_error_messages'] = EE_Form_Section_Proper::_get_localized_error_messages();
849
-        $email_validation_level = isset(EE_Registry::instance()->CFG->registration->email_validation_level)
850
-            ? EE_Registry::instance()->CFG->registration->email_validation_level
851
-            : 'wp_default';
852
-        EE_Form_Section_Proper::$_js_localization['email_validation_level']   = $email_validation_level;
853
-        wp_enqueue_script('ee_form_section_validation');
854
-        wp_localize_script(
855
-            'ee_form_section_validation',
856
-            'ee_form_section_vars',
857
-            EE_Form_Section_Proper::$_js_localization
858
-        );
859
-    }
860
-
861
-
862
-    /**
863
-     * ensure_scripts_localized
864
-     *
865
-     * @throws EE_Error
866
-     */
867
-    public function ensure_scripts_localized()
868
-    {
869
-        if (! EE_Form_Section_Proper::$_scripts_localized) {
870
-            $this->_enqueue_and_localize_form_js();
871
-        }
872
-    }
873
-
874
-
875
-    /**
876
-     * Gets the hard-coded validation error messages to be used in the JS. The convention
877
-     * is that the key here should be the same as the custom validation rule put in the JS file
878
-     *
879
-     * @return array keys are custom validation rules, and values are internationalized strings
880
-     */
881
-    private static function _get_localized_error_messages()
882
-    {
883
-        return array(
884
-            'validUrl' => wp_strip_all_tags(__('This is not a valid absolute URL. Eg, http://domain.com/monkey.jpg', 'event_espresso')),
885
-            'regex'    => wp_strip_all_tags(__('Please check your input', 'event_espresso'))
886
-        );
887
-    }
888
-
889
-
890
-    /**
891
-     * @return array
892
-     */
893
-    public static function js_localization()
894
-    {
895
-        return self::$_js_localization;
896
-    }
897
-
898
-
899
-    /**
900
-     * @return void
901
-     */
902
-    public static function reset_js_localization()
903
-    {
904
-        self::$_js_localization = array();
905
-    }
906
-
907
-
908
-    /**
909
-     * Gets the JS to put inside the jquery validation rules for subsection of this form section.
910
-     * See parent function for more...
911
-     *
912
-     * @return array
913
-     * @throws EE_Error
914
-     */
915
-    public function get_jquery_validation_rules()
916
-    {
917
-        $jquery_validation_rules = array();
918
-        foreach ($this->get_validatable_subsections() as $subsection) {
919
-            $jquery_validation_rules = array_merge(
920
-                $jquery_validation_rules,
921
-                $subsection->get_jquery_validation_rules()
922
-            );
923
-        }
924
-        return $jquery_validation_rules;
925
-    }
926
-
927
-
928
-    /**
929
-     * Sanitizes all the data and sets the sanitized value of each field
930
-     *
931
-     * @param array $req_data
932
-     * @return void
933
-     * @throws EE_Error
934
-     */
935
-    protected function _normalize($req_data)
936
-    {
937
-        $this->_received_submission = true;
938
-        $this->_validation_errors   = array();
939
-        foreach ($this->get_validatable_subsections() as $subsection) {
940
-            try {
941
-                $subsection->_normalize($req_data);
942
-            } catch (EE_Validation_Error $e) {
943
-                $subsection->add_validation_error($e);
944
-            }
945
-        }
946
-    }
947
-
948
-
949
-    /**
950
-     * Performs validation on this form section and its subsections.
951
-     * For each subsection,
952
-     * calls _validate_{subsection_name} on THIS form (if the function exists)
953
-     * and passes it the subsection, then calls _validate on that subsection.
954
-     * If you need to perform validation on the form as a whole (considering multiple)
955
-     * you would be best to override this _validate method,
956
-     * calling parent::_validate() first.
957
-     *
958
-     * @throws EE_Error
959
-     */
960
-    protected function _validate()
961
-    {
962
-        // reset the cache of whether this form is valid or not- we're re-validating it now
963
-        $this->is_valid = null;
964
-        foreach ($this->get_validatable_subsections() as $subsection_name => $subsection) {
965
-            if (method_exists($this, '_validate_' . $subsection_name)) {
966
-                call_user_func_array(array($this, '_validate_' . $subsection_name), array($subsection));
967
-            }
968
-            $subsection->_validate();
969
-        }
970
-    }
971
-
972
-
973
-    /**
974
-     * Gets all the validated inputs for the form section
975
-     *
976
-     * @return array
977
-     * @throws EE_Error
978
-     */
979
-    public function valid_data()
980
-    {
981
-        $inputs = array();
982
-        foreach ($this->subsections() as $subsection_name => $subsection) {
983
-            if ($subsection instanceof EE_Form_Section_Proper) {
984
-                $inputs[ $subsection_name ] = $subsection->valid_data();
985
-            } elseif ($subsection instanceof EE_Form_Input_Base) {
986
-                $inputs[ $subsection_name ] = $subsection->normalized_value();
987
-            }
988
-        }
989
-        return $inputs;
990
-    }
991
-
992
-
993
-    /**
994
-     * Gets all the inputs on this form section
995
-     *
996
-     * @return EE_Form_Input_Base[]
997
-     * @throws EE_Error
998
-     */
999
-    public function inputs()
1000
-    {
1001
-        $inputs = array();
1002
-        foreach ($this->subsections() as $subsection_name => $subsection) {
1003
-            if ($subsection instanceof EE_Form_Input_Base) {
1004
-                $inputs[ $subsection_name ] = $subsection;
1005
-            }
1006
-        }
1007
-        return $inputs;
1008
-    }
1009
-
1010
-
1011
-    /**
1012
-     * Gets all the subsections which are a proper form
1013
-     *
1014
-     * @return EE_Form_Section_Proper[]
1015
-     * @throws EE_Error
1016
-     */
1017
-    public function subforms()
1018
-    {
1019
-        $form_sections = array();
1020
-        foreach ($this->subsections() as $name => $obj) {
1021
-            if ($obj instanceof EE_Form_Section_Proper) {
1022
-                $form_sections[ $name ] = $obj;
1023
-            }
1024
-        }
1025
-        return $form_sections;
1026
-    }
1027
-
1028
-
1029
-    /**
1030
-     * Gets all the subsections (inputs, proper subsections, or html-only sections).
1031
-     * Consider using inputs() or subforms()
1032
-     * if you only want form inputs or proper form sections.
1033
-     *
1034
-     * @param boolean $require_construction_to_be_finalized most client code should
1035
-     *                                                      leave this as TRUE so that the inputs will be properly
1036
-     *                                                      configured. However, some client code may be ok with
1037
-     *                                                      construction finalize being called later
1038
-     *                                                      (realizing that the subsections' html names might not be
1039
-     *                                                      set yet, etc.)
1040
-     * @return EE_Form_Section_Proper[]
1041
-     * @throws EE_Error
1042
-     */
1043
-    public function subsections($require_construction_to_be_finalized = true)
1044
-    {
1045
-        if ($require_construction_to_be_finalized) {
1046
-            $this->ensure_construct_finalized_called();
1047
-        }
1048
-        return $this->_subsections;
1049
-    }
1050
-
1051
-
1052
-    /**
1053
-     * Returns whether this form has any subforms or inputs
1054
-     * @return bool
1055
-     */
1056
-    public function hasSubsections()
1057
-    {
1058
-        return ! empty($this->_subsections);
1059
-    }
1060
-
1061
-
1062
-    /**
1063
-     * Returns a simple array where keys are input names, and values are their normalized
1064
-     * values. (Similar to calling get_input_value on inputs)
1065
-     *
1066
-     * @param boolean $include_subform_inputs Whether to include inputs from subforms,
1067
-     *                                        or just this forms' direct children inputs
1068
-     * @param boolean $flatten                Whether to force the results into 1-dimensional array,
1069
-     *                                        or allow multidimensional array
1070
-     * @return array if $flatten is TRUE it will always be a 1-dimensional array
1071
-     *                                        with array keys being input names
1072
-     *                                        (regardless of whether they are from a subsection or not),
1073
-     *                                        and if $flatten is FALSE it can be a multidimensional array
1074
-     *                                        where keys are always subsection names and values are either
1075
-     *                                        the input's normalized value, or an array like the top-level array
1076
-     * @throws EE_Error
1077
-     */
1078
-    public function input_values($include_subform_inputs = false, $flatten = false)
1079
-    {
1080
-        return $this->_input_values(false, $include_subform_inputs, $flatten);
1081
-    }
1082
-
1083
-
1084
-    /**
1085
-     * Similar to EE_Form_Section_Proper::input_values(), except this returns the 'display_value'
1086
-     * of each input. On some inputs (especially radio boxes or checkboxes), the value stored
1087
-     * is not necessarily the value we want to display to users. This creates an array
1088
-     * where keys are the input names, and values are their display values
1089
-     *
1090
-     * @param boolean $include_subform_inputs Whether to include inputs from subforms,
1091
-     *                                        or just this forms' direct children inputs
1092
-     * @param boolean $flatten                Whether to force the results into 1-dimensional array,
1093
-     *                                        or allow multidimensional array
1094
-     * @return array if $flatten is TRUE it will always be a 1-dimensional array
1095
-     *                                        with array keys being input names
1096
-     *                                        (regardless of whether they are from a subsection or not),
1097
-     *                                        and if $flatten is FALSE it can be a multidimensional array
1098
-     *                                        where keys are always subsection names and values are either
1099
-     *                                        the input's normalized value, or an array like the top-level array
1100
-     * @throws EE_Error
1101
-     */
1102
-    public function input_pretty_values($include_subform_inputs = false, $flatten = false)
1103
-    {
1104
-        return $this->_input_values(true, $include_subform_inputs, $flatten);
1105
-    }
1106
-
1107
-
1108
-    /**
1109
-     * Gets the input values from the form
1110
-     *
1111
-     * @param boolean $pretty                 Whether to retrieve the pretty value,
1112
-     *                                        or just the normalized value
1113
-     * @param boolean $include_subform_inputs Whether to include inputs from subforms,
1114
-     *                                        or just this forms' direct children inputs
1115
-     * @param boolean $flatten                Whether to force the results into 1-dimensional array,
1116
-     *                                        or allow multidimensional array
1117
-     * @return array if $flatten is TRUE it will always be a 1-dimensional array with array keys being
1118
-     *                                        input names (regardless of whether they are from a subsection or not),
1119
-     *                                        and if $flatten is FALSE it can be a multidimensional array
1120
-     *                                        where keys are always subsection names and values are either
1121
-     *                                        the input's normalized value, or an array like the top-level array
1122
-     * @throws EE_Error
1123
-     */
1124
-    public function _input_values($pretty = false, $include_subform_inputs = false, $flatten = false)
1125
-    {
1126
-        $input_values = array();
1127
-        foreach ($this->subsections() as $subsection_name => $subsection) {
1128
-            if ($subsection instanceof EE_Form_Input_Base) {
1129
-                $input_values[ $subsection_name ] = $pretty
1130
-                    ? $subsection->pretty_value()
1131
-                    : $subsection->normalized_value();
1132
-            } elseif ($subsection instanceof EE_Form_Section_Proper && $include_subform_inputs) {
1133
-                $subform_input_values = $subsection->_input_values(
1134
-                    $pretty,
1135
-                    $include_subform_inputs,
1136
-                    $flatten
1137
-                );
1138
-                if ($flatten) {
1139
-                    $input_values = array_merge($input_values, $subform_input_values);
1140
-                } else {
1141
-                    $input_values[ $subsection_name ] = $subform_input_values;
1142
-                }
1143
-            }
1144
-        }
1145
-        return $input_values;
1146
-    }
1147
-
1148
-
1149
-    /**
1150
-     * Gets the originally submitted input values from the form
1151
-     *
1152
-     * @param boolean $include_subforms  Whether to include inputs from subforms,
1153
-     *                                   or just this forms' direct children inputs
1154
-     * @return array                     if $flatten is TRUE it will always be a 1-dimensional array
1155
-     *                                   with array keys being input names
1156
-     *                                   (regardless of whether they are from a subsection or not),
1157
-     *                                   and if $flatten is FALSE it can be a multidimensional array
1158
-     *                                   where keys are always subsection names and values are either
1159
-     *                                   the input's normalized value, or an array like the top-level array
1160
-     * @throws EE_Error
1161
-     */
1162
-    public function submitted_values($include_subforms = false)
1163
-    {
1164
-        $submitted_values = array();
1165
-        foreach ($this->subsections() as $subsection) {
1166
-            if ($subsection instanceof EE_Form_Input_Base) {
1167
-                // is this input part of an array of inputs?
1168
-                if (strpos($subsection->html_name(), '[') !== false) {
1169
-                    $full_input_name  = EEH_Array::convert_array_values_to_keys(
1170
-                        explode(
1171
-                            '[',
1172
-                            str_replace(']', '', $subsection->html_name())
1173
-                        ),
1174
-                        $subsection->raw_value()
1175
-                    );
1176
-                    $submitted_values = array_replace_recursive($submitted_values, $full_input_name);
1177
-                } else {
1178
-                    $submitted_values[ $subsection->html_name() ] = $subsection->raw_value();
1179
-                }
1180
-            } elseif ($subsection instanceof EE_Form_Section_Proper && $include_subforms) {
1181
-                $subform_input_values = $subsection->submitted_values($include_subforms);
1182
-                $submitted_values     = array_replace_recursive($submitted_values, $subform_input_values);
1183
-            }
1184
-        }
1185
-        return $submitted_values;
1186
-    }
1187
-
1188
-
1189
-    /**
1190
-     * Indicates whether or not this form has received a submission yet
1191
-     * (ie, had receive_form_submission called on it yet)
1192
-     *
1193
-     * @return boolean
1194
-     * @throws EE_Error
1195
-     */
1196
-    public function has_received_submission()
1197
-    {
1198
-        $this->ensure_construct_finalized_called();
1199
-        return $this->_received_submission;
1200
-    }
1201
-
1202
-
1203
-    /**
1204
-     * Equivalent to passing 'exclude' in the constructor's options array.
1205
-     * Removes the listed inputs from the form
1206
-     *
1207
-     * @param array $inputs_to_exclude values are the input names
1208
-     * @return void
1209
-     */
1210
-    public function exclude(array $inputs_to_exclude = array())
1211
-    {
1212
-        foreach ($inputs_to_exclude as $input_to_exclude_name) {
1213
-            unset($this->_subsections[ $input_to_exclude_name ]);
1214
-        }
1215
-    }
1216
-
1217
-
1218
-    /**
1219
-     * Changes these inputs' display strategy to be EE_Hidden_Display_Strategy.
1220
-     * @param array $inputs_to_hide
1221
-     * @throws EE_Error
1222
-     */
1223
-    public function hide(array $inputs_to_hide = array())
1224
-    {
1225
-        foreach ($inputs_to_hide as $input_to_hide) {
1226
-            $input = $this->get_input($input_to_hide);
1227
-            $input->set_display_strategy(new EE_Hidden_Display_Strategy());
1228
-        }
1229
-    }
1230
-
1231
-
1232
-    /**
1233
-     * add_subsections
1234
-     * Adds the listed subsections to the form section.
1235
-     * If $subsection_name_to_target is provided,
1236
-     * then new subsections are added before or after that subsection,
1237
-     * otherwise to the start or end of the entire subsections array.
1238
-     *
1239
-     * @param EE_Form_Section_Base[] $new_subsections           array of new form subsections
1240
-     *                                                          where keys are their names
1241
-     * @param string                 $subsection_name_to_target an existing for section that $new_subsections
1242
-     *                                                          should be added before or after
1243
-     *                                                          IF $subsection_name_to_target is null,
1244
-     *                                                          then $new_subsections will be added to
1245
-     *                                                          the beginning or end of the entire subsections array
1246
-     * @param boolean                $add_before                whether to add $new_subsections, before or after
1247
-     *                                                          $subsection_name_to_target,
1248
-     *                                                          or if $subsection_name_to_target is null,
1249
-     *                                                          before or after entire subsections array
1250
-     * @return void
1251
-     * @throws EE_Error
1252
-     */
1253
-    public function add_subsections($new_subsections, $subsection_name_to_target = null, $add_before = true)
1254
-    {
1255
-        foreach ($new_subsections as $subsection_name => $subsection) {
1256
-            if (! $subsection instanceof EE_Form_Section_Base) {
1257
-                EE_Error::add_error(
1258
-                    sprintf(
1259
-                        esc_html__(
1260
-                            "Trying to add a %s as a subsection (it was named '%s') to the form section '%s'. It was removed.",
1261
-                            'event_espresso'
1262
-                        ),
1263
-                        get_class($subsection),
1264
-                        $subsection_name,
1265
-                        $this->name()
1266
-                    )
1267
-                );
1268
-                unset($new_subsections[ $subsection_name ]);
1269
-            }
1270
-        }
1271
-        $this->_subsections = EEH_Array::insert_into_array(
1272
-            $this->_subsections,
1273
-            $new_subsections,
1274
-            $subsection_name_to_target,
1275
-            $add_before
1276
-        );
1277
-        if ($this->_construction_finalized) {
1278
-            foreach ($this->_subsections as $name => $subsection) {
1279
-                $subsection->_construct_finalize($this, $name);
1280
-            }
1281
-        }
1282
-    }
1283
-
1284
-
1285
-    /**
1286
-     * @param string $subsection_name
1287
-     * @param bool   $recursive
1288
-     * @return bool
1289
-     */
1290
-    public function has_subsection($subsection_name, $recursive = false)
1291
-    {
1292
-        foreach ($this->_subsections as $name => $subsection) {
1293
-            if (
1294
-                $name === $subsection_name
1295
-                || (
1296
-                    $recursive
1297
-                    && $subsection instanceof EE_Form_Section_Proper
1298
-                    && $subsection->has_subsection($subsection_name, $recursive)
1299
-                )
1300
-            ) {
1301
-                return true;
1302
-            }
1303
-        }
1304
-        return false;
1305
-    }
1306
-
1307
-
1308
-
1309
-    /**
1310
-     * Just gets all validatable subsections to clean their sensitive data
1311
-     *
1312
-     * @throws EE_Error
1313
-     */
1314
-    public function clean_sensitive_data()
1315
-    {
1316
-        foreach ($this->get_validatable_subsections() as $subsection) {
1317
-            $subsection->clean_sensitive_data();
1318
-        }
1319
-    }
1320
-
1321
-
1322
-    /**
1323
-     * Sets the submission error message (aka validation error message for this form section and all sub-sections)
1324
-     * @param string                           $form_submission_error_message
1325
-     * @param EE_Form_Section_Validatable $form_section unused
1326
-     * @throws EE_Error
1327
-     */
1328
-    public function set_submission_error_message(
1329
-        $form_submission_error_message = ''
1330
-    ) {
1331
-        $this->_form_submission_error_message = ! empty($form_submission_error_message)
1332
-            ? $form_submission_error_message
1333
-            : $this->getAllValidationErrorsString();
1334
-    }
1335
-
1336
-
1337
-    /**
1338
-     * Returns the cached error message. A default value is set for this during _validate(),
1339
-     * (called during receive_form_submission) but it can be explicitly set using
1340
-     * set_submission_error_message
1341
-     *
1342
-     * @return string
1343
-     */
1344
-    public function submission_error_message()
1345
-    {
1346
-        return $this->_form_submission_error_message;
1347
-    }
1348
-
1349
-
1350
-    /**
1351
-     * Sets a message to display if the data submitted to the form was valid.
1352
-     * @param string $form_submission_success_message
1353
-     */
1354
-    public function set_submission_success_message($form_submission_success_message = '')
1355
-    {
1356
-        $this->_form_submission_success_message = ! empty($form_submission_success_message)
1357
-            ? $form_submission_success_message
1358
-            : esc_html__('Form submitted successfully', 'event_espresso');
1359
-    }
1360
-
1361
-
1362
-    /**
1363
-     * Gets a message appropriate for display when the form is correctly submitted
1364
-     * @return string
1365
-     */
1366
-    public function submission_success_message()
1367
-    {
1368
-        return $this->_form_submission_success_message;
1369
-    }
1370
-
1371
-
1372
-    /**
1373
-     * Returns the prefix that should be used on child of this form section for
1374
-     * their html names. If this form section itself has a parent, prepends ITS
1375
-     * prefix onto this form section's prefix. Used primarily by
1376
-     * EE_Form_Input_Base::_set_default_html_name_if_empty
1377
-     *
1378
-     * @return string
1379
-     * @throws EE_Error
1380
-     */
1381
-    public function html_name_prefix()
1382
-    {
1383
-        if ($this->parent_section() instanceof EE_Form_Section_Proper) {
1384
-            return $this->parent_section()->html_name_prefix() . '[' . $this->name() . ']';
1385
-        }
1386
-        return $this->name();
1387
-    }
1388
-
1389
-
1390
-    /**
1391
-     * Gets the name, but first checks _construct_finalize has been called. If not,
1392
-     * calls it (assumes there is no parent and that we want the name to be whatever
1393
-     * was set, which is probably nothing, or the classname)
1394
-     *
1395
-     * @return string
1396
-     * @throws EE_Error
1397
-     */
1398
-    public function name()
1399
-    {
1400
-        $this->ensure_construct_finalized_called();
1401
-        return parent::name();
1402
-    }
1403
-
1404
-
1405
-    /**
1406
-     * @return EE_Form_Section_Proper
1407
-     * @throws EE_Error
1408
-     */
1409
-    public function parent_section()
1410
-    {
1411
-        $this->ensure_construct_finalized_called();
1412
-        return parent::parent_section();
1413
-    }
1414
-
1415
-
1416
-    /**
1417
-     * make sure construction finalized was called, otherwise children might not be ready
1418
-     *
1419
-     * @return void
1420
-     * @throws EE_Error
1421
-     */
1422
-    public function ensure_construct_finalized_called()
1423
-    {
1424
-        if (! $this->_construction_finalized) {
1425
-            $this->_construct_finalize($this->_parent_section, $this->_name);
1426
-        }
1427
-    }
1428
-
1429
-
1430
-    /**
1431
-     * Checks if any of this form section's inputs, or any of its children's inputs,
1432
-     * are in teh form data. If any are found, returns true. Else false
1433
-     *
1434
-     * @param array $req_data
1435
-     * @return boolean
1436
-     * @throws EE_Error
1437
-     */
1438
-    public function form_data_present_in($req_data = null)
1439
-    {
1440
-        $req_data = $this->getCachedRequest($req_data);
1441
-        foreach ($this->subsections() as $subsection) {
1442
-            if ($subsection instanceof EE_Form_Input_Base) {
1443
-                if ($subsection->form_data_present_in($req_data)) {
1444
-                    return true;
1445
-                }
1446
-            } elseif ($subsection instanceof EE_Form_Section_Proper) {
1447
-                if ($subsection->form_data_present_in($req_data)) {
1448
-                    return true;
1449
-                }
1450
-            }
1451
-        }
1452
-        return false;
1453
-    }
1454
-
1455
-
1456
-    /**
1457
-     * Gets validation errors for this form section and subsections
1458
-     * Similar to EE_Form_Section_Validatable::get_validation_errors() except this
1459
-     * gets the validation errors for ALL subsection
1460
-     *
1461
-     * @return EE_Validation_Error[]
1462
-     * @throws EE_Error
1463
-     */
1464
-    public function get_validation_errors_accumulated()
1465
-    {
1466
-        $validation_errors = $this->get_validation_errors();
1467
-        foreach ($this->get_validatable_subsections() as $subsection) {
1468
-            if ($subsection instanceof EE_Form_Section_Proper) {
1469
-                $validation_errors_on_this_subsection = $subsection->get_validation_errors_accumulated();
1470
-            } else {
1471
-                $validation_errors_on_this_subsection = $subsection->get_validation_errors();
1472
-            }
1473
-            if ($validation_errors_on_this_subsection) {
1474
-                $validation_errors = array_merge($validation_errors, $validation_errors_on_this_subsection);
1475
-            }
1476
-        }
1477
-        return $validation_errors;
1478
-    }
1479
-
1480
-    /**
1481
-     * Fetch validation errors from children and grandchildren and puts them in a single string.
1482
-     * This traverses the form section tree to generate this, but you probably want to instead use
1483
-     * get_form_submission_error_message() which is usually this message cached (or a custom validation error message)
1484
-     *
1485
-     * @return string
1486
-     * @since 4.9.59.p
1487
-     */
1488
-    protected function getAllValidationErrorsString()
1489
-    {
1490
-        $submission_error_messages = array();
1491
-        // bad, bad, bad registrant
1492
-        foreach ($this->get_validation_errors_accumulated() as $validation_error) {
1493
-            if ($validation_error instanceof EE_Validation_Error) {
1494
-                $form_section = $validation_error->get_form_section();
1495
-                if ($form_section instanceof EE_Form_Input_Base) {
1496
-                    $label = $validation_error->get_form_section()->html_label_text();
1497
-                } elseif ($form_section instanceof EE_Form_Section_Validatable) {
1498
-                    $label = $validation_error->get_form_section()->name();
1499
-                } else {
1500
-                    $label = esc_html__('Unknown', 'event_espresso');
1501
-                }
1502
-                $submission_error_messages[] = sprintf(
1503
-                    esc_html__('%s : %s', 'event_espresso'),
1504
-                    $label,
1505
-                    $validation_error->getMessage()
1506
-                );
1507
-            }
1508
-        }
1509
-        return implode('<br>', $submission_error_messages);
1510
-    }
1511
-
1512
-
1513
-    /**
1514
-     * This isn't just the name of an input, it's a path pointing to an input. The
1515
-     * path is similar to a folder path: slash (/) means to descend into a subsection,
1516
-     * dot-dot-slash (../) means to ascend into the parent section.
1517
-     * After a series of slashes and dot-dot-slashes, there should be the name of an input,
1518
-     * which will be returned.
1519
-     * Eg, if you want the related input to be conditional on a sibling input name 'foobar'
1520
-     * just use 'foobar'. If you want it to be conditional on an aunt/uncle input name
1521
-     * 'baz', use '../baz'. If you want it to be conditional on a cousin input,
1522
-     * the child of 'baz_section' named 'baz_child', use '../baz_section/baz_child'.
1523
-     * Etc
1524
-     *
1525
-     * @param string|false $form_section_path we accept false also because substr( '../', '../' ) = false
1526
-     * @return EE_Form_Section_Base
1527
-     * @throws EE_Error
1528
-     */
1529
-    public function find_section_from_path($form_section_path)
1530
-    {
1531
-        // check if we can find the input from purely going straight up the tree
1532
-        $input = parent::find_section_from_path($form_section_path);
1533
-        if ($input instanceof EE_Form_Section_Base) {
1534
-            return $input;
1535
-        }
1536
-        $next_slash_pos = strpos($form_section_path, '/');
1537
-        if ($next_slash_pos !== false) {
1538
-            $child_section_name = substr($form_section_path, 0, $next_slash_pos);
1539
-            $subpath            = substr($form_section_path, $next_slash_pos + 1);
1540
-        } else {
1541
-            $child_section_name = $form_section_path;
1542
-            $subpath            = '';
1543
-        }
1544
-        $child_section = $this->get_subsection($child_section_name);
1545
-        if ($child_section instanceof EE_Form_Section_Base) {
1546
-            return $child_section->find_section_from_path($subpath);
1547
-        }
1548
-        return null;
1549
-    }
18
+	const SUBMITTED_FORM_DATA_SSN_KEY = 'submitted_form_data';
19
+
20
+	/**
21
+	 * Subsections
22
+	 *
23
+	 * @var EE_Form_Section_Validatable[]
24
+	 */
25
+	protected $_subsections = array();
26
+
27
+	/**
28
+	 * Strategy for laying out the form
29
+	 *
30
+	 * @var EE_Form_Section_Layout_Base
31
+	 */
32
+	protected $_layout_strategy;
33
+
34
+	/**
35
+	 * Whether or not this form has received and validated a form submission yet
36
+	 *
37
+	 * @var boolean
38
+	 */
39
+	protected $_received_submission = false;
40
+
41
+	/**
42
+	 * message displayed to users upon successful form submission
43
+	 *
44
+	 * @var string
45
+	 */
46
+	protected $_form_submission_success_message = '';
47
+
48
+	/**
49
+	 * message displayed to users upon unsuccessful form submission
50
+	 *
51
+	 * @var string
52
+	 */
53
+	protected $_form_submission_error_message = '';
54
+
55
+	/**
56
+	 * @var array like post / request
57
+	 */
58
+	protected $cached_request_data;
59
+
60
+	/**
61
+	 * Stores whether this form (and its sub-sections) were found to be valid or not.
62
+	 * Starts off as null, but once the form is validated, it set to either true or false
63
+	 * @var boolean|null
64
+	 */
65
+	protected $is_valid;
66
+
67
+	/**
68
+	 * Stores all the data that will localized for form validation
69
+	 *
70
+	 * @var array
71
+	 */
72
+	protected static $_js_localization = array();
73
+
74
+	/**
75
+	 * whether or not the form's localized validation JS vars have been set
76
+	 *
77
+	 * @type boolean
78
+	 */
79
+	protected static $_scripts_localized = false;
80
+
81
+
82
+	/**
83
+	 * when constructing a proper form section, calls _construct_finalize on children
84
+	 * so that they know who their parent is, and what name they've been given.
85
+	 *
86
+	 * @param array[] $options_array   {
87
+	 * @type          $subsections     EE_Form_Section_Validatable[] where keys are the section's name
88
+	 * @type          $include         string[] numerically-indexed where values are section names to be included,
89
+	 *                                 and in that order. This is handy if you want
90
+	 *                                 the subsections to be ordered differently than the default, and if you override
91
+	 *                                 which fields are shown
92
+	 * @type          $exclude         string[] values are subsections to be excluded. This is handy if you want
93
+	 *                                 to remove certain default subsections (note: if you specify BOTH 'include' AND
94
+	 *                                 'exclude', the inclusions will be applied first, and the exclusions will exclude
95
+	 *                                 items from that list of inclusions)
96
+	 * @type          $layout_strategy EE_Form_Section_Layout_Base strategy for laying out the form
97
+	 *                                 } @see EE_Form_Section_Validatable::__construct()
98
+	 * @throws EE_Error
99
+	 */
100
+	public function __construct($options_array = array())
101
+	{
102
+		$options_array = (array) apply_filters(
103
+			'FHEE__EE_Form_Section_Proper___construct__options_array',
104
+			$options_array,
105
+			$this
106
+		);
107
+		// call parent first, as it may be setting the name
108
+		parent::__construct($options_array);
109
+		// if they've included subsections in the constructor, add them now
110
+		if (isset($options_array['include'])) {
111
+			// we are going to make sure we ONLY have those subsections to include
112
+			// AND we are going to make sure they're in that specified order
113
+			$reordered_subsections = array();
114
+			foreach ($options_array['include'] as $input_name) {
115
+				if (isset($this->_subsections[ $input_name ])) {
116
+					$reordered_subsections[ $input_name ] = $this->_subsections[ $input_name ];
117
+				}
118
+			}
119
+			$this->_subsections = $reordered_subsections;
120
+		}
121
+		if (isset($options_array['exclude'])) {
122
+			$exclude            = $options_array['exclude'];
123
+			$this->_subsections = array_diff_key($this->_subsections, array_flip($exclude));
124
+		}
125
+		if (isset($options_array['layout_strategy'])) {
126
+			$this->_layout_strategy = $options_array['layout_strategy'];
127
+		}
128
+		if (! $this->_layout_strategy) {
129
+			$this->_layout_strategy = is_admin() ? new EE_Admin_Two_Column_Layout() : new EE_Two_Column_Layout();
130
+		}
131
+		$this->_layout_strategy->_construct_finalize($this);
132
+		// ok so we are definitely going to want the forms JS,
133
+		// so enqueue it or remember to enqueue it during wp_enqueue_scripts
134
+		if (did_action('wp_enqueue_scripts') || did_action('admin_enqueue_scripts')) {
135
+			// ok so they've constructed this object after when they should have.
136
+			// just enqueue the generic form scripts and initialize the form immediately in the JS
137
+			EE_Form_Section_Proper::wp_enqueue_scripts(true);
138
+		} else {
139
+			add_action('wp_enqueue_scripts', array('EE_Form_Section_Proper', 'wp_enqueue_scripts'));
140
+			add_action('admin_enqueue_scripts', array('EE_Form_Section_Proper', 'wp_enqueue_scripts'));
141
+		}
142
+		add_action('wp_footer', array($this, 'ensure_scripts_localized'), 1);
143
+		/**
144
+		 * Gives other plugins a chance to hook in before construct finalize is called.
145
+		 * The form probably doesn't yet have a parent form section.
146
+		 * Since 4.9.32, when this action was introduced, this is the best place to add a subsection onto a form,
147
+		 * assuming you don't care what the form section's name, HTML ID, or HTML name etc are.
148
+		 * Also see AHEE__EE_Form_Section_Proper___construct_finalize__end
149
+		 *
150
+		 * @since 4.9.32
151
+		 * @param EE_Form_Section_Proper $this          before __construct is done, but all of its logic,
152
+		 *                                              except maybe calling _construct_finalize has been done
153
+		 * @param array                  $options_array options passed into the constructor
154
+		 */
155
+		do_action(
156
+			'AHEE__EE_Form_Input_Base___construct__before_construct_finalize_called',
157
+			$this,
158
+			$options_array
159
+		);
160
+		if (isset($options_array['name'])) {
161
+			$this->_construct_finalize(null, $options_array['name']);
162
+		}
163
+	}
164
+
165
+
166
+	/**
167
+	 * Finishes construction given the parent form section and this form section's name
168
+	 *
169
+	 * @param EE_Form_Section_Proper $parent_form_section
170
+	 * @param string                 $name
171
+	 * @throws EE_Error
172
+	 */
173
+	public function _construct_finalize($parent_form_section, $name)
174
+	{
175
+		parent::_construct_finalize($parent_form_section, $name);
176
+		$this->_set_default_name_if_empty();
177
+		$this->_set_default_html_id_if_empty();
178
+		foreach ($this->_subsections as $subsection_name => $subsection) {
179
+			if ($subsection instanceof EE_Form_Section_Base) {
180
+				$subsection->_construct_finalize($this, $subsection_name);
181
+			} else {
182
+				throw new EE_Error(
183
+					sprintf(
184
+						esc_html__(
185
+							'Subsection "%s" is not an instanceof EE_Form_Section_Base on form "%s". It is a "%s"',
186
+							'event_espresso'
187
+						),
188
+						$subsection_name,
189
+						get_class($this),
190
+						$subsection ? get_class($subsection) : esc_html__('NULL', 'event_espresso')
191
+					)
192
+				);
193
+			}
194
+		}
195
+		/**
196
+		 * Action performed just after form has been given a name (and HTML ID etc) and is fully constructed.
197
+		 * If you have code that should modify the form and needs it and its subsections to have a name, HTML ID
198
+		 * (or other attributes derived from the name like the HTML label id, etc), this is where it should be done.
199
+		 * This might only happen just before displaying the form, or just before it receives form submission data.
200
+		 * If you need to modify the form or its subsections before _construct_finalize is called on it (and we've
201
+		 * ensured it has a name, HTML IDs, etc
202
+		 *
203
+		 * @param EE_Form_Section_Proper      $this
204
+		 * @param EE_Form_Section_Proper|null $parent_form_section
205
+		 * @param string                      $name
206
+		 */
207
+		do_action(
208
+			'AHEE__EE_Form_Section_Proper___construct_finalize__end',
209
+			$this,
210
+			$parent_form_section,
211
+			$name
212
+		);
213
+	}
214
+
215
+
216
+	/**
217
+	 * Gets the layout strategy for this form section
218
+	 *
219
+	 * @return EE_Form_Section_Layout_Base
220
+	 */
221
+	public function get_layout_strategy()
222
+	{
223
+		return $this->_layout_strategy;
224
+	}
225
+
226
+
227
+	/**
228
+	 * Gets the HTML for a single input for this form section according
229
+	 * to the layout strategy
230
+	 *
231
+	 * @param EE_Form_Input_Base $input
232
+	 * @return string
233
+	 */
234
+	public function get_html_for_input($input)
235
+	{
236
+		return $this->_layout_strategy->layout_input($input);
237
+	}
238
+
239
+
240
+	/**
241
+	 * was_submitted - checks if form inputs are present in request data
242
+	 * Basically an alias for form_data_present_in() (which is used by both
243
+	 * proper form sections and form inputs)
244
+	 *
245
+	 * @param null $form_data
246
+	 * @return boolean
247
+	 * @throws EE_Error
248
+	 */
249
+	public function was_submitted($form_data = null)
250
+	{
251
+		return $this->form_data_present_in($form_data);
252
+	}
253
+
254
+	/**
255
+	 * Gets the cached request data; but if there is none, or $req_data was set with
256
+	 * something different, refresh the cache, and then return it
257
+	 * @param null $req_data
258
+	 * @return array
259
+	 */
260
+	protected function getCachedRequest($req_data = null)
261
+	{
262
+		if (
263
+			$this->cached_request_data === null
264
+			|| (
265
+				$req_data !== null
266
+				&& $req_data !== $this->cached_request_data
267
+			)
268
+		) {
269
+			$req_data = apply_filters(
270
+				'FHEE__EE_Form_Section_Proper__receive_form_submission__req_data',
271
+				$req_data,
272
+				$this
273
+			);
274
+			if ($req_data === null) {
275
+				/** @var RequestInterface $request */
276
+				$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
277
+				$req_data = $request->requestParams();
278
+			}
279
+			$req_data = apply_filters(
280
+				'FHEE__EE_Form_Section_Proper__receive_form_submission__request_data',
281
+				$req_data,
282
+				$this
283
+			);
284
+			$this->cached_request_data = (array) $req_data;
285
+		}
286
+		return $this->cached_request_data;
287
+	}
288
+
289
+
290
+	/**
291
+	 * After the form section is initially created, call this to sanitize the data in the submission
292
+	 * which relates to this form section, validate it, and set it as properties on the form.
293
+	 *
294
+	 * @param array|null $req_data should usually be post data (the default).
295
+	 *                             However, you CAN supply a different array.
296
+	 *                             Consider using set_defaults() instead however.
297
+	 *                             (If you rendered the form in the page using $form_x->get_html()
298
+	 *                             the inputs will have the correct name in the request data for this function
299
+	 *                             to find them and populate the form with them.
300
+	 *                             If you have a flat form (with only input subsections),
301
+	 *                             you can supply a flat array where keys
302
+	 *                             are the form input names and values are their values)
303
+	 * @param boolean    $validate whether or not to perform validation on this data. Default is,
304
+	 *                             of course, to validate that data, and set errors on the invalid values.
305
+	 *                             But if the data has already been validated
306
+	 *                             (eg you validated the data then stored it in the DB)
307
+	 *                             you may want to skip this step.
308
+	 * @throws InvalidArgumentException
309
+	 * @throws InvalidInterfaceException
310
+	 * @throws InvalidDataTypeException
311
+	 * @throws EE_Error
312
+	 */
313
+	public function receive_form_submission($req_data = null, $validate = true)
314
+	{
315
+		$req_data = $this->getCachedRequest($req_data);
316
+		$this->_normalize($req_data);
317
+		if ($validate) {
318
+			$this->_validate();
319
+			// if it's invalid, we're going to want to re-display so remember what they submitted
320
+			if (! $this->is_valid()) {
321
+				$this->store_submitted_form_data_in_session();
322
+			}
323
+		}
324
+		if ($this->submission_error_message() === '' && ! $this->is_valid()) {
325
+			$this->set_submission_error_message();
326
+		}
327
+		do_action(
328
+			'AHEE__EE_Form_Section_Proper__receive_form_submission__end',
329
+			$req_data,
330
+			$this,
331
+			$validate
332
+		);
333
+	}
334
+
335
+
336
+	/**
337
+	 * caches the originally submitted input values in the session
338
+	 * so that they can be used to repopulate the form if it failed validation
339
+	 *
340
+	 * @return boolean whether or not the data was successfully stored in the session
341
+	 * @throws InvalidArgumentException
342
+	 * @throws InvalidInterfaceException
343
+	 * @throws InvalidDataTypeException
344
+	 * @throws EE_Error
345
+	 */
346
+	protected function store_submitted_form_data_in_session()
347
+	{
348
+		$session = EE_Registry::instance()->SSN;
349
+		if ($session instanceof EE_Session) {
350
+			return EE_Registry::instance()->SSN->set_session_data(
351
+				[
352
+					EE_Form_Section_Proper::SUBMITTED_FORM_DATA_SSN_KEY => $this->submitted_values(true),
353
+				]
354
+			);
355
+		}
356
+		return false;
357
+	}
358
+
359
+
360
+	/**
361
+	 * retrieves the originally submitted input values in the session
362
+	 * so that they can be used to repopulate the form if it failed validation
363
+	 *
364
+	 * @return array
365
+	 * @throws InvalidArgumentException
366
+	 * @throws InvalidInterfaceException
367
+	 * @throws InvalidDataTypeException
368
+	 */
369
+	protected function get_submitted_form_data_from_session()
370
+	{
371
+		$session = EE_Registry::instance()->SSN;
372
+		if ($session instanceof EE_Session) {
373
+			return $session->get_session_data(
374
+				EE_Form_Section_Proper::SUBMITTED_FORM_DATA_SSN_KEY
375
+			);
376
+		}
377
+		return array();
378
+	}
379
+
380
+
381
+	/**
382
+	 * flushed the originally submitted input values from the session
383
+	 *
384
+	 * @return boolean whether or not the data was successfully removed from the session
385
+	 * @throws InvalidArgumentException
386
+	 * @throws InvalidInterfaceException
387
+	 * @throws InvalidDataTypeException
388
+	 */
389
+	public static function flush_submitted_form_data_from_session()
390
+	{
391
+		return EE_Registry::instance()->SSN->reset_data(
392
+			array(EE_Form_Section_Proper::SUBMITTED_FORM_DATA_SSN_KEY)
393
+		);
394
+	}
395
+
396
+
397
+	/**
398
+	 * Populates this form and its subsections with data from the session.
399
+	 * (Wrapper for EE_Form_Section_Proper::receive_form_submission, so it shows
400
+	 * validation errors when displaying too)
401
+	 * Returns true if the form was populated from the session, false otherwise
402
+	 *
403
+	 * @return boolean
404
+	 * @throws InvalidArgumentException
405
+	 * @throws InvalidInterfaceException
406
+	 * @throws InvalidDataTypeException
407
+	 * @throws EE_Error
408
+	 */
409
+	public function populate_from_session()
410
+	{
411
+		$form_data_in_session = $this->get_submitted_form_data_from_session();
412
+		if (empty($form_data_in_session)) {
413
+			return false;
414
+		}
415
+		$this->receive_form_submission($form_data_in_session);
416
+		add_action('shutdown', array('EE_Form_Section_Proper', 'flush_submitted_form_data_from_session'));
417
+		if ($this->form_data_present_in($form_data_in_session)) {
418
+			return true;
419
+		}
420
+		return false;
421
+	}
422
+
423
+
424
+	/**
425
+	 * Populates the default data for the form, given an array where keys are
426
+	 * the input names, and values are their values (preferably normalized to be their
427
+	 * proper PHP types, not all strings... although that should be ok too).
428
+	 * Proper subsections are sub-arrays, the key being the subsection's name, and
429
+	 * the value being an array formatted in teh same way
430
+	 *
431
+	 * @param array $default_data
432
+	 * @throws EE_Error
433
+	 */
434
+	public function populate_defaults($default_data)
435
+	{
436
+		foreach ($this->subsections(false) as $subsection_name => $subsection) {
437
+			if (isset($default_data[ $subsection_name ])) {
438
+				if ($subsection instanceof EE_Form_Input_Base) {
439
+					$subsection->set_default($default_data[ $subsection_name ]);
440
+				} elseif ($subsection instanceof EE_Form_Section_Proper) {
441
+					$subsection->populate_defaults($default_data[ $subsection_name ]);
442
+				}
443
+			}
444
+		}
445
+	}
446
+
447
+
448
+	/**
449
+	 * returns true if subsection exists
450
+	 *
451
+	 * @param string $name
452
+	 * @return boolean
453
+	 */
454
+	public function subsection_exists($name)
455
+	{
456
+		return isset($this->_subsections[ $name ]) ? true : false;
457
+	}
458
+
459
+
460
+	/**
461
+	 * Gets the subsection specified by its name
462
+	 *
463
+	 * @param string  $name
464
+	 * @param boolean $require_construction_to_be_finalized most client code should leave this as TRUE
465
+	 *                                                      so that the inputs will be properly configured.
466
+	 *                                                      However, some client code may be ok
467
+	 *                                                      with construction finalize being called later
468
+	 *                                                      (realizing that the subsections' html names
469
+	 *                                                      might not be set yet, etc.)
470
+	 * @return EE_Form_Section_Base
471
+	 * @throws EE_Error
472
+	 */
473
+	public function get_subsection($name, $require_construction_to_be_finalized = true)
474
+	{
475
+		if ($require_construction_to_be_finalized) {
476
+			$this->ensure_construct_finalized_called();
477
+		}
478
+		return $this->subsection_exists($name) ? $this->_subsections[ $name ] : null;
479
+	}
480
+
481
+
482
+	/**
483
+	 * Gets all the validatable subsections of this form section
484
+	 *
485
+	 * @return EE_Form_Section_Validatable[]
486
+	 * @throws EE_Error
487
+	 */
488
+	public function get_validatable_subsections()
489
+	{
490
+		$validatable_subsections = array();
491
+		foreach ($this->subsections() as $name => $obj) {
492
+			if ($obj instanceof EE_Form_Section_Validatable) {
493
+				$validatable_subsections[ $name ] = $obj;
494
+			}
495
+		}
496
+		return $validatable_subsections;
497
+	}
498
+
499
+
500
+	/**
501
+	 * Gets an input by the given name. If not found, or if its not an EE_FOrm_Input_Base child,
502
+	 * throw an EE_Error.
503
+	 *
504
+	 * @param string  $name
505
+	 * @param boolean $require_construction_to_be_finalized most client code should
506
+	 *                                                      leave this as TRUE so that the inputs will be properly
507
+	 *                                                      configured. However, some client code may be ok with
508
+	 *                                                      construction finalize being called later
509
+	 *                                                      (realizing that the subsections' html names might not be
510
+	 *                                                      set yet, etc.)
511
+	 * @return EE_Form_Input_Base
512
+	 * @throws EE_Error
513
+	 */
514
+	public function get_input($name, $require_construction_to_be_finalized = true)
515
+	{
516
+		$subsection = $this->get_subsection(
517
+			$name,
518
+			$require_construction_to_be_finalized
519
+		);
520
+		if (! $subsection instanceof EE_Form_Input_Base) {
521
+			throw new EE_Error(
522
+				sprintf(
523
+					esc_html__(
524
+						"Subsection '%s' is not an instanceof EE_Form_Input_Base on form '%s'. It is a '%s'",
525
+						'event_espresso'
526
+					),
527
+					$name,
528
+					get_class($this),
529
+					$subsection ? get_class($subsection) : esc_html__('NULL', 'event_espresso')
530
+				)
531
+			);
532
+		}
533
+		return $subsection;
534
+	}
535
+
536
+
537
+	/**
538
+	 * Like get_input(), gets the proper subsection of the form given the name,
539
+	 * otherwise throws an EE_Error
540
+	 *
541
+	 * @param string  $name
542
+	 * @param boolean $require_construction_to_be_finalized most client code should
543
+	 *                                                      leave this as TRUE so that the inputs will be properly
544
+	 *                                                      configured. However, some client code may be ok with
545
+	 *                                                      construction finalize being called later
546
+	 *                                                      (realizing that the subsections' html names might not be
547
+	 *                                                      set yet, etc.)
548
+	 * @return EE_Form_Section_Proper
549
+	 * @throws EE_Error
550
+	 */
551
+	public function get_proper_subsection($name, $require_construction_to_be_finalized = true)
552
+	{
553
+		$subsection = $this->get_subsection(
554
+			$name,
555
+			$require_construction_to_be_finalized
556
+		);
557
+		if (! $subsection instanceof EE_Form_Section_Proper) {
558
+			throw new EE_Error(
559
+				sprintf(
560
+					esc_html__(
561
+						"Subsection '%'s is not an instanceof EE_Form_Section_Proper on form '%s'",
562
+						'event_espresso'
563
+					),
564
+					$name,
565
+					get_class($this)
566
+				)
567
+			);
568
+		}
569
+		return $subsection;
570
+	}
571
+
572
+
573
+	/**
574
+	 * Gets the value of the specified input. Should be called after receive_form_submission()
575
+	 * or populate_defaults() on the form, where the normalized value on the input is set.
576
+	 *
577
+	 * @param string $name
578
+	 * @return mixed depending on the input's type and its normalization strategy
579
+	 * @throws EE_Error
580
+	 */
581
+	public function get_input_value($name)
582
+	{
583
+		$input = $this->get_input($name);
584
+		return $input->normalized_value();
585
+	}
586
+
587
+
588
+	/**
589
+	 * Checks if this form section itself is valid, and then checks its subsections
590
+	 *
591
+	 * @throws EE_Error
592
+	 * @return boolean
593
+	 */
594
+	public function is_valid()
595
+	{
596
+		if ($this->is_valid === null) {
597
+			if (! $this->has_received_submission()) {
598
+				throw new EE_Error(
599
+					sprintf(
600
+						esc_html__(
601
+							'You cannot check if a form is valid before receiving the form submission using receive_form_submission',
602
+							'event_espresso'
603
+						)
604
+					)
605
+				);
606
+			}
607
+			if (! parent::is_valid()) {
608
+				$this->is_valid = false;
609
+			} else {
610
+				// ok so no general errors to this entire form section.
611
+				// so let's check the subsections, but only set errors if that hasn't been done yet
612
+				$this->is_valid = true;
613
+				foreach ($this->get_validatable_subsections() as $subsection) {
614
+					if (! $subsection->is_valid()) {
615
+						$this->is_valid = false;
616
+					}
617
+				}
618
+			}
619
+		}
620
+		return $this->is_valid;
621
+	}
622
+
623
+
624
+	/**
625
+	 * gets the default name of this form section if none is specified
626
+	 *
627
+	 * @return void
628
+	 */
629
+	protected function _set_default_name_if_empty()
630
+	{
631
+		if (! $this->_name) {
632
+			$classname    = get_class($this);
633
+			$default_name = str_replace('EE_', '', $classname);
634
+			$this->_name  = $default_name;
635
+		}
636
+	}
637
+
638
+
639
+	/**
640
+	 * Returns the HTML for the form, except for the form opening and closing tags
641
+	 * (as the form section doesn't know where you necessarily want to send the information to),
642
+	 * and except for a submit button. Enqueues JS and CSS; if called early enough we will
643
+	 * try to enqueue them in the header, otherwise they'll be enqueued in the footer.
644
+	 * Not doing_it_wrong because theoretically this CAN be used properly,
645
+	 * provided its used during "wp_enqueue_scripts", or it doesn't need to enqueue
646
+	 * any CSS.
647
+	 *
648
+	 * @throws InvalidArgumentException
649
+	 * @throws InvalidInterfaceException
650
+	 * @throws InvalidDataTypeException
651
+	 * @throws EE_Error
652
+	 */
653
+	public function get_html_and_js()
654
+	{
655
+		$this->enqueue_js();
656
+		return $this->get_html();
657
+	}
658
+
659
+
660
+	/**
661
+	 * returns HTML for displaying this form section. recursively calls display_section() on all subsections
662
+	 *
663
+	 * @param bool $display_previously_submitted_data
664
+	 * @return string
665
+	 * @throws InvalidArgumentException
666
+	 * @throws InvalidInterfaceException
667
+	 * @throws InvalidDataTypeException
668
+	 * @throws EE_Error
669
+	 * @throws EE_Error
670
+	 * @throws EE_Error
671
+	 */
672
+	public function get_html($display_previously_submitted_data = true)
673
+	{
674
+		$this->ensure_construct_finalized_called();
675
+		if ($display_previously_submitted_data) {
676
+			$this->populate_from_session();
677
+		}
678
+		return $this->_form_html_filter
679
+			? $this->_form_html_filter->filterHtml($this->_layout_strategy->layout_form(), $this)
680
+			: $this->_layout_strategy->layout_form();
681
+	}
682
+
683
+
684
+	/**
685
+	 * enqueues JS and CSS for the form.
686
+	 * It is preferred to call this before wp_enqueue_scripts so the
687
+	 * scripts and styles can be put in the header, but if called later
688
+	 * they will be put in the footer (which is OK for JS, but in HTML4 CSS should
689
+	 * only be in the header; but in HTML5 its ok in the body.
690
+	 * See http://stackoverflow.com/questions/4957446/load-external-css-file-in-body-tag.
691
+	 * So if your form enqueues CSS, it's preferred to call this before wp_enqueue_scripts.)
692
+	 *
693
+	 * @return void
694
+	 * @throws EE_Error
695
+	 */
696
+	public function enqueue_js()
697
+	{
698
+		$this->_enqueue_and_localize_form_js();
699
+		foreach ($this->subsections() as $subsection) {
700
+			$subsection->enqueue_js();
701
+		}
702
+	}
703
+
704
+
705
+	/**
706
+	 * adds a filter so that jquery validate gets enqueued in EE_System::wp_enqueue_scripts().
707
+	 * This must be done BEFORE wp_enqueue_scripts() gets called, which is on
708
+	 * the wp_enqueue_scripts hook.
709
+	 * However, registering the form js and localizing it can happen when we
710
+	 * actually output the form (which is preferred, seeing how teh form's fields
711
+	 * could change until it's actually outputted)
712
+	 *
713
+	 * @param boolean $init_form_validation_automatically whether or not we want the form validation
714
+	 *                                                    to be triggered automatically or not
715
+	 * @return void
716
+	 */
717
+	public static function wp_enqueue_scripts($init_form_validation_automatically = true)
718
+	{
719
+		wp_register_script(
720
+			'ee_form_section_validation',
721
+			EE_GLOBAL_ASSETS_URL . 'scripts' . '/form_section_validation.js',
722
+			array('jquery-validate', 'jquery-ui-datepicker', 'jquery-validate-extra-methods'),
723
+			EVENT_ESPRESSO_VERSION,
724
+			true
725
+		);
726
+		wp_localize_script(
727
+			'ee_form_section_validation',
728
+			'ee_form_section_validation_init',
729
+			array('init' => $init_form_validation_automatically ? '1' : '0')
730
+		);
731
+	}
732
+
733
+
734
+	/**
735
+	 * gets the variables used by form_section_validation.js.
736
+	 * This needs to be called AFTER we've called $this->_enqueue_jquery_validate_script,
737
+	 * but before the wordpress hook wp_loaded
738
+	 *
739
+	 * @throws EE_Error
740
+	 */
741
+	public function _enqueue_and_localize_form_js()
742
+	{
743
+		$this->ensure_construct_finalized_called();
744
+		// actually, we don't want to localize just yet. There may be other forms on the page.
745
+		// so we need to add our form section data to a static variable accessible by all form sections
746
+		// and localize it just before the footer
747
+		$this->localize_validation_rules();
748
+		add_action('wp_footer', array('EE_Form_Section_Proper', 'localize_script_for_all_forms'), 2);
749
+		add_action('admin_footer', array('EE_Form_Section_Proper', 'localize_script_for_all_forms'));
750
+	}
751
+
752
+
753
+	/**
754
+	 * add our form section data to a static variable accessible by all form sections
755
+	 *
756
+	 * @param bool $return_for_subsection
757
+	 * @return void
758
+	 * @throws EE_Error
759
+	 */
760
+	public function localize_validation_rules($return_for_subsection = false)
761
+	{
762
+		// we only want to localize vars ONCE for the entire form,
763
+		// so if the form section doesn't have a parent, then it must be the top dog
764
+		if ($return_for_subsection || ! $this->parent_section()) {
765
+			EE_Form_Section_Proper::$_js_localization['form_data'][ $this->html_id() ] = array(
766
+				'form_section_id'  => $this->html_id(true),
767
+				'validation_rules' => $this->get_jquery_validation_rules(),
768
+				'other_data'       => $this->get_other_js_data(),
769
+				'errors'           => $this->subsection_validation_errors_by_html_name(),
770
+			);
771
+			EE_Form_Section_Proper::$_scripts_localized                                = true;
772
+		}
773
+	}
774
+
775
+
776
+	/**
777
+	 * Gets an array of extra data that will be useful for client-side javascript.
778
+	 * This is primarily data added by inputs and forms in addition to any
779
+	 * scripts they might enqueue
780
+	 *
781
+	 * @param array $form_other_js_data
782
+	 * @return array
783
+	 * @throws EE_Error
784
+	 */
785
+	public function get_other_js_data($form_other_js_data = array())
786
+	{
787
+		foreach ($this->subsections() as $subsection) {
788
+			$form_other_js_data = $subsection->get_other_js_data($form_other_js_data);
789
+		}
790
+		return $form_other_js_data;
791
+	}
792
+
793
+
794
+	/**
795
+	 * Gets a flat array of inputs for this form section and its subsections.
796
+	 * Keys are their form names, and values are the inputs themselves
797
+	 *
798
+	 * @return EE_Form_Input_Base
799
+	 * @throws EE_Error
800
+	 */
801
+	public function inputs_in_subsections()
802
+	{
803
+		$inputs = array();
804
+		foreach ($this->subsections() as $subsection) {
805
+			if ($subsection instanceof EE_Form_Input_Base) {
806
+				$inputs[ $subsection->html_name() ] = $subsection;
807
+			} elseif ($subsection instanceof EE_Form_Section_Proper) {
808
+				$inputs += $subsection->inputs_in_subsections();
809
+			}
810
+		}
811
+		return $inputs;
812
+	}
813
+
814
+
815
+	/**
816
+	 * Gets a flat array of all the validation errors.
817
+	 * Keys are html names (because those should be unique)
818
+	 * and values are a string of all their validation errors
819
+	 *
820
+	 * @return string[]
821
+	 * @throws EE_Error
822
+	 */
823
+	public function subsection_validation_errors_by_html_name()
824
+	{
825
+		$inputs = $this->inputs();
826
+		$errors = array();
827
+		foreach ($inputs as $form_input) {
828
+			if ($form_input instanceof EE_Form_Input_Base && $form_input->get_validation_errors()) {
829
+				$errors[ $form_input->html_name() ] = $form_input->get_validation_error_string();
830
+			}
831
+		}
832
+		return $errors;
833
+	}
834
+
835
+
836
+	/**
837
+	 * passes all the form data required by the JS to the JS, and enqueues the few required JS files.
838
+	 * Should be setup by each form during the _enqueues_and_localize_form_js
839
+	 *
840
+	 * @throws InvalidArgumentException
841
+	 * @throws InvalidInterfaceException
842
+	 * @throws InvalidDataTypeException
843
+	 */
844
+	public static function localize_script_for_all_forms()
845
+	{
846
+		// allow inputs and stuff to hook in their JS and stuff here
847
+		do_action('AHEE__EE_Form_Section_Proper__localize_script_for_all_forms__begin');
848
+		EE_Form_Section_Proper::$_js_localization['localized_error_messages'] = EE_Form_Section_Proper::_get_localized_error_messages();
849
+		$email_validation_level = isset(EE_Registry::instance()->CFG->registration->email_validation_level)
850
+			? EE_Registry::instance()->CFG->registration->email_validation_level
851
+			: 'wp_default';
852
+		EE_Form_Section_Proper::$_js_localization['email_validation_level']   = $email_validation_level;
853
+		wp_enqueue_script('ee_form_section_validation');
854
+		wp_localize_script(
855
+			'ee_form_section_validation',
856
+			'ee_form_section_vars',
857
+			EE_Form_Section_Proper::$_js_localization
858
+		);
859
+	}
860
+
861
+
862
+	/**
863
+	 * ensure_scripts_localized
864
+	 *
865
+	 * @throws EE_Error
866
+	 */
867
+	public function ensure_scripts_localized()
868
+	{
869
+		if (! EE_Form_Section_Proper::$_scripts_localized) {
870
+			$this->_enqueue_and_localize_form_js();
871
+		}
872
+	}
873
+
874
+
875
+	/**
876
+	 * Gets the hard-coded validation error messages to be used in the JS. The convention
877
+	 * is that the key here should be the same as the custom validation rule put in the JS file
878
+	 *
879
+	 * @return array keys are custom validation rules, and values are internationalized strings
880
+	 */
881
+	private static function _get_localized_error_messages()
882
+	{
883
+		return array(
884
+			'validUrl' => wp_strip_all_tags(__('This is not a valid absolute URL. Eg, http://domain.com/monkey.jpg', 'event_espresso')),
885
+			'regex'    => wp_strip_all_tags(__('Please check your input', 'event_espresso'))
886
+		);
887
+	}
888
+
889
+
890
+	/**
891
+	 * @return array
892
+	 */
893
+	public static function js_localization()
894
+	{
895
+		return self::$_js_localization;
896
+	}
897
+
898
+
899
+	/**
900
+	 * @return void
901
+	 */
902
+	public static function reset_js_localization()
903
+	{
904
+		self::$_js_localization = array();
905
+	}
906
+
907
+
908
+	/**
909
+	 * Gets the JS to put inside the jquery validation rules for subsection of this form section.
910
+	 * See parent function for more...
911
+	 *
912
+	 * @return array
913
+	 * @throws EE_Error
914
+	 */
915
+	public function get_jquery_validation_rules()
916
+	{
917
+		$jquery_validation_rules = array();
918
+		foreach ($this->get_validatable_subsections() as $subsection) {
919
+			$jquery_validation_rules = array_merge(
920
+				$jquery_validation_rules,
921
+				$subsection->get_jquery_validation_rules()
922
+			);
923
+		}
924
+		return $jquery_validation_rules;
925
+	}
926
+
927
+
928
+	/**
929
+	 * Sanitizes all the data and sets the sanitized value of each field
930
+	 *
931
+	 * @param array $req_data
932
+	 * @return void
933
+	 * @throws EE_Error
934
+	 */
935
+	protected function _normalize($req_data)
936
+	{
937
+		$this->_received_submission = true;
938
+		$this->_validation_errors   = array();
939
+		foreach ($this->get_validatable_subsections() as $subsection) {
940
+			try {
941
+				$subsection->_normalize($req_data);
942
+			} catch (EE_Validation_Error $e) {
943
+				$subsection->add_validation_error($e);
944
+			}
945
+		}
946
+	}
947
+
948
+
949
+	/**
950
+	 * Performs validation on this form section and its subsections.
951
+	 * For each subsection,
952
+	 * calls _validate_{subsection_name} on THIS form (if the function exists)
953
+	 * and passes it the subsection, then calls _validate on that subsection.
954
+	 * If you need to perform validation on the form as a whole (considering multiple)
955
+	 * you would be best to override this _validate method,
956
+	 * calling parent::_validate() first.
957
+	 *
958
+	 * @throws EE_Error
959
+	 */
960
+	protected function _validate()
961
+	{
962
+		// reset the cache of whether this form is valid or not- we're re-validating it now
963
+		$this->is_valid = null;
964
+		foreach ($this->get_validatable_subsections() as $subsection_name => $subsection) {
965
+			if (method_exists($this, '_validate_' . $subsection_name)) {
966
+				call_user_func_array(array($this, '_validate_' . $subsection_name), array($subsection));
967
+			}
968
+			$subsection->_validate();
969
+		}
970
+	}
971
+
972
+
973
+	/**
974
+	 * Gets all the validated inputs for the form section
975
+	 *
976
+	 * @return array
977
+	 * @throws EE_Error
978
+	 */
979
+	public function valid_data()
980
+	{
981
+		$inputs = array();
982
+		foreach ($this->subsections() as $subsection_name => $subsection) {
983
+			if ($subsection instanceof EE_Form_Section_Proper) {
984
+				$inputs[ $subsection_name ] = $subsection->valid_data();
985
+			} elseif ($subsection instanceof EE_Form_Input_Base) {
986
+				$inputs[ $subsection_name ] = $subsection->normalized_value();
987
+			}
988
+		}
989
+		return $inputs;
990
+	}
991
+
992
+
993
+	/**
994
+	 * Gets all the inputs on this form section
995
+	 *
996
+	 * @return EE_Form_Input_Base[]
997
+	 * @throws EE_Error
998
+	 */
999
+	public function inputs()
1000
+	{
1001
+		$inputs = array();
1002
+		foreach ($this->subsections() as $subsection_name => $subsection) {
1003
+			if ($subsection instanceof EE_Form_Input_Base) {
1004
+				$inputs[ $subsection_name ] = $subsection;
1005
+			}
1006
+		}
1007
+		return $inputs;
1008
+	}
1009
+
1010
+
1011
+	/**
1012
+	 * Gets all the subsections which are a proper form
1013
+	 *
1014
+	 * @return EE_Form_Section_Proper[]
1015
+	 * @throws EE_Error
1016
+	 */
1017
+	public function subforms()
1018
+	{
1019
+		$form_sections = array();
1020
+		foreach ($this->subsections() as $name => $obj) {
1021
+			if ($obj instanceof EE_Form_Section_Proper) {
1022
+				$form_sections[ $name ] = $obj;
1023
+			}
1024
+		}
1025
+		return $form_sections;
1026
+	}
1027
+
1028
+
1029
+	/**
1030
+	 * Gets all the subsections (inputs, proper subsections, or html-only sections).
1031
+	 * Consider using inputs() or subforms()
1032
+	 * if you only want form inputs or proper form sections.
1033
+	 *
1034
+	 * @param boolean $require_construction_to_be_finalized most client code should
1035
+	 *                                                      leave this as TRUE so that the inputs will be properly
1036
+	 *                                                      configured. However, some client code may be ok with
1037
+	 *                                                      construction finalize being called later
1038
+	 *                                                      (realizing that the subsections' html names might not be
1039
+	 *                                                      set yet, etc.)
1040
+	 * @return EE_Form_Section_Proper[]
1041
+	 * @throws EE_Error
1042
+	 */
1043
+	public function subsections($require_construction_to_be_finalized = true)
1044
+	{
1045
+		if ($require_construction_to_be_finalized) {
1046
+			$this->ensure_construct_finalized_called();
1047
+		}
1048
+		return $this->_subsections;
1049
+	}
1050
+
1051
+
1052
+	/**
1053
+	 * Returns whether this form has any subforms or inputs
1054
+	 * @return bool
1055
+	 */
1056
+	public function hasSubsections()
1057
+	{
1058
+		return ! empty($this->_subsections);
1059
+	}
1060
+
1061
+
1062
+	/**
1063
+	 * Returns a simple array where keys are input names, and values are their normalized
1064
+	 * values. (Similar to calling get_input_value on inputs)
1065
+	 *
1066
+	 * @param boolean $include_subform_inputs Whether to include inputs from subforms,
1067
+	 *                                        or just this forms' direct children inputs
1068
+	 * @param boolean $flatten                Whether to force the results into 1-dimensional array,
1069
+	 *                                        or allow multidimensional array
1070
+	 * @return array if $flatten is TRUE it will always be a 1-dimensional array
1071
+	 *                                        with array keys being input names
1072
+	 *                                        (regardless of whether they are from a subsection or not),
1073
+	 *                                        and if $flatten is FALSE it can be a multidimensional array
1074
+	 *                                        where keys are always subsection names and values are either
1075
+	 *                                        the input's normalized value, or an array like the top-level array
1076
+	 * @throws EE_Error
1077
+	 */
1078
+	public function input_values($include_subform_inputs = false, $flatten = false)
1079
+	{
1080
+		return $this->_input_values(false, $include_subform_inputs, $flatten);
1081
+	}
1082
+
1083
+
1084
+	/**
1085
+	 * Similar to EE_Form_Section_Proper::input_values(), except this returns the 'display_value'
1086
+	 * of each input. On some inputs (especially radio boxes or checkboxes), the value stored
1087
+	 * is not necessarily the value we want to display to users. This creates an array
1088
+	 * where keys are the input names, and values are their display values
1089
+	 *
1090
+	 * @param boolean $include_subform_inputs Whether to include inputs from subforms,
1091
+	 *                                        or just this forms' direct children inputs
1092
+	 * @param boolean $flatten                Whether to force the results into 1-dimensional array,
1093
+	 *                                        or allow multidimensional array
1094
+	 * @return array if $flatten is TRUE it will always be a 1-dimensional array
1095
+	 *                                        with array keys being input names
1096
+	 *                                        (regardless of whether they are from a subsection or not),
1097
+	 *                                        and if $flatten is FALSE it can be a multidimensional array
1098
+	 *                                        where keys are always subsection names and values are either
1099
+	 *                                        the input's normalized value, or an array like the top-level array
1100
+	 * @throws EE_Error
1101
+	 */
1102
+	public function input_pretty_values($include_subform_inputs = false, $flatten = false)
1103
+	{
1104
+		return $this->_input_values(true, $include_subform_inputs, $flatten);
1105
+	}
1106
+
1107
+
1108
+	/**
1109
+	 * Gets the input values from the form
1110
+	 *
1111
+	 * @param boolean $pretty                 Whether to retrieve the pretty value,
1112
+	 *                                        or just the normalized value
1113
+	 * @param boolean $include_subform_inputs Whether to include inputs from subforms,
1114
+	 *                                        or just this forms' direct children inputs
1115
+	 * @param boolean $flatten                Whether to force the results into 1-dimensional array,
1116
+	 *                                        or allow multidimensional array
1117
+	 * @return array if $flatten is TRUE it will always be a 1-dimensional array with array keys being
1118
+	 *                                        input names (regardless of whether they are from a subsection or not),
1119
+	 *                                        and if $flatten is FALSE it can be a multidimensional array
1120
+	 *                                        where keys are always subsection names and values are either
1121
+	 *                                        the input's normalized value, or an array like the top-level array
1122
+	 * @throws EE_Error
1123
+	 */
1124
+	public function _input_values($pretty = false, $include_subform_inputs = false, $flatten = false)
1125
+	{
1126
+		$input_values = array();
1127
+		foreach ($this->subsections() as $subsection_name => $subsection) {
1128
+			if ($subsection instanceof EE_Form_Input_Base) {
1129
+				$input_values[ $subsection_name ] = $pretty
1130
+					? $subsection->pretty_value()
1131
+					: $subsection->normalized_value();
1132
+			} elseif ($subsection instanceof EE_Form_Section_Proper && $include_subform_inputs) {
1133
+				$subform_input_values = $subsection->_input_values(
1134
+					$pretty,
1135
+					$include_subform_inputs,
1136
+					$flatten
1137
+				);
1138
+				if ($flatten) {
1139
+					$input_values = array_merge($input_values, $subform_input_values);
1140
+				} else {
1141
+					$input_values[ $subsection_name ] = $subform_input_values;
1142
+				}
1143
+			}
1144
+		}
1145
+		return $input_values;
1146
+	}
1147
+
1148
+
1149
+	/**
1150
+	 * Gets the originally submitted input values from the form
1151
+	 *
1152
+	 * @param boolean $include_subforms  Whether to include inputs from subforms,
1153
+	 *                                   or just this forms' direct children inputs
1154
+	 * @return array                     if $flatten is TRUE it will always be a 1-dimensional array
1155
+	 *                                   with array keys being input names
1156
+	 *                                   (regardless of whether they are from a subsection or not),
1157
+	 *                                   and if $flatten is FALSE it can be a multidimensional array
1158
+	 *                                   where keys are always subsection names and values are either
1159
+	 *                                   the input's normalized value, or an array like the top-level array
1160
+	 * @throws EE_Error
1161
+	 */
1162
+	public function submitted_values($include_subforms = false)
1163
+	{
1164
+		$submitted_values = array();
1165
+		foreach ($this->subsections() as $subsection) {
1166
+			if ($subsection instanceof EE_Form_Input_Base) {
1167
+				// is this input part of an array of inputs?
1168
+				if (strpos($subsection->html_name(), '[') !== false) {
1169
+					$full_input_name  = EEH_Array::convert_array_values_to_keys(
1170
+						explode(
1171
+							'[',
1172
+							str_replace(']', '', $subsection->html_name())
1173
+						),
1174
+						$subsection->raw_value()
1175
+					);
1176
+					$submitted_values = array_replace_recursive($submitted_values, $full_input_name);
1177
+				} else {
1178
+					$submitted_values[ $subsection->html_name() ] = $subsection->raw_value();
1179
+				}
1180
+			} elseif ($subsection instanceof EE_Form_Section_Proper && $include_subforms) {
1181
+				$subform_input_values = $subsection->submitted_values($include_subforms);
1182
+				$submitted_values     = array_replace_recursive($submitted_values, $subform_input_values);
1183
+			}
1184
+		}
1185
+		return $submitted_values;
1186
+	}
1187
+
1188
+
1189
+	/**
1190
+	 * Indicates whether or not this form has received a submission yet
1191
+	 * (ie, had receive_form_submission called on it yet)
1192
+	 *
1193
+	 * @return boolean
1194
+	 * @throws EE_Error
1195
+	 */
1196
+	public function has_received_submission()
1197
+	{
1198
+		$this->ensure_construct_finalized_called();
1199
+		return $this->_received_submission;
1200
+	}
1201
+
1202
+
1203
+	/**
1204
+	 * Equivalent to passing 'exclude' in the constructor's options array.
1205
+	 * Removes the listed inputs from the form
1206
+	 *
1207
+	 * @param array $inputs_to_exclude values are the input names
1208
+	 * @return void
1209
+	 */
1210
+	public function exclude(array $inputs_to_exclude = array())
1211
+	{
1212
+		foreach ($inputs_to_exclude as $input_to_exclude_name) {
1213
+			unset($this->_subsections[ $input_to_exclude_name ]);
1214
+		}
1215
+	}
1216
+
1217
+
1218
+	/**
1219
+	 * Changes these inputs' display strategy to be EE_Hidden_Display_Strategy.
1220
+	 * @param array $inputs_to_hide
1221
+	 * @throws EE_Error
1222
+	 */
1223
+	public function hide(array $inputs_to_hide = array())
1224
+	{
1225
+		foreach ($inputs_to_hide as $input_to_hide) {
1226
+			$input = $this->get_input($input_to_hide);
1227
+			$input->set_display_strategy(new EE_Hidden_Display_Strategy());
1228
+		}
1229
+	}
1230
+
1231
+
1232
+	/**
1233
+	 * add_subsections
1234
+	 * Adds the listed subsections to the form section.
1235
+	 * If $subsection_name_to_target is provided,
1236
+	 * then new subsections are added before or after that subsection,
1237
+	 * otherwise to the start or end of the entire subsections array.
1238
+	 *
1239
+	 * @param EE_Form_Section_Base[] $new_subsections           array of new form subsections
1240
+	 *                                                          where keys are their names
1241
+	 * @param string                 $subsection_name_to_target an existing for section that $new_subsections
1242
+	 *                                                          should be added before or after
1243
+	 *                                                          IF $subsection_name_to_target is null,
1244
+	 *                                                          then $new_subsections will be added to
1245
+	 *                                                          the beginning or end of the entire subsections array
1246
+	 * @param boolean                $add_before                whether to add $new_subsections, before or after
1247
+	 *                                                          $subsection_name_to_target,
1248
+	 *                                                          or if $subsection_name_to_target is null,
1249
+	 *                                                          before or after entire subsections array
1250
+	 * @return void
1251
+	 * @throws EE_Error
1252
+	 */
1253
+	public function add_subsections($new_subsections, $subsection_name_to_target = null, $add_before = true)
1254
+	{
1255
+		foreach ($new_subsections as $subsection_name => $subsection) {
1256
+			if (! $subsection instanceof EE_Form_Section_Base) {
1257
+				EE_Error::add_error(
1258
+					sprintf(
1259
+						esc_html__(
1260
+							"Trying to add a %s as a subsection (it was named '%s') to the form section '%s'. It was removed.",
1261
+							'event_espresso'
1262
+						),
1263
+						get_class($subsection),
1264
+						$subsection_name,
1265
+						$this->name()
1266
+					)
1267
+				);
1268
+				unset($new_subsections[ $subsection_name ]);
1269
+			}
1270
+		}
1271
+		$this->_subsections = EEH_Array::insert_into_array(
1272
+			$this->_subsections,
1273
+			$new_subsections,
1274
+			$subsection_name_to_target,
1275
+			$add_before
1276
+		);
1277
+		if ($this->_construction_finalized) {
1278
+			foreach ($this->_subsections as $name => $subsection) {
1279
+				$subsection->_construct_finalize($this, $name);
1280
+			}
1281
+		}
1282
+	}
1283
+
1284
+
1285
+	/**
1286
+	 * @param string $subsection_name
1287
+	 * @param bool   $recursive
1288
+	 * @return bool
1289
+	 */
1290
+	public function has_subsection($subsection_name, $recursive = false)
1291
+	{
1292
+		foreach ($this->_subsections as $name => $subsection) {
1293
+			if (
1294
+				$name === $subsection_name
1295
+				|| (
1296
+					$recursive
1297
+					&& $subsection instanceof EE_Form_Section_Proper
1298
+					&& $subsection->has_subsection($subsection_name, $recursive)
1299
+				)
1300
+			) {
1301
+				return true;
1302
+			}
1303
+		}
1304
+		return false;
1305
+	}
1306
+
1307
+
1308
+
1309
+	/**
1310
+	 * Just gets all validatable subsections to clean their sensitive data
1311
+	 *
1312
+	 * @throws EE_Error
1313
+	 */
1314
+	public function clean_sensitive_data()
1315
+	{
1316
+		foreach ($this->get_validatable_subsections() as $subsection) {
1317
+			$subsection->clean_sensitive_data();
1318
+		}
1319
+	}
1320
+
1321
+
1322
+	/**
1323
+	 * Sets the submission error message (aka validation error message for this form section and all sub-sections)
1324
+	 * @param string                           $form_submission_error_message
1325
+	 * @param EE_Form_Section_Validatable $form_section unused
1326
+	 * @throws EE_Error
1327
+	 */
1328
+	public function set_submission_error_message(
1329
+		$form_submission_error_message = ''
1330
+	) {
1331
+		$this->_form_submission_error_message = ! empty($form_submission_error_message)
1332
+			? $form_submission_error_message
1333
+			: $this->getAllValidationErrorsString();
1334
+	}
1335
+
1336
+
1337
+	/**
1338
+	 * Returns the cached error message. A default value is set for this during _validate(),
1339
+	 * (called during receive_form_submission) but it can be explicitly set using
1340
+	 * set_submission_error_message
1341
+	 *
1342
+	 * @return string
1343
+	 */
1344
+	public function submission_error_message()
1345
+	{
1346
+		return $this->_form_submission_error_message;
1347
+	}
1348
+
1349
+
1350
+	/**
1351
+	 * Sets a message to display if the data submitted to the form was valid.
1352
+	 * @param string $form_submission_success_message
1353
+	 */
1354
+	public function set_submission_success_message($form_submission_success_message = '')
1355
+	{
1356
+		$this->_form_submission_success_message = ! empty($form_submission_success_message)
1357
+			? $form_submission_success_message
1358
+			: esc_html__('Form submitted successfully', 'event_espresso');
1359
+	}
1360
+
1361
+
1362
+	/**
1363
+	 * Gets a message appropriate for display when the form is correctly submitted
1364
+	 * @return string
1365
+	 */
1366
+	public function submission_success_message()
1367
+	{
1368
+		return $this->_form_submission_success_message;
1369
+	}
1370
+
1371
+
1372
+	/**
1373
+	 * Returns the prefix that should be used on child of this form section for
1374
+	 * their html names. If this form section itself has a parent, prepends ITS
1375
+	 * prefix onto this form section's prefix. Used primarily by
1376
+	 * EE_Form_Input_Base::_set_default_html_name_if_empty
1377
+	 *
1378
+	 * @return string
1379
+	 * @throws EE_Error
1380
+	 */
1381
+	public function html_name_prefix()
1382
+	{
1383
+		if ($this->parent_section() instanceof EE_Form_Section_Proper) {
1384
+			return $this->parent_section()->html_name_prefix() . '[' . $this->name() . ']';
1385
+		}
1386
+		return $this->name();
1387
+	}
1388
+
1389
+
1390
+	/**
1391
+	 * Gets the name, but first checks _construct_finalize has been called. If not,
1392
+	 * calls it (assumes there is no parent and that we want the name to be whatever
1393
+	 * was set, which is probably nothing, or the classname)
1394
+	 *
1395
+	 * @return string
1396
+	 * @throws EE_Error
1397
+	 */
1398
+	public function name()
1399
+	{
1400
+		$this->ensure_construct_finalized_called();
1401
+		return parent::name();
1402
+	}
1403
+
1404
+
1405
+	/**
1406
+	 * @return EE_Form_Section_Proper
1407
+	 * @throws EE_Error
1408
+	 */
1409
+	public function parent_section()
1410
+	{
1411
+		$this->ensure_construct_finalized_called();
1412
+		return parent::parent_section();
1413
+	}
1414
+
1415
+
1416
+	/**
1417
+	 * make sure construction finalized was called, otherwise children might not be ready
1418
+	 *
1419
+	 * @return void
1420
+	 * @throws EE_Error
1421
+	 */
1422
+	public function ensure_construct_finalized_called()
1423
+	{
1424
+		if (! $this->_construction_finalized) {
1425
+			$this->_construct_finalize($this->_parent_section, $this->_name);
1426
+		}
1427
+	}
1428
+
1429
+
1430
+	/**
1431
+	 * Checks if any of this form section's inputs, or any of its children's inputs,
1432
+	 * are in teh form data. If any are found, returns true. Else false
1433
+	 *
1434
+	 * @param array $req_data
1435
+	 * @return boolean
1436
+	 * @throws EE_Error
1437
+	 */
1438
+	public function form_data_present_in($req_data = null)
1439
+	{
1440
+		$req_data = $this->getCachedRequest($req_data);
1441
+		foreach ($this->subsections() as $subsection) {
1442
+			if ($subsection instanceof EE_Form_Input_Base) {
1443
+				if ($subsection->form_data_present_in($req_data)) {
1444
+					return true;
1445
+				}
1446
+			} elseif ($subsection instanceof EE_Form_Section_Proper) {
1447
+				if ($subsection->form_data_present_in($req_data)) {
1448
+					return true;
1449
+				}
1450
+			}
1451
+		}
1452
+		return false;
1453
+	}
1454
+
1455
+
1456
+	/**
1457
+	 * Gets validation errors for this form section and subsections
1458
+	 * Similar to EE_Form_Section_Validatable::get_validation_errors() except this
1459
+	 * gets the validation errors for ALL subsection
1460
+	 *
1461
+	 * @return EE_Validation_Error[]
1462
+	 * @throws EE_Error
1463
+	 */
1464
+	public function get_validation_errors_accumulated()
1465
+	{
1466
+		$validation_errors = $this->get_validation_errors();
1467
+		foreach ($this->get_validatable_subsections() as $subsection) {
1468
+			if ($subsection instanceof EE_Form_Section_Proper) {
1469
+				$validation_errors_on_this_subsection = $subsection->get_validation_errors_accumulated();
1470
+			} else {
1471
+				$validation_errors_on_this_subsection = $subsection->get_validation_errors();
1472
+			}
1473
+			if ($validation_errors_on_this_subsection) {
1474
+				$validation_errors = array_merge($validation_errors, $validation_errors_on_this_subsection);
1475
+			}
1476
+		}
1477
+		return $validation_errors;
1478
+	}
1479
+
1480
+	/**
1481
+	 * Fetch validation errors from children and grandchildren and puts them in a single string.
1482
+	 * This traverses the form section tree to generate this, but you probably want to instead use
1483
+	 * get_form_submission_error_message() which is usually this message cached (or a custom validation error message)
1484
+	 *
1485
+	 * @return string
1486
+	 * @since 4.9.59.p
1487
+	 */
1488
+	protected function getAllValidationErrorsString()
1489
+	{
1490
+		$submission_error_messages = array();
1491
+		// bad, bad, bad registrant
1492
+		foreach ($this->get_validation_errors_accumulated() as $validation_error) {
1493
+			if ($validation_error instanceof EE_Validation_Error) {
1494
+				$form_section = $validation_error->get_form_section();
1495
+				if ($form_section instanceof EE_Form_Input_Base) {
1496
+					$label = $validation_error->get_form_section()->html_label_text();
1497
+				} elseif ($form_section instanceof EE_Form_Section_Validatable) {
1498
+					$label = $validation_error->get_form_section()->name();
1499
+				} else {
1500
+					$label = esc_html__('Unknown', 'event_espresso');
1501
+				}
1502
+				$submission_error_messages[] = sprintf(
1503
+					esc_html__('%s : %s', 'event_espresso'),
1504
+					$label,
1505
+					$validation_error->getMessage()
1506
+				);
1507
+			}
1508
+		}
1509
+		return implode('<br>', $submission_error_messages);
1510
+	}
1511
+
1512
+
1513
+	/**
1514
+	 * This isn't just the name of an input, it's a path pointing to an input. The
1515
+	 * path is similar to a folder path: slash (/) means to descend into a subsection,
1516
+	 * dot-dot-slash (../) means to ascend into the parent section.
1517
+	 * After a series of slashes and dot-dot-slashes, there should be the name of an input,
1518
+	 * which will be returned.
1519
+	 * Eg, if you want the related input to be conditional on a sibling input name 'foobar'
1520
+	 * just use 'foobar'. If you want it to be conditional on an aunt/uncle input name
1521
+	 * 'baz', use '../baz'. If you want it to be conditional on a cousin input,
1522
+	 * the child of 'baz_section' named 'baz_child', use '../baz_section/baz_child'.
1523
+	 * Etc
1524
+	 *
1525
+	 * @param string|false $form_section_path we accept false also because substr( '../', '../' ) = false
1526
+	 * @return EE_Form_Section_Base
1527
+	 * @throws EE_Error
1528
+	 */
1529
+	public function find_section_from_path($form_section_path)
1530
+	{
1531
+		// check if we can find the input from purely going straight up the tree
1532
+		$input = parent::find_section_from_path($form_section_path);
1533
+		if ($input instanceof EE_Form_Section_Base) {
1534
+			return $input;
1535
+		}
1536
+		$next_slash_pos = strpos($form_section_path, '/');
1537
+		if ($next_slash_pos !== false) {
1538
+			$child_section_name = substr($form_section_path, 0, $next_slash_pos);
1539
+			$subpath            = substr($form_section_path, $next_slash_pos + 1);
1540
+		} else {
1541
+			$child_section_name = $form_section_path;
1542
+			$subpath            = '';
1543
+		}
1544
+		$child_section = $this->get_subsection($child_section_name);
1545
+		if ($child_section instanceof EE_Form_Section_Base) {
1546
+			return $child_section->find_section_from_path($subpath);
1547
+		}
1548
+		return null;
1549
+	}
1550 1550
 }
Please login to merge, or discard this patch.
core/libraries/messages/messenger/EE_Html_messenger.class.php 1 patch
Indentation   +546 added lines, -546 removed lines patch added patch discarded remove patch
@@ -12,550 +12,550 @@
 block discarded – undo
12 12
  */
13 13
 class EE_Html_messenger extends EE_messenger
14 14
 {
15
-    /**
16
-     * The following are the properties that this messenger requires for displaying the html
17
-     */
18
-    /**
19
-     * This is the html body generated by the template via the message type.
20
-     *
21
-     * @var string
22
-     */
23
-    protected $_content;
24
-
25
-
26
-    /**
27
-     * This is for the page title that gets displayed.  (Why use "subject"?  Because the "title" tag in html is
28
-     * equivalent to the "subject" of the page.
29
-     *
30
-     * @var string
31
-     */
32
-    protected $_subject;
33
-
34
-
35
-    /**
36
-     * EE_Html_messenger constructor.
37
-     */
38
-    public function __construct()
39
-    {
40
-        // set properties
41
-        $this->name = 'html';
42
-        $this->description = esc_html__('This messenger outputs a message to a browser for display.', 'event_espresso');
43
-        $this->label = array(
44
-            'singular' => esc_html__('html', 'event_espresso'),
45
-            'plural' => esc_html__('html', 'event_espresso'),
46
-        );
47
-        $this->activate_on_install = true;
48
-        // add the "powered by EE" credit link to the HTML receipt and invoice
49
-        add_filter(
50
-            'FHEE__EE_Html_messenger___send_message__main_body',
51
-            array($this, 'add_powered_by_credit_link_to_receipt_and_invoice'),
52
-            10,
53
-            3
54
-        );
55
-        parent::__construct();
56
-    }
57
-
58
-
59
-    /**
60
-     * HTML Messenger desires execution immediately.
61
-     *
62
-     * @see    parent::send_now() for documentation.
63
-     * @since  4.9.0
64
-     * @return bool
65
-     */
66
-    public function send_now()
67
-    {
68
-        return true;
69
-    }
70
-
71
-
72
-    /**
73
-     * HTML Messenger allows an empty to field.
74
-     *
75
-     * @see    parent::allow_empty_to_field() for documentation
76
-     * @since  4.9.0
77
-     * @return bool
78
-     */
79
-    public function allow_empty_to_field()
80
-    {
81
-        return true;
82
-    }
83
-
84
-
85
-    /**
86
-     * @see abstract declaration in EE_messenger for details.
87
-     */
88
-    protected function _set_admin_pages()
89
-    {
90
-        $this->admin_registered_pages = array('events_edit' => true);
91
-    }
92
-
93
-
94
-    /**
95
-     * @see abstract declaration in EE_messenger for details.
96
-     */
97
-    protected function _set_valid_shortcodes()
98
-    {
99
-        $this->_valid_shortcodes = array();
100
-    }
101
-
102
-
103
-    /**
104
-     * @see abstract declaration in EE_messenger for details.
105
-     */
106
-    protected function _set_validator_config()
107
-    {
108
-        $this->_validator_config = array(
109
-            'subject' => array(
110
-                'shortcodes' => array('organization', 'primary_registration_details', 'email', 'transaction'),
111
-            ),
112
-            'content' => array(
113
-                'shortcodes' => array(
114
-                    'organization',
115
-                    'primary_registration_list',
116
-                    'primary_registration_details',
117
-                    'email',
118
-                    'transaction',
119
-                    'event_list',
120
-                    'payment_list',
121
-                    'venue',
122
-                    'line_item_list',
123
-                    'messenger',
124
-                    'ticket_list',
125
-                ),
126
-            ),
127
-            'event_list' => array(
128
-                'shortcodes' => array(
129
-                    'event',
130
-                    'ticket_list',
131
-                    'venue',
132
-                    'primary_registration_details',
133
-                    'primary_registration_list',
134
-                    'event_author',
135
-                ),
136
-                'required' => array('[EVENT_LIST]'),
137
-            ),
138
-            'ticket_list' => array(
139
-                'shortcodes' => array(
140
-                    'attendee_list',
141
-                    'ticket',
142
-                    'datetime_list',
143
-                    'primary_registration_details',
144
-                    'line_item_list',
145
-                    'venue',
146
-                ),
147
-                'required' => array('[TICKET_LIST]'),
148
-            ),
149
-            'ticket_line_item_no_pms' => array(
150
-                'shortcodes' => array('line_item', 'ticket'),
151
-                'required' => array('[TICKET_LINE_ITEM_LIST]'),
152
-            ),
153
-            'ticket_line_item_pms' => array(
154
-                'shortcodes' => array('line_item', 'ticket', 'line_item_list'),
155
-                'required' => array('[TICKET_LINE_ITEM_LIST]'),
156
-            ),
157
-            'price_modifier_line_item_list' => array(
158
-                'shortcodes' => array('line_item'),
159
-                'required' => array('[PRICE_MODIFIER_LINE_ITEM_LIST]'),
160
-            ),
161
-            'datetime_list' => array(
162
-                'shortcodes' => array('datetime'),
163
-                'required' => array('[DATETIME_LIST]'),
164
-            ),
165
-            'attendee_list' => array(
166
-                'shortcodes' => array('attendee'),
167
-                'required' => array('[ATTENDEE_LIST]'),
168
-            ),
169
-            'tax_line_item_list' => array(
170
-                'shortcodes' => array('line_item'),
171
-                'required' => array('[TAX_LINE_ITEM_LIST]'),
172
-            ),
173
-            'additional_line_item_list' => array(
174
-                'shortcodes' => array('line_item'),
175
-                'required' => array('[ADDITIONAL_LINE_ITEM_LIST]'),
176
-            ),
177
-            'payment_list' => array(
178
-                'shortcodes' => array('payment'),
179
-                'required' => array('[PAYMENT_LIST_*]'),
180
-            ),
181
-        );
182
-    }
183
-
184
-
185
-    /**
186
-     * This is a method called from EE_messages when this messenger is a generating messenger and the sending messenger
187
-     * is a different messenger.  Child messengers can set hooks for the sending messenger to callback on if necessary
188
-     * (i.e. swap out css files or something else).
189
-     *
190
-     * @since 4.5.0
191
-     * @param string $sending_messenger_name the name of the sending messenger so we only set the hooks needed.
192
-     * @return void
193
-     */
194
-    public function do_secondary_messenger_hooks($sending_messenger_name)
195
-    {
196
-        if ($sending_messenger_name === 'pdf') {
197
-            add_filter('EE_messenger__get_variation__variation', array($this, 'add_html_css'), 10, 8);
198
-        }
199
-    }
200
-
201
-
202
-    /**
203
-     * @param                            $variation_path
204
-     * @param \EE_Messages_Template_Pack $template_pack
205
-     * @param                            $messenger_name
206
-     * @param                            $message_type_name
207
-     * @param                            $url
208
-     * @param                            $type
209
-     * @param                            $variation
210
-     * @param                            $skip_filters
211
-     * @return string
212
-     */
213
-    public function add_html_css(
214
-        $variation_path,
215
-        EE_Messages_Template_Pack $template_pack,
216
-        $messenger_name,
217
-        $message_type_name,
218
-        $url,
219
-        $type,
220
-        $variation,
221
-        $skip_filters
222
-    ) {
223
-        $variation = $template_pack->get_variation(
224
-            $this->name,
225
-            $message_type_name,
226
-            $type,
227
-            $variation,
228
-            $url,
229
-            '.css',
230
-            $skip_filters
231
-        );
232
-        return $variation;
233
-    }
234
-
235
-
236
-    /**
237
-     * Takes care of enqueuing any necessary scripts or styles for the page.  A do_action() so message types using this
238
-     * messenger can add their own js.
239
-     *
240
-     * @return void.
241
-     */
242
-    public function enqueue_scripts_styles()
243
-    {
244
-        parent::enqueue_scripts_styles();
245
-        do_action('AHEE__EE_Html_messenger__enqueue_scripts_styles');
246
-    }
247
-
248
-
249
-    /**
250
-     * _set_template_fields
251
-     * This sets up the fields that a messenger requires for the message to go out.
252
-     *
253
-     * @access  protected
254
-     * @return void
255
-     */
256
-    protected function _set_template_fields()
257
-    {
258
-        // any extra template fields that are NOT used by the messenger
259
-        // but will get used by a messenger field for shortcode replacement
260
-        // get added to the 'extra' key in an associated array
261
-        // indexed by the messenger field they relate to.
262
-        // This is important for the Messages_admin to know what fields to display to the user.
263
-        // Also, notice that the "values" are equal to the field type
264
-        // that messages admin will use to know what kind of field to display.
265
-        // The values ALSO have one index labeled "shortcode".
266
-        // The values in that array indicate which ACTUAL SHORTCODE (i.e. [SHORTCODE])
267
-        // is required in order for this extra field to be displayed.
268
-        //  If the required shortcode isn't part of the shortcodes array
269
-        // then the field is not needed and will not be displayed/parsed.
270
-        $this->_template_fields = array(
271
-            'subject' => array(
272
-                'input' => 'text',
273
-                'label' => esc_html__('Page Title', 'event_espresso'),
274
-                'type' => 'string',
275
-                'required' => true,
276
-                'validation' => true,
277
-                'css_class' => 'large-text',
278
-                'format' => '%s',
279
-            ),
280
-            'content' => '',
281
-            // left empty b/c it is in the "extra array" but messenger still needs needs to know this is a field.
282
-            'extra' => array(
283
-                'content' => array(
284
-                    'main' => array(
285
-                        'input' => 'wp_editor',
286
-                        'label' => esc_html__('Main Content', 'event_espresso'),
287
-                        'type' => 'string',
288
-                        'required' => true,
289
-                        'validation' => true,
290
-                        'format' => '%s',
291
-                        'rows' => '15',
292
-                    ),
293
-                    'event_list' => array(
294
-                        'input' => 'wp_editor',
295
-                        'label' => '[EVENT_LIST]',
296
-                        'type' => 'string',
297
-                        'required' => true,
298
-                        'validation' => true,
299
-                        'format' => '%s',
300
-                        'rows' => '15',
301
-                        'shortcodes_required' => array('[EVENT_LIST]'),
302
-                    ),
303
-                    'ticket_list' => array(
304
-                        'input' => 'textarea',
305
-                        'label' => '[TICKET_LIST]',
306
-                        'type' => 'string',
307
-                        'required' => true,
308
-                        'validation' => true,
309
-                        'format' => '%s',
310
-                        'css_class' => 'large-text',
311
-                        'rows' => '10',
312
-                        'shortcodes_required' => array('[TICKET_LIST]'),
313
-                    ),
314
-                    'ticket_line_item_no_pms' => array(
315
-                        'input' => 'textarea',
316
-                        'label' => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
317
-                            'Ticket Line Item List with no Price Modifiers',
318
-                            'event_espresso'
319
-                        ),
320
-                        'type' => 'string',
321
-                        'required' => false,
322
-                        'validation' => true,
323
-                        'format' => '%s',
324
-                        'css_class' => 'large-text',
325
-                        'rows' => '5',
326
-                        'shortcodes_required' => array('[TICKET_LINE_ITEM_LIST]'),
327
-                    ),
328
-                    'ticket_line_item_pms' => array(
329
-                        'input' => 'textarea',
330
-                        'label' => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
331
-                            'Ticket Line Item List with Price Modifiers',
332
-                            'event_espresso'
333
-                        ),
334
-                        'type' => 'string',
335
-                        'required' => false,
336
-                        'validation' => true,
337
-                        'format' => '%s',
338
-                        'css_class' => 'large-text',
339
-                        'rows' => '5',
340
-                        'shortcodes_required' => array('[TICKET_LINE_ITEM_LIST]'),
341
-                    ),
342
-                    'price_modifier_line_item_list' => array(
343
-                        'input' => 'textarea',
344
-                        'label' => '[PRICE_MODIFIER_LINE_ITEM_LIST]',
345
-                        'type' => 'string',
346
-                        'required' => false,
347
-                        'validation' => true,
348
-                        'format' => '%s',
349
-                        'css_class' => 'large-text',
350
-                        'rows' => '5',
351
-                        'shortcodes_required' => array('[PRICE_MODIFIER_LINE_ITEM_LIST]'),
352
-                    ),
353
-                    'datetime_list' => array(
354
-                        'input' => 'textarea',
355
-                        'label' => '[DATETIME_LIST]',
356
-                        'type' => 'string',
357
-                        'required' => true,
358
-                        'validation' => true,
359
-                        'format' => '%s',
360
-                        'css_class' => 'large-text',
361
-                        'rows' => '5',
362
-                        'shortcodes_required' => array('[DATETIME_LIST]'),
363
-                    ),
364
-                    'attendee_list' => array(
365
-                        'input' => 'textarea',
366
-                        'label' => '[ATTENDEE_LIST]',
367
-                        'type' => 'string',
368
-                        'required' => true,
369
-                        'validation' => true,
370
-                        'format' => '%s',
371
-                        'css_class' => 'large-text',
372
-                        'rows' => '5',
373
-                        'shortcodes_required' => array('[ATTENDEE_LIST]'),
374
-                    ),
375
-                    'tax_line_item_list' => array(
376
-                        'input' => 'textarea',
377
-                        'label' => '[TAX_LINE_ITEM_LIST]',
378
-                        'type' => 'string',
379
-                        'required' => false,
380
-                        'validation' => true,
381
-                        'format' => '%s',
382
-                        'css_class' => 'large-text',
383
-                        'rows' => '5',
384
-                        'shortcodes_required' => array('[TAX_LINE_ITEM_LIST]'),
385
-                    ),
386
-                    'additional_line_item_list' => array(
387
-                        'input' => 'textarea',
388
-                        'label' => '[ADDITIONAL_LINE_ITEM_LIST]',
389
-                        'type' => 'string',
390
-                        'required' => false,
391
-                        'validation' => true,
392
-                        'format' => '%s',
393
-                        'css_class' => 'large-text',
394
-                        'rows' => '5',
395
-                        'shortcodes_required' => array('[ADDITIONAL_LINE_ITEM_LIST]'),
396
-                    ),
397
-                    'payment_list' => array(
398
-                        'input' => 'textarea',
399
-                        'label' => '[PAYMENT_LIST]',
400
-                        'type' => 'string',
401
-                        'required' => true,
402
-                        'validation' => true,
403
-                        'format' => '%s',
404
-                        'css_class' => 'large-text',
405
-                        'rows' => '5',
406
-                        'shortcodes_required' => array('[PAYMENT_LIST_*]'),
407
-                    ),
408
-                ),
409
-            ),
410
-        );
411
-    }
412
-
413
-
414
-    /**
415
-     * @see   definition of this method in parent
416
-     * @since 4.5.0
417
-     */
418
-    protected function _set_default_message_types()
419
-    {
420
-        $this->_default_message_types = array('receipt', 'invoice');
421
-    }
422
-
423
-
424
-    /**
425
-     * @see   definition of this method in parent
426
-     * @since 4.5.0
427
-     */
428
-    protected function _set_valid_message_types()
429
-    {
430
-        $this->_valid_message_types = array('receipt', 'invoice');
431
-    }
432
-
433
-
434
-    /**
435
-     * Displays the message in the browser.
436
-     *
437
-     * @since 4.5.0
438
-     * @return string.
439
-     */
440
-    protected function _send_message()
441
-    {
442
-        $this->_template_args = array(
443
-            'page_title' => $this->_subject,
444
-            'base_css' => $this->get_variation(
445
-                $this->_tmp_pack,
446
-                $this->_incoming_message_type->name,
447
-                true,
448
-                'base',
449
-                $this->_variation
450
-            ),
451
-            'print_css' => $this->get_variation(
452
-                $this->_tmp_pack,
453
-                $this->_incoming_message_type->name,
454
-                true,
455
-                'print',
456
-                $this->_variation
457
-            ),
458
-            'main_css' => $this->get_variation(
459
-                $this->_tmp_pack,
460
-                $this->_incoming_message_type->name,
461
-                true,
462
-                'main',
463
-                $this->_variation
464
-            ),
465
-            'main_body' => wpautop(
466
-                apply_filters(
467
-                    'FHEE__EE_Html_messenger___send_message__main_body',
468
-                    $this->_content,
469
-                    $this->_content,
470
-                    $this->_incoming_message_type
471
-                )
472
-            )
473
-        );
474
-        $this->_deregister_wp_hooks();
475
-        add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts_styles'));
476
-
477
-        echo $this->_get_main_template();
478
-        exit();
479
-    }
480
-
481
-
482
-    /**
483
-     * The purpose of this function is to de register all actions hooked into wp_head and wp_footer so that it doesn't
484
-     * interfere with our templates.  If users want to add any custom styles or scripts they must use the
485
-     * AHEE__EE_Html_messenger__enqueue_scripts_styles hook.
486
-     *
487
-     * @since 4.5.0
488
-     * @return void
489
-     */
490
-    protected function _deregister_wp_hooks()
491
-    {
492
-        remove_all_actions('wp_head');
493
-        remove_all_actions('wp_footer');
494
-        remove_all_actions('wp_print_footer_scripts');
495
-        remove_all_actions('wp_enqueue_scripts');
496
-        global $wp_scripts, $wp_styles;
497
-        $wp_scripts = $wp_styles = array();
498
-        // just add back in wp_enqueue_scripts and wp_print_footer_scripts cause that's all we want to load.
499
-        add_action('wp_footer', 'wp_print_footer_scripts');
500
-        add_action('wp_print_footer_scripts', '_wp_footer_scripts');
501
-        add_action('wp_head', 'wp_enqueue_scripts');
502
-    }
503
-
504
-
505
-    /**
506
-     * Overwrite parent _get_main_template for display_html purposes.
507
-     *
508
-     * @since  4.5.0
509
-     * @param bool $preview
510
-     * @return string
511
-     */
512
-    protected function _get_main_template($preview = false)
513
-    {
514
-        $wrapper_template = $this->_tmp_pack->get_wrapper($this->name, 'main');
515
-        // include message type as a template arg
516
-        $this->_template_args['message_type'] = $this->_incoming_message_type;
517
-        return EEH_Template::display_template($wrapper_template, $this->_template_args, true);
518
-    }
519
-
520
-
521
-    /**
522
-     * @return string
523
-     */
524
-    protected function _preview()
525
-    {
526
-        return $this->_send_message();
527
-    }
528
-
529
-
530
-    protected function _set_admin_settings_fields()
531
-    {
532
-    }
533
-
534
-
535
-    /**
536
-     * add the "powered by EE" credit link to the HTML receipt and invoice
537
-     *
538
-     * @param string $content
539
-     * @param string $content_again
540
-     * @param \EE_message_type $incoming_message_type
541
-     * @return string
542
-     */
543
-    public function add_powered_by_credit_link_to_receipt_and_invoice(
544
-        $content = '',
545
-        $content_again = '',
546
-        EE_message_type $incoming_message_type
547
-    ) {
548
-        if (
549
-            ($incoming_message_type->name === 'invoice' || $incoming_message_type->name === 'receipt')
550
-            && apply_filters('FHEE_EE_Html_messenger__add_powered_by_credit_link_to_receipt_and_invoice', true)
551
-        ) {
552
-            $content .= \EEH_Template::powered_by_event_espresso(
553
-                'aln-cntr',
554
-                '',
555
-                array('utm_content' => 'messages_system')
556
-            )
557
-                . EEH_HTML::div(EEH_HTML::p('&nbsp;'));
558
-        }
559
-        return $content;
560
-    }
15
+	/**
16
+	 * The following are the properties that this messenger requires for displaying the html
17
+	 */
18
+	/**
19
+	 * This is the html body generated by the template via the message type.
20
+	 *
21
+	 * @var string
22
+	 */
23
+	protected $_content;
24
+
25
+
26
+	/**
27
+	 * This is for the page title that gets displayed.  (Why use "subject"?  Because the "title" tag in html is
28
+	 * equivalent to the "subject" of the page.
29
+	 *
30
+	 * @var string
31
+	 */
32
+	protected $_subject;
33
+
34
+
35
+	/**
36
+	 * EE_Html_messenger constructor.
37
+	 */
38
+	public function __construct()
39
+	{
40
+		// set properties
41
+		$this->name = 'html';
42
+		$this->description = esc_html__('This messenger outputs a message to a browser for display.', 'event_espresso');
43
+		$this->label = array(
44
+			'singular' => esc_html__('html', 'event_espresso'),
45
+			'plural' => esc_html__('html', 'event_espresso'),
46
+		);
47
+		$this->activate_on_install = true;
48
+		// add the "powered by EE" credit link to the HTML receipt and invoice
49
+		add_filter(
50
+			'FHEE__EE_Html_messenger___send_message__main_body',
51
+			array($this, 'add_powered_by_credit_link_to_receipt_and_invoice'),
52
+			10,
53
+			3
54
+		);
55
+		parent::__construct();
56
+	}
57
+
58
+
59
+	/**
60
+	 * HTML Messenger desires execution immediately.
61
+	 *
62
+	 * @see    parent::send_now() for documentation.
63
+	 * @since  4.9.0
64
+	 * @return bool
65
+	 */
66
+	public function send_now()
67
+	{
68
+		return true;
69
+	}
70
+
71
+
72
+	/**
73
+	 * HTML Messenger allows an empty to field.
74
+	 *
75
+	 * @see    parent::allow_empty_to_field() for documentation
76
+	 * @since  4.9.0
77
+	 * @return bool
78
+	 */
79
+	public function allow_empty_to_field()
80
+	{
81
+		return true;
82
+	}
83
+
84
+
85
+	/**
86
+	 * @see abstract declaration in EE_messenger for details.
87
+	 */
88
+	protected function _set_admin_pages()
89
+	{
90
+		$this->admin_registered_pages = array('events_edit' => true);
91
+	}
92
+
93
+
94
+	/**
95
+	 * @see abstract declaration in EE_messenger for details.
96
+	 */
97
+	protected function _set_valid_shortcodes()
98
+	{
99
+		$this->_valid_shortcodes = array();
100
+	}
101
+
102
+
103
+	/**
104
+	 * @see abstract declaration in EE_messenger for details.
105
+	 */
106
+	protected function _set_validator_config()
107
+	{
108
+		$this->_validator_config = array(
109
+			'subject' => array(
110
+				'shortcodes' => array('organization', 'primary_registration_details', 'email', 'transaction'),
111
+			),
112
+			'content' => array(
113
+				'shortcodes' => array(
114
+					'organization',
115
+					'primary_registration_list',
116
+					'primary_registration_details',
117
+					'email',
118
+					'transaction',
119
+					'event_list',
120
+					'payment_list',
121
+					'venue',
122
+					'line_item_list',
123
+					'messenger',
124
+					'ticket_list',
125
+				),
126
+			),
127
+			'event_list' => array(
128
+				'shortcodes' => array(
129
+					'event',
130
+					'ticket_list',
131
+					'venue',
132
+					'primary_registration_details',
133
+					'primary_registration_list',
134
+					'event_author',
135
+				),
136
+				'required' => array('[EVENT_LIST]'),
137
+			),
138
+			'ticket_list' => array(
139
+				'shortcodes' => array(
140
+					'attendee_list',
141
+					'ticket',
142
+					'datetime_list',
143
+					'primary_registration_details',
144
+					'line_item_list',
145
+					'venue',
146
+				),
147
+				'required' => array('[TICKET_LIST]'),
148
+			),
149
+			'ticket_line_item_no_pms' => array(
150
+				'shortcodes' => array('line_item', 'ticket'),
151
+				'required' => array('[TICKET_LINE_ITEM_LIST]'),
152
+			),
153
+			'ticket_line_item_pms' => array(
154
+				'shortcodes' => array('line_item', 'ticket', 'line_item_list'),
155
+				'required' => array('[TICKET_LINE_ITEM_LIST]'),
156
+			),
157
+			'price_modifier_line_item_list' => array(
158
+				'shortcodes' => array('line_item'),
159
+				'required' => array('[PRICE_MODIFIER_LINE_ITEM_LIST]'),
160
+			),
161
+			'datetime_list' => array(
162
+				'shortcodes' => array('datetime'),
163
+				'required' => array('[DATETIME_LIST]'),
164
+			),
165
+			'attendee_list' => array(
166
+				'shortcodes' => array('attendee'),
167
+				'required' => array('[ATTENDEE_LIST]'),
168
+			),
169
+			'tax_line_item_list' => array(
170
+				'shortcodes' => array('line_item'),
171
+				'required' => array('[TAX_LINE_ITEM_LIST]'),
172
+			),
173
+			'additional_line_item_list' => array(
174
+				'shortcodes' => array('line_item'),
175
+				'required' => array('[ADDITIONAL_LINE_ITEM_LIST]'),
176
+			),
177
+			'payment_list' => array(
178
+				'shortcodes' => array('payment'),
179
+				'required' => array('[PAYMENT_LIST_*]'),
180
+			),
181
+		);
182
+	}
183
+
184
+
185
+	/**
186
+	 * This is a method called from EE_messages when this messenger is a generating messenger and the sending messenger
187
+	 * is a different messenger.  Child messengers can set hooks for the sending messenger to callback on if necessary
188
+	 * (i.e. swap out css files or something else).
189
+	 *
190
+	 * @since 4.5.0
191
+	 * @param string $sending_messenger_name the name of the sending messenger so we only set the hooks needed.
192
+	 * @return void
193
+	 */
194
+	public function do_secondary_messenger_hooks($sending_messenger_name)
195
+	{
196
+		if ($sending_messenger_name === 'pdf') {
197
+			add_filter('EE_messenger__get_variation__variation', array($this, 'add_html_css'), 10, 8);
198
+		}
199
+	}
200
+
201
+
202
+	/**
203
+	 * @param                            $variation_path
204
+	 * @param \EE_Messages_Template_Pack $template_pack
205
+	 * @param                            $messenger_name
206
+	 * @param                            $message_type_name
207
+	 * @param                            $url
208
+	 * @param                            $type
209
+	 * @param                            $variation
210
+	 * @param                            $skip_filters
211
+	 * @return string
212
+	 */
213
+	public function add_html_css(
214
+		$variation_path,
215
+		EE_Messages_Template_Pack $template_pack,
216
+		$messenger_name,
217
+		$message_type_name,
218
+		$url,
219
+		$type,
220
+		$variation,
221
+		$skip_filters
222
+	) {
223
+		$variation = $template_pack->get_variation(
224
+			$this->name,
225
+			$message_type_name,
226
+			$type,
227
+			$variation,
228
+			$url,
229
+			'.css',
230
+			$skip_filters
231
+		);
232
+		return $variation;
233
+	}
234
+
235
+
236
+	/**
237
+	 * Takes care of enqueuing any necessary scripts or styles for the page.  A do_action() so message types using this
238
+	 * messenger can add their own js.
239
+	 *
240
+	 * @return void.
241
+	 */
242
+	public function enqueue_scripts_styles()
243
+	{
244
+		parent::enqueue_scripts_styles();
245
+		do_action('AHEE__EE_Html_messenger__enqueue_scripts_styles');
246
+	}
247
+
248
+
249
+	/**
250
+	 * _set_template_fields
251
+	 * This sets up the fields that a messenger requires for the message to go out.
252
+	 *
253
+	 * @access  protected
254
+	 * @return void
255
+	 */
256
+	protected function _set_template_fields()
257
+	{
258
+		// any extra template fields that are NOT used by the messenger
259
+		// but will get used by a messenger field for shortcode replacement
260
+		// get added to the 'extra' key in an associated array
261
+		// indexed by the messenger field they relate to.
262
+		// This is important for the Messages_admin to know what fields to display to the user.
263
+		// Also, notice that the "values" are equal to the field type
264
+		// that messages admin will use to know what kind of field to display.
265
+		// The values ALSO have one index labeled "shortcode".
266
+		// The values in that array indicate which ACTUAL SHORTCODE (i.e. [SHORTCODE])
267
+		// is required in order for this extra field to be displayed.
268
+		//  If the required shortcode isn't part of the shortcodes array
269
+		// then the field is not needed and will not be displayed/parsed.
270
+		$this->_template_fields = array(
271
+			'subject' => array(
272
+				'input' => 'text',
273
+				'label' => esc_html__('Page Title', 'event_espresso'),
274
+				'type' => 'string',
275
+				'required' => true,
276
+				'validation' => true,
277
+				'css_class' => 'large-text',
278
+				'format' => '%s',
279
+			),
280
+			'content' => '',
281
+			// left empty b/c it is in the "extra array" but messenger still needs needs to know this is a field.
282
+			'extra' => array(
283
+				'content' => array(
284
+					'main' => array(
285
+						'input' => 'wp_editor',
286
+						'label' => esc_html__('Main Content', 'event_espresso'),
287
+						'type' => 'string',
288
+						'required' => true,
289
+						'validation' => true,
290
+						'format' => '%s',
291
+						'rows' => '15',
292
+					),
293
+					'event_list' => array(
294
+						'input' => 'wp_editor',
295
+						'label' => '[EVENT_LIST]',
296
+						'type' => 'string',
297
+						'required' => true,
298
+						'validation' => true,
299
+						'format' => '%s',
300
+						'rows' => '15',
301
+						'shortcodes_required' => array('[EVENT_LIST]'),
302
+					),
303
+					'ticket_list' => array(
304
+						'input' => 'textarea',
305
+						'label' => '[TICKET_LIST]',
306
+						'type' => 'string',
307
+						'required' => true,
308
+						'validation' => true,
309
+						'format' => '%s',
310
+						'css_class' => 'large-text',
311
+						'rows' => '10',
312
+						'shortcodes_required' => array('[TICKET_LIST]'),
313
+					),
314
+					'ticket_line_item_no_pms' => array(
315
+						'input' => 'textarea',
316
+						'label' => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
317
+							'Ticket Line Item List with no Price Modifiers',
318
+							'event_espresso'
319
+						),
320
+						'type' => 'string',
321
+						'required' => false,
322
+						'validation' => true,
323
+						'format' => '%s',
324
+						'css_class' => 'large-text',
325
+						'rows' => '5',
326
+						'shortcodes_required' => array('[TICKET_LINE_ITEM_LIST]'),
327
+					),
328
+					'ticket_line_item_pms' => array(
329
+						'input' => 'textarea',
330
+						'label' => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
331
+							'Ticket Line Item List with Price Modifiers',
332
+							'event_espresso'
333
+						),
334
+						'type' => 'string',
335
+						'required' => false,
336
+						'validation' => true,
337
+						'format' => '%s',
338
+						'css_class' => 'large-text',
339
+						'rows' => '5',
340
+						'shortcodes_required' => array('[TICKET_LINE_ITEM_LIST]'),
341
+					),
342
+					'price_modifier_line_item_list' => array(
343
+						'input' => 'textarea',
344
+						'label' => '[PRICE_MODIFIER_LINE_ITEM_LIST]',
345
+						'type' => 'string',
346
+						'required' => false,
347
+						'validation' => true,
348
+						'format' => '%s',
349
+						'css_class' => 'large-text',
350
+						'rows' => '5',
351
+						'shortcodes_required' => array('[PRICE_MODIFIER_LINE_ITEM_LIST]'),
352
+					),
353
+					'datetime_list' => array(
354
+						'input' => 'textarea',
355
+						'label' => '[DATETIME_LIST]',
356
+						'type' => 'string',
357
+						'required' => true,
358
+						'validation' => true,
359
+						'format' => '%s',
360
+						'css_class' => 'large-text',
361
+						'rows' => '5',
362
+						'shortcodes_required' => array('[DATETIME_LIST]'),
363
+					),
364
+					'attendee_list' => array(
365
+						'input' => 'textarea',
366
+						'label' => '[ATTENDEE_LIST]',
367
+						'type' => 'string',
368
+						'required' => true,
369
+						'validation' => true,
370
+						'format' => '%s',
371
+						'css_class' => 'large-text',
372
+						'rows' => '5',
373
+						'shortcodes_required' => array('[ATTENDEE_LIST]'),
374
+					),
375
+					'tax_line_item_list' => array(
376
+						'input' => 'textarea',
377
+						'label' => '[TAX_LINE_ITEM_LIST]',
378
+						'type' => 'string',
379
+						'required' => false,
380
+						'validation' => true,
381
+						'format' => '%s',
382
+						'css_class' => 'large-text',
383
+						'rows' => '5',
384
+						'shortcodes_required' => array('[TAX_LINE_ITEM_LIST]'),
385
+					),
386
+					'additional_line_item_list' => array(
387
+						'input' => 'textarea',
388
+						'label' => '[ADDITIONAL_LINE_ITEM_LIST]',
389
+						'type' => 'string',
390
+						'required' => false,
391
+						'validation' => true,
392
+						'format' => '%s',
393
+						'css_class' => 'large-text',
394
+						'rows' => '5',
395
+						'shortcodes_required' => array('[ADDITIONAL_LINE_ITEM_LIST]'),
396
+					),
397
+					'payment_list' => array(
398
+						'input' => 'textarea',
399
+						'label' => '[PAYMENT_LIST]',
400
+						'type' => 'string',
401
+						'required' => true,
402
+						'validation' => true,
403
+						'format' => '%s',
404
+						'css_class' => 'large-text',
405
+						'rows' => '5',
406
+						'shortcodes_required' => array('[PAYMENT_LIST_*]'),
407
+					),
408
+				),
409
+			),
410
+		);
411
+	}
412
+
413
+
414
+	/**
415
+	 * @see   definition of this method in parent
416
+	 * @since 4.5.0
417
+	 */
418
+	protected function _set_default_message_types()
419
+	{
420
+		$this->_default_message_types = array('receipt', 'invoice');
421
+	}
422
+
423
+
424
+	/**
425
+	 * @see   definition of this method in parent
426
+	 * @since 4.5.0
427
+	 */
428
+	protected function _set_valid_message_types()
429
+	{
430
+		$this->_valid_message_types = array('receipt', 'invoice');
431
+	}
432
+
433
+
434
+	/**
435
+	 * Displays the message in the browser.
436
+	 *
437
+	 * @since 4.5.0
438
+	 * @return string.
439
+	 */
440
+	protected function _send_message()
441
+	{
442
+		$this->_template_args = array(
443
+			'page_title' => $this->_subject,
444
+			'base_css' => $this->get_variation(
445
+				$this->_tmp_pack,
446
+				$this->_incoming_message_type->name,
447
+				true,
448
+				'base',
449
+				$this->_variation
450
+			),
451
+			'print_css' => $this->get_variation(
452
+				$this->_tmp_pack,
453
+				$this->_incoming_message_type->name,
454
+				true,
455
+				'print',
456
+				$this->_variation
457
+			),
458
+			'main_css' => $this->get_variation(
459
+				$this->_tmp_pack,
460
+				$this->_incoming_message_type->name,
461
+				true,
462
+				'main',
463
+				$this->_variation
464
+			),
465
+			'main_body' => wpautop(
466
+				apply_filters(
467
+					'FHEE__EE_Html_messenger___send_message__main_body',
468
+					$this->_content,
469
+					$this->_content,
470
+					$this->_incoming_message_type
471
+				)
472
+			)
473
+		);
474
+		$this->_deregister_wp_hooks();
475
+		add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts_styles'));
476
+
477
+		echo $this->_get_main_template();
478
+		exit();
479
+	}
480
+
481
+
482
+	/**
483
+	 * The purpose of this function is to de register all actions hooked into wp_head and wp_footer so that it doesn't
484
+	 * interfere with our templates.  If users want to add any custom styles or scripts they must use the
485
+	 * AHEE__EE_Html_messenger__enqueue_scripts_styles hook.
486
+	 *
487
+	 * @since 4.5.0
488
+	 * @return void
489
+	 */
490
+	protected function _deregister_wp_hooks()
491
+	{
492
+		remove_all_actions('wp_head');
493
+		remove_all_actions('wp_footer');
494
+		remove_all_actions('wp_print_footer_scripts');
495
+		remove_all_actions('wp_enqueue_scripts');
496
+		global $wp_scripts, $wp_styles;
497
+		$wp_scripts = $wp_styles = array();
498
+		// just add back in wp_enqueue_scripts and wp_print_footer_scripts cause that's all we want to load.
499
+		add_action('wp_footer', 'wp_print_footer_scripts');
500
+		add_action('wp_print_footer_scripts', '_wp_footer_scripts');
501
+		add_action('wp_head', 'wp_enqueue_scripts');
502
+	}
503
+
504
+
505
+	/**
506
+	 * Overwrite parent _get_main_template for display_html purposes.
507
+	 *
508
+	 * @since  4.5.0
509
+	 * @param bool $preview
510
+	 * @return string
511
+	 */
512
+	protected function _get_main_template($preview = false)
513
+	{
514
+		$wrapper_template = $this->_tmp_pack->get_wrapper($this->name, 'main');
515
+		// include message type as a template arg
516
+		$this->_template_args['message_type'] = $this->_incoming_message_type;
517
+		return EEH_Template::display_template($wrapper_template, $this->_template_args, true);
518
+	}
519
+
520
+
521
+	/**
522
+	 * @return string
523
+	 */
524
+	protected function _preview()
525
+	{
526
+		return $this->_send_message();
527
+	}
528
+
529
+
530
+	protected function _set_admin_settings_fields()
531
+	{
532
+	}
533
+
534
+
535
+	/**
536
+	 * add the "powered by EE" credit link to the HTML receipt and invoice
537
+	 *
538
+	 * @param string $content
539
+	 * @param string $content_again
540
+	 * @param \EE_message_type $incoming_message_type
541
+	 * @return string
542
+	 */
543
+	public function add_powered_by_credit_link_to_receipt_and_invoice(
544
+		$content = '',
545
+		$content_again = '',
546
+		EE_message_type $incoming_message_type
547
+	) {
548
+		if (
549
+			($incoming_message_type->name === 'invoice' || $incoming_message_type->name === 'receipt')
550
+			&& apply_filters('FHEE_EE_Html_messenger__add_powered_by_credit_link_to_receipt_and_invoice', true)
551
+		) {
552
+			$content .= \EEH_Template::powered_by_event_espresso(
553
+				'aln-cntr',
554
+				'',
555
+				array('utm_content' => 'messages_system')
556
+			)
557
+				. EEH_HTML::div(EEH_HTML::p('&nbsp;'));
558
+		}
559
+		return $content;
560
+	}
561 561
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Base_Class.class.php 1 patch
Indentation   +3368 added lines, -3368 removed lines patch added patch discarded remove patch
@@ -13,3384 +13,3384 @@
 block discarded – undo
13 13
  */
14 14
 abstract class EE_Base_Class
15 15
 {
16
-    /**
17
-     * This is an array of the original properties and values provided during construction
18
-     * of this model object. (keys are model field names, values are their values).
19
-     * This list is important to remember so that when we are merging data from the db, we know
20
-     * which values to override and which to not override.
21
-     *
22
-     * @var array
23
-     */
24
-    protected $_props_n_values_provided_in_constructor;
25
-
26
-    /**
27
-     * Timezone
28
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
29
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
30
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
31
-     * access to it.
32
-     *
33
-     * @var string
34
-     */
35
-    protected $_timezone;
36
-
37
-    /**
38
-     * date format
39
-     * pattern or format for displaying dates
40
-     *
41
-     * @var string $_dt_frmt
42
-     */
43
-    protected $_dt_frmt;
44
-
45
-    /**
46
-     * time format
47
-     * pattern or format for displaying time
48
-     *
49
-     * @var string $_tm_frmt
50
-     */
51
-    protected $_tm_frmt;
52
-
53
-    /**
54
-     * This property is for holding a cached array of object properties indexed by property name as the key.
55
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
56
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
57
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
58
-     *
59
-     * @var array
60
-     */
61
-    protected $_cached_properties = array();
62
-
63
-    /**
64
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
65
-     * single
66
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
67
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
68
-     * all others have an array)
69
-     *
70
-     * @var array
71
-     */
72
-    protected $_model_relations = array();
73
-
74
-    /**
75
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
76
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
77
-     *
78
-     * @var array
79
-     */
80
-    protected $_fields = array();
81
-
82
-    /**
83
-     * @var boolean indicating whether or not this model object is intended to ever be saved
84
-     * For example, we might create model objects intended to only be used for the duration
85
-     * of this request and to be thrown away, and if they were accidentally saved
86
-     * it would be a bug.
87
-     */
88
-    protected $_allow_persist = true;
89
-
90
-    /**
91
-     * @var boolean indicating whether or not this model object's properties have changed since construction
92
-     */
93
-    protected $_has_changes = false;
94
-
95
-    /**
96
-     * @var EEM_Base
97
-     */
98
-    protected $_model;
99
-
100
-    /**
101
-     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
102
-     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
103
-     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
104
-     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
105
-     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
106
-     * array as:
107
-     * array(
108
-     *  'Registration_Count' => 24
109
-     * );
110
-     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
111
-     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
112
-     * info)
113
-     *
114
-     * @var array
115
-     */
116
-    protected $custom_selection_results = array();
117
-
118
-
119
-    /**
120
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
121
-     * play nice
122
-     *
123
-     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
124
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
125
-     *                                                         TXN_amount, QST_name, etc) and values are their values
126
-     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
127
-     *                                                         corresponding db model or not.
128
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
129
-     *                                                         be in when instantiating a EE_Base_Class object.
130
-     * @param array   $date_formats                            An array of date formats to set on construct where first
131
-     *                                                         value is the date_format and second value is the time
132
-     *                                                         format.
133
-     * @throws InvalidArgumentException
134
-     * @throws InvalidInterfaceException
135
-     * @throws InvalidDataTypeException
136
-     * @throws EE_Error
137
-     * @throws ReflectionException
138
-     */
139
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
140
-    {
141
-        $className = get_class($this);
142
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
143
-        $model = $this->get_model();
144
-        $model_fields = $model->field_settings(false);
145
-        // ensure $fieldValues is an array
146
-        $fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
147
-        // verify client code has not passed any invalid field names
148
-        foreach ($fieldValues as $field_name => $field_value) {
149
-            if (! isset($model_fields[ $field_name ])) {
150
-                throw new EE_Error(
151
-                    sprintf(
152
-                        esc_html__(
153
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
154
-                            'event_espresso'
155
-                        ),
156
-                        $field_name,
157
-                        get_class($this),
158
-                        implode(', ', array_keys($model_fields))
159
-                    )
160
-                );
161
-            }
162
-        }
163
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
-        if (! empty($date_formats) && is_array($date_formats)) {
165
-            [$this->_dt_frmt, $this->_tm_frmt] = $date_formats;
166
-        } else {
167
-            // set default formats for date and time
168
-            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
169
-            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
170
-        }
171
-        // if db model is instantiating
172
-        if ($bydb) {
173
-            // client code has indicated these field values are from the database
174
-            foreach ($model_fields as $fieldName => $field) {
175
-                $this->set_from_db(
176
-                    $fieldName,
177
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
-                );
179
-            }
180
-        } else {
181
-            // we're constructing a brand
182
-            // new instance of the model object. Generally, this means we'll need to do more field validation
183
-            foreach ($model_fields as $fieldName => $field) {
184
-                $this->set(
185
-                    $fieldName,
186
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
-                    true
188
-                );
189
-            }
190
-        }
191
-        // remember what values were passed to this constructor
192
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
193
-        // remember in entity mapper
194
-        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
195
-            $model->add_to_entity_map($this);
196
-        }
197
-        // setup all the relations
198
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
199
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
-                $this->_model_relations[ $relation_name ] = null;
201
-            } else {
202
-                $this->_model_relations[ $relation_name ] = array();
203
-            }
204
-        }
205
-        /**
206
-         * Action done at the end of each model object construction
207
-         *
208
-         * @param EE_Base_Class $this the model object just created
209
-         */
210
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
211
-    }
212
-
213
-
214
-    /**
215
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
216
-     *
217
-     * @return boolean
218
-     */
219
-    public function allow_persist()
220
-    {
221
-        return $this->_allow_persist;
222
-    }
223
-
224
-
225
-    /**
226
-     * Sets whether or not this model object should be allowed to be saved to the DB.
227
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
228
-     * you got new information that somehow made you change your mind.
229
-     *
230
-     * @param boolean $allow_persist
231
-     * @return boolean
232
-     */
233
-    public function set_allow_persist($allow_persist)
234
-    {
235
-        return $this->_allow_persist = $allow_persist;
236
-    }
237
-
238
-
239
-    /**
240
-     * Gets the field's original value when this object was constructed during this request.
241
-     * This can be helpful when determining if a model object has changed or not
242
-     *
243
-     * @param string $field_name
244
-     * @return mixed|null
245
-     * @throws ReflectionException
246
-     * @throws InvalidArgumentException
247
-     * @throws InvalidInterfaceException
248
-     * @throws InvalidDataTypeException
249
-     * @throws EE_Error
250
-     */
251
-    public function get_original($field_name)
252
-    {
253
-        if (
254
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
255
-            && $field_settings = $this->get_model()->field_settings_for($field_name)
256
-        ) {
257
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
258
-        }
259
-        return null;
260
-    }
261
-
262
-
263
-    /**
264
-     * @param EE_Base_Class $obj
265
-     * @return string
266
-     */
267
-    public function get_class($obj)
268
-    {
269
-        return get_class($obj);
270
-    }
271
-
272
-
273
-    /**
274
-     * Overrides parent because parent expects old models.
275
-     * This also doesn't do any validation, and won't work for serialized arrays
276
-     *
277
-     * @param    string $field_name
278
-     * @param    mixed  $field_value
279
-     * @param bool      $use_default
280
-     * @throws InvalidArgumentException
281
-     * @throws InvalidInterfaceException
282
-     * @throws InvalidDataTypeException
283
-     * @throws EE_Error
284
-     * @throws ReflectionException
285
-     * @throws ReflectionException
286
-     * @throws ReflectionException
287
-     */
288
-    public function set($field_name, $field_value, $use_default = false)
289
-    {
290
-        // if not using default and nothing has changed, and object has already been setup (has ID),
291
-        // then don't do anything
292
-        if (
293
-            ! $use_default
294
-            && $this->_fields[ $field_name ] === $field_value
295
-            && $this->ID()
296
-        ) {
297
-            return;
298
-        }
299
-        $model = $this->get_model();
300
-        $this->_has_changes = true;
301
-        $field_obj = $model->field_settings_for($field_name);
302
-        if ($field_obj instanceof EE_Model_Field_Base) {
303
-            // if ( method_exists( $field_obj, 'set_timezone' )) {
304
-            if ($field_obj instanceof EE_Datetime_Field) {
305
-                $field_obj->set_timezone($this->_timezone);
306
-                $field_obj->set_date_format($this->_dt_frmt);
307
-                $field_obj->set_time_format($this->_tm_frmt);
308
-            }
309
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
310
-            // should the value be null?
311
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
312
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
313
-                /**
314
-                 * To save having to refactor all the models, if a default value is used for a
315
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
316
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
317
-                 * object.
318
-                 *
319
-                 * @since 4.6.10+
320
-                 */
321
-                if (
322
-                    $field_obj instanceof EE_Datetime_Field
323
-                    && $this->_fields[ $field_name ] !== null
324
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
325
-                ) {
326
-                    empty($this->_fields[ $field_name ])
327
-                        ? $this->set($field_name, time())
328
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
329
-                }
330
-            } else {
331
-                $this->_fields[ $field_name ] = $holder_of_value;
332
-            }
333
-            // if we're not in the constructor...
334
-            // now check if what we set was a primary key
335
-            if (
16
+	/**
17
+	 * This is an array of the original properties and values provided during construction
18
+	 * of this model object. (keys are model field names, values are their values).
19
+	 * This list is important to remember so that when we are merging data from the db, we know
20
+	 * which values to override and which to not override.
21
+	 *
22
+	 * @var array
23
+	 */
24
+	protected $_props_n_values_provided_in_constructor;
25
+
26
+	/**
27
+	 * Timezone
28
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
29
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
30
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
31
+	 * access to it.
32
+	 *
33
+	 * @var string
34
+	 */
35
+	protected $_timezone;
36
+
37
+	/**
38
+	 * date format
39
+	 * pattern or format for displaying dates
40
+	 *
41
+	 * @var string $_dt_frmt
42
+	 */
43
+	protected $_dt_frmt;
44
+
45
+	/**
46
+	 * time format
47
+	 * pattern or format for displaying time
48
+	 *
49
+	 * @var string $_tm_frmt
50
+	 */
51
+	protected $_tm_frmt;
52
+
53
+	/**
54
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
55
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
56
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
57
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
58
+	 *
59
+	 * @var array
60
+	 */
61
+	protected $_cached_properties = array();
62
+
63
+	/**
64
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
65
+	 * single
66
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
67
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
68
+	 * all others have an array)
69
+	 *
70
+	 * @var array
71
+	 */
72
+	protected $_model_relations = array();
73
+
74
+	/**
75
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
76
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
77
+	 *
78
+	 * @var array
79
+	 */
80
+	protected $_fields = array();
81
+
82
+	/**
83
+	 * @var boolean indicating whether or not this model object is intended to ever be saved
84
+	 * For example, we might create model objects intended to only be used for the duration
85
+	 * of this request and to be thrown away, and if they were accidentally saved
86
+	 * it would be a bug.
87
+	 */
88
+	protected $_allow_persist = true;
89
+
90
+	/**
91
+	 * @var boolean indicating whether or not this model object's properties have changed since construction
92
+	 */
93
+	protected $_has_changes = false;
94
+
95
+	/**
96
+	 * @var EEM_Base
97
+	 */
98
+	protected $_model;
99
+
100
+	/**
101
+	 * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
102
+	 * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
103
+	 * the db.  They also do not automatically update if there are any changes to the data that produced their results.
104
+	 * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
105
+	 * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
106
+	 * array as:
107
+	 * array(
108
+	 *  'Registration_Count' => 24
109
+	 * );
110
+	 * Note: if the custom select configuration for the query included a data type, the value will be in the data type
111
+	 * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
112
+	 * info)
113
+	 *
114
+	 * @var array
115
+	 */
116
+	protected $custom_selection_results = array();
117
+
118
+
119
+	/**
120
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
121
+	 * play nice
122
+	 *
123
+	 * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
124
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
125
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
126
+	 * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
127
+	 *                                                         corresponding db model or not.
128
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
129
+	 *                                                         be in when instantiating a EE_Base_Class object.
130
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
131
+	 *                                                         value is the date_format and second value is the time
132
+	 *                                                         format.
133
+	 * @throws InvalidArgumentException
134
+	 * @throws InvalidInterfaceException
135
+	 * @throws InvalidDataTypeException
136
+	 * @throws EE_Error
137
+	 * @throws ReflectionException
138
+	 */
139
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
140
+	{
141
+		$className = get_class($this);
142
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
143
+		$model = $this->get_model();
144
+		$model_fields = $model->field_settings(false);
145
+		// ensure $fieldValues is an array
146
+		$fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
147
+		// verify client code has not passed any invalid field names
148
+		foreach ($fieldValues as $field_name => $field_value) {
149
+			if (! isset($model_fields[ $field_name ])) {
150
+				throw new EE_Error(
151
+					sprintf(
152
+						esc_html__(
153
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
154
+							'event_espresso'
155
+						),
156
+						$field_name,
157
+						get_class($this),
158
+						implode(', ', array_keys($model_fields))
159
+					)
160
+				);
161
+			}
162
+		}
163
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
+		if (! empty($date_formats) && is_array($date_formats)) {
165
+			[$this->_dt_frmt, $this->_tm_frmt] = $date_formats;
166
+		} else {
167
+			// set default formats for date and time
168
+			$this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
169
+			$this->_tm_frmt = (string) get_option('time_format', 'g:i a');
170
+		}
171
+		// if db model is instantiating
172
+		if ($bydb) {
173
+			// client code has indicated these field values are from the database
174
+			foreach ($model_fields as $fieldName => $field) {
175
+				$this->set_from_db(
176
+					$fieldName,
177
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
+				);
179
+			}
180
+		} else {
181
+			// we're constructing a brand
182
+			// new instance of the model object. Generally, this means we'll need to do more field validation
183
+			foreach ($model_fields as $fieldName => $field) {
184
+				$this->set(
185
+					$fieldName,
186
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
+					true
188
+				);
189
+			}
190
+		}
191
+		// remember what values were passed to this constructor
192
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
193
+		// remember in entity mapper
194
+		if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
195
+			$model->add_to_entity_map($this);
196
+		}
197
+		// setup all the relations
198
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
199
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
+				$this->_model_relations[ $relation_name ] = null;
201
+			} else {
202
+				$this->_model_relations[ $relation_name ] = array();
203
+			}
204
+		}
205
+		/**
206
+		 * Action done at the end of each model object construction
207
+		 *
208
+		 * @param EE_Base_Class $this the model object just created
209
+		 */
210
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
211
+	}
212
+
213
+
214
+	/**
215
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
216
+	 *
217
+	 * @return boolean
218
+	 */
219
+	public function allow_persist()
220
+	{
221
+		return $this->_allow_persist;
222
+	}
223
+
224
+
225
+	/**
226
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
227
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
228
+	 * you got new information that somehow made you change your mind.
229
+	 *
230
+	 * @param boolean $allow_persist
231
+	 * @return boolean
232
+	 */
233
+	public function set_allow_persist($allow_persist)
234
+	{
235
+		return $this->_allow_persist = $allow_persist;
236
+	}
237
+
238
+
239
+	/**
240
+	 * Gets the field's original value when this object was constructed during this request.
241
+	 * This can be helpful when determining if a model object has changed or not
242
+	 *
243
+	 * @param string $field_name
244
+	 * @return mixed|null
245
+	 * @throws ReflectionException
246
+	 * @throws InvalidArgumentException
247
+	 * @throws InvalidInterfaceException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws EE_Error
250
+	 */
251
+	public function get_original($field_name)
252
+	{
253
+		if (
254
+			isset($this->_props_n_values_provided_in_constructor[ $field_name ])
255
+			&& $field_settings = $this->get_model()->field_settings_for($field_name)
256
+		) {
257
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
258
+		}
259
+		return null;
260
+	}
261
+
262
+
263
+	/**
264
+	 * @param EE_Base_Class $obj
265
+	 * @return string
266
+	 */
267
+	public function get_class($obj)
268
+	{
269
+		return get_class($obj);
270
+	}
271
+
272
+
273
+	/**
274
+	 * Overrides parent because parent expects old models.
275
+	 * This also doesn't do any validation, and won't work for serialized arrays
276
+	 *
277
+	 * @param    string $field_name
278
+	 * @param    mixed  $field_value
279
+	 * @param bool      $use_default
280
+	 * @throws InvalidArgumentException
281
+	 * @throws InvalidInterfaceException
282
+	 * @throws InvalidDataTypeException
283
+	 * @throws EE_Error
284
+	 * @throws ReflectionException
285
+	 * @throws ReflectionException
286
+	 * @throws ReflectionException
287
+	 */
288
+	public function set($field_name, $field_value, $use_default = false)
289
+	{
290
+		// if not using default and nothing has changed, and object has already been setup (has ID),
291
+		// then don't do anything
292
+		if (
293
+			! $use_default
294
+			&& $this->_fields[ $field_name ] === $field_value
295
+			&& $this->ID()
296
+		) {
297
+			return;
298
+		}
299
+		$model = $this->get_model();
300
+		$this->_has_changes = true;
301
+		$field_obj = $model->field_settings_for($field_name);
302
+		if ($field_obj instanceof EE_Model_Field_Base) {
303
+			// if ( method_exists( $field_obj, 'set_timezone' )) {
304
+			if ($field_obj instanceof EE_Datetime_Field) {
305
+				$field_obj->set_timezone($this->_timezone);
306
+				$field_obj->set_date_format($this->_dt_frmt);
307
+				$field_obj->set_time_format($this->_tm_frmt);
308
+			}
309
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
310
+			// should the value be null?
311
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
312
+				$this->_fields[ $field_name ] = $field_obj->get_default_value();
313
+				/**
314
+				 * To save having to refactor all the models, if a default value is used for a
315
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
316
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
317
+				 * object.
318
+				 *
319
+				 * @since 4.6.10+
320
+				 */
321
+				if (
322
+					$field_obj instanceof EE_Datetime_Field
323
+					&& $this->_fields[ $field_name ] !== null
324
+					&& ! $this->_fields[ $field_name ] instanceof DateTime
325
+				) {
326
+					empty($this->_fields[ $field_name ])
327
+						? $this->set($field_name, time())
328
+						: $this->set($field_name, $this->_fields[ $field_name ]);
329
+				}
330
+			} else {
331
+				$this->_fields[ $field_name ] = $holder_of_value;
332
+			}
333
+			// if we're not in the constructor...
334
+			// now check if what we set was a primary key
335
+			if (
336 336
 // note: props_n_values_provided_in_constructor is only set at the END of the constructor
337
-                $this->_props_n_values_provided_in_constructor
338
-                && $field_value
339
-                && $field_name === $model->primary_key_name()
340
-            ) {
341
-                // if so, we want all this object's fields to be filled either with
342
-                // what we've explicitly set on this model
343
-                // or what we have in the db
344
-                // echo "setting primary key!";
345
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
346
-                $obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
347
-                foreach ($fields_on_model as $field_obj) {
348
-                    if (
349
-                        ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
350
-                        && $field_obj->get_name() !== $field_name
351
-                    ) {
352
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
353
-                    }
354
-                }
355
-                // oh this model object has an ID? well make sure its in the entity mapper
356
-                $model->add_to_entity_map($this);
357
-            }
358
-            // let's unset any cache for this field_name from the $_cached_properties property.
359
-            $this->_clear_cached_property($field_name);
360
-        } else {
361
-            throw new EE_Error(
362
-                sprintf(
363
-                    esc_html__(
364
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
365
-                        'event_espresso'
366
-                    ),
367
-                    $field_name
368
-                )
369
-            );
370
-        }
371
-    }
372
-
373
-
374
-    /**
375
-     * Set custom select values for model.
376
-     *
377
-     * @param array $custom_select_values
378
-     */
379
-    public function setCustomSelectsValues(array $custom_select_values)
380
-    {
381
-        $this->custom_selection_results = $custom_select_values;
382
-    }
383
-
384
-
385
-    /**
386
-     * Returns the custom select value for the provided alias if its set.
387
-     * If not set, returns null.
388
-     *
389
-     * @param string $alias
390
-     * @return string|int|float|null
391
-     */
392
-    public function getCustomSelect($alias)
393
-    {
394
-        return isset($this->custom_selection_results[ $alias ])
395
-            ? $this->custom_selection_results[ $alias ]
396
-            : null;
397
-    }
398
-
399
-
400
-    /**
401
-     * This sets the field value on the db column if it exists for the given $column_name or
402
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
403
-     *
404
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
405
-     * @param string $field_name  Must be the exact column name.
406
-     * @param mixed  $field_value The value to set.
407
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
408
-     * @throws InvalidArgumentException
409
-     * @throws InvalidInterfaceException
410
-     * @throws InvalidDataTypeException
411
-     * @throws EE_Error
412
-     * @throws ReflectionException
413
-     */
414
-    public function set_field_or_extra_meta($field_name, $field_value)
415
-    {
416
-        if ($this->get_model()->has_field($field_name)) {
417
-            $this->set($field_name, $field_value);
418
-            return true;
419
-        }
420
-        // ensure this object is saved first so that extra meta can be properly related.
421
-        $this->save();
422
-        return $this->update_extra_meta($field_name, $field_value);
423
-    }
424
-
425
-
426
-    /**
427
-     * This retrieves the value of the db column set on this class or if that's not present
428
-     * it will attempt to retrieve from extra_meta if found.
429
-     * Example Usage:
430
-     * Via EE_Message child class:
431
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
432
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
433
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
434
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
435
-     * value for those extra fields dynamically via the EE_message object.
436
-     *
437
-     * @param  string $field_name expecting the fully qualified field name.
438
-     * @return mixed|null  value for the field if found.  null if not found.
439
-     * @throws ReflectionException
440
-     * @throws InvalidArgumentException
441
-     * @throws InvalidInterfaceException
442
-     * @throws InvalidDataTypeException
443
-     * @throws EE_Error
444
-     */
445
-    public function get_field_or_extra_meta($field_name)
446
-    {
447
-        if ($this->get_model()->has_field($field_name)) {
448
-            $column_value = $this->get($field_name);
449
-        } else {
450
-            // This isn't a column in the main table, let's see if it is in the extra meta.
451
-            $column_value = $this->get_extra_meta($field_name, true, null);
452
-        }
453
-        return $column_value;
454
-    }
455
-
456
-
457
-    /**
458
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
459
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
460
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
461
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
462
-     *
463
-     * @access public
464
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
465
-     * @return void
466
-     * @throws InvalidArgumentException
467
-     * @throws InvalidInterfaceException
468
-     * @throws InvalidDataTypeException
469
-     * @throws EE_Error
470
-     * @throws ReflectionException
471
-     */
472
-    public function set_timezone($timezone = '')
473
-    {
474
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
475
-        // make sure we clear all cached properties because they won't be relevant now
476
-        $this->_clear_cached_properties();
477
-        // make sure we update field settings and the date for all EE_Datetime_Fields
478
-        $model_fields = $this->get_model()->field_settings(false);
479
-        foreach ($model_fields as $field_name => $field_obj) {
480
-            if ($field_obj instanceof EE_Datetime_Field) {
481
-                $field_obj->set_timezone($this->_timezone);
482
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
483
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
484
-                }
485
-            }
486
-        }
487
-    }
488
-
489
-
490
-    /**
491
-     * This just returns whatever is set for the current timezone.
492
-     *
493
-     * @access public
494
-     * @return string timezone string
495
-     */
496
-    public function get_timezone()
497
-    {
498
-        return $this->_timezone;
499
-    }
500
-
501
-
502
-    /**
503
-     * This sets the internal date format to what is sent in to be used as the new default for the class
504
-     * internally instead of wp set date format options
505
-     *
506
-     * @since 4.6
507
-     * @param string $format should be a format recognizable by PHP date() functions.
508
-     */
509
-    public function set_date_format($format)
510
-    {
511
-        $this->_dt_frmt = $format;
512
-        // clear cached_properties because they won't be relevant now.
513
-        $this->_clear_cached_properties();
514
-    }
515
-
516
-
517
-    /**
518
-     * This sets the internal time format string to what is sent in to be used as the new default for the
519
-     * class internally instead of wp set time format options.
520
-     *
521
-     * @since 4.6
522
-     * @param string $format should be a format recognizable by PHP date() functions.
523
-     */
524
-    public function set_time_format($format)
525
-    {
526
-        $this->_tm_frmt = $format;
527
-        // clear cached_properties because they won't be relevant now.
528
-        $this->_clear_cached_properties();
529
-    }
530
-
531
-
532
-    /**
533
-     * This returns the current internal set format for the date and time formats.
534
-     *
535
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
536
-     *                             where the first value is the date format and the second value is the time format.
537
-     * @return mixed string|array
538
-     */
539
-    public function get_format($full = true)
540
-    {
541
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
542
-    }
543
-
544
-
545
-    /**
546
-     * cache
547
-     * stores the passed model object on the current model object.
548
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
549
-     *
550
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
551
-     *                                       'Registration' associated with this model object
552
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
553
-     *                                       that could be a payment or a registration)
554
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
555
-     *                                       items which will be stored in an array on this object
556
-     * @throws ReflectionException
557
-     * @throws InvalidArgumentException
558
-     * @throws InvalidInterfaceException
559
-     * @throws InvalidDataTypeException
560
-     * @throws EE_Error
561
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
562
-     *                                       related thing, no array)
563
-     */
564
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
565
-    {
566
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
567
-        if (! $object_to_cache instanceof EE_Base_Class) {
568
-            return false;
569
-        }
570
-        // also get "how" the object is related, or throw an error
571
-        if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
572
-            throw new EE_Error(
573
-                sprintf(
574
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
575
-                    $relationName,
576
-                    get_class($this)
577
-                )
578
-            );
579
-        }
580
-        // how many things are related ?
581
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
582
-            // if it's a "belongs to" relationship, then there's only one related model object
583
-            // eg, if this is a registration, there's only 1 attendee for it
584
-            // so for these model objects just set it to be cached
585
-            $this->_model_relations[ $relationName ] = $object_to_cache;
586
-            $return = true;
587
-        } else {
588
-            // otherwise, this is the "many" side of a one to many relationship,
589
-            // so we'll add the object to the array of related objects for that type.
590
-            // eg: if this is an event, there are many registrations for that event,
591
-            // so we cache the registrations in an array
592
-            if (! is_array($this->_model_relations[ $relationName ])) {
593
-                // if for some reason, the cached item is a model object,
594
-                // then stick that in the array, otherwise start with an empty array
595
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
596
-                                                           instanceof
597
-                                                           EE_Base_Class
598
-                    ? array($this->_model_relations[ $relationName ]) : array();
599
-            }
600
-            // first check for a cache_id which is normally empty
601
-            if (! empty($cache_id)) {
602
-                // if the cache_id exists, then it means we are purposely trying to cache this
603
-                // with a known key that can then be used to retrieve the object later on
604
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
605
-                $return = $cache_id;
606
-            } elseif ($object_to_cache->ID()) {
607
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
608
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
609
-                $return = $object_to_cache->ID();
610
-            } else {
611
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
612
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
613
-                // move the internal pointer to the end of the array
614
-                end($this->_model_relations[ $relationName ]);
615
-                // and grab the key so that we can return it
616
-                $return = key($this->_model_relations[ $relationName ]);
617
-            }
618
-        }
619
-        return $return;
620
-    }
621
-
622
-
623
-    /**
624
-     * For adding an item to the cached_properties property.
625
-     *
626
-     * @access protected
627
-     * @param string      $fieldname the property item the corresponding value is for.
628
-     * @param mixed       $value     The value we are caching.
629
-     * @param string|null $cache_type
630
-     * @return void
631
-     * @throws ReflectionException
632
-     * @throws InvalidArgumentException
633
-     * @throws InvalidInterfaceException
634
-     * @throws InvalidDataTypeException
635
-     * @throws EE_Error
636
-     */
637
-    protected function _set_cached_property($fieldname, $value, $cache_type = null)
638
-    {
639
-        // first make sure this property exists
640
-        $this->get_model()->field_settings_for($fieldname);
641
-        $cache_type = empty($cache_type) ? 'standard' : $cache_type;
642
-        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
643
-    }
644
-
645
-
646
-    /**
647
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
648
-     * This also SETS the cache if we return the actual property!
649
-     *
650
-     * @param string $fieldname        the name of the property we're trying to retrieve
651
-     * @param bool   $pretty
652
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
653
-     *                                 (in cases where the same property may be used for different outputs
654
-     *                                 - i.e. datetime, money etc.)
655
-     *                                 It can also accept certain pre-defined "schema" strings
656
-     *                                 to define how to output the property.
657
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
658
-     * @return mixed                   whatever the value for the property is we're retrieving
659
-     * @throws ReflectionException
660
-     * @throws InvalidArgumentException
661
-     * @throws InvalidInterfaceException
662
-     * @throws InvalidDataTypeException
663
-     * @throws EE_Error
664
-     */
665
-    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
666
-    {
667
-        // verify the field exists
668
-        $model = $this->get_model();
669
-        $model->field_settings_for($fieldname);
670
-        $cache_type = $pretty ? 'pretty' : 'standard';
671
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
672
-        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
673
-            return $this->_cached_properties[ $fieldname ][ $cache_type ];
674
-        }
675
-        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
676
-        $this->_set_cached_property($fieldname, $value, $cache_type);
677
-        return $value;
678
-    }
679
-
680
-
681
-    /**
682
-     * If the cache didn't fetch the needed item, this fetches it.
683
-     *
684
-     * @param string $fieldname
685
-     * @param bool   $pretty
686
-     * @param string $extra_cache_ref
687
-     * @return mixed
688
-     * @throws InvalidArgumentException
689
-     * @throws InvalidInterfaceException
690
-     * @throws InvalidDataTypeException
691
-     * @throws EE_Error
692
-     * @throws ReflectionException
693
-     */
694
-    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
695
-    {
696
-        $field_obj = $this->get_model()->field_settings_for($fieldname);
697
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
698
-        if ($field_obj instanceof EE_Datetime_Field) {
699
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
700
-        }
701
-        if (! isset($this->_fields[ $fieldname ])) {
702
-            $this->_fields[ $fieldname ] = null;
703
-        }
704
-        $value = $pretty
705
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
706
-            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
707
-        return $value;
708
-    }
709
-
710
-
711
-    /**
712
-     * set timezone, formats, and output for EE_Datetime_Field objects
713
-     *
714
-     * @param \EE_Datetime_Field $datetime_field
715
-     * @param bool               $pretty
716
-     * @param null               $date_or_time
717
-     * @return void
718
-     * @throws InvalidArgumentException
719
-     * @throws InvalidInterfaceException
720
-     * @throws InvalidDataTypeException
721
-     * @throws EE_Error
722
-     */
723
-    protected function _prepare_datetime_field(
724
-        EE_Datetime_Field $datetime_field,
725
-        $pretty = false,
726
-        $date_or_time = null
727
-    ) {
728
-        $datetime_field->set_timezone($this->_timezone);
729
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
730
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
731
-        // set the output returned
732
-        switch ($date_or_time) {
733
-            case 'D':
734
-                $datetime_field->set_date_time_output('date');
735
-                break;
736
-            case 'T':
737
-                $datetime_field->set_date_time_output('time');
738
-                break;
739
-            default:
740
-                $datetime_field->set_date_time_output();
741
-        }
742
-    }
743
-
744
-
745
-    /**
746
-     * This just takes care of clearing out the cached_properties
747
-     *
748
-     * @return void
749
-     */
750
-    protected function _clear_cached_properties()
751
-    {
752
-        $this->_cached_properties = array();
753
-    }
754
-
755
-
756
-    /**
757
-     * This just clears out ONE property if it exists in the cache
758
-     *
759
-     * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
760
-     * @return void
761
-     */
762
-    protected function _clear_cached_property($property_name)
763
-    {
764
-        if (isset($this->_cached_properties[ $property_name ])) {
765
-            unset($this->_cached_properties[ $property_name ]);
766
-        }
767
-    }
768
-
769
-
770
-    /**
771
-     * Ensures that this related thing is a model object.
772
-     *
773
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
774
-     * @param string $model_name   name of the related thing, eg 'Attendee',
775
-     * @return EE_Base_Class
776
-     * @throws ReflectionException
777
-     * @throws InvalidArgumentException
778
-     * @throws InvalidInterfaceException
779
-     * @throws InvalidDataTypeException
780
-     * @throws EE_Error
781
-     */
782
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
783
-    {
784
-        $other_model_instance = self::_get_model_instance_with_name(
785
-            self::_get_model_classname($model_name),
786
-            $this->_timezone
787
-        );
788
-        return $other_model_instance->ensure_is_obj($object_or_id);
789
-    }
790
-
791
-
792
-    /**
793
-     * Forgets the cached model of the given relation Name. So the next time we request it,
794
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
795
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
796
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
797
-     *
798
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
799
-     *                                                     Eg 'Registration'
800
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
801
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
802
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
803
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
804
-     *                                                     this is HasMany or HABTM.
805
-     * @throws ReflectionException
806
-     * @throws InvalidArgumentException
807
-     * @throws InvalidInterfaceException
808
-     * @throws InvalidDataTypeException
809
-     * @throws EE_Error
810
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
811
-     *                                                     relation from all
812
-     */
813
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
814
-    {
815
-        $relationship_to_model = $this->get_model()->related_settings_for($relationName);
816
-        $index_in_cache = '';
817
-        if (! $relationship_to_model) {
818
-            throw new EE_Error(
819
-                sprintf(
820
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
821
-                    $relationName,
822
-                    get_class($this)
823
-                )
824
-            );
825
-        }
826
-        if ($clear_all) {
827
-            $obj_removed = true;
828
-            $this->_model_relations[ $relationName ] = null;
829
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
830
-            $obj_removed = $this->_model_relations[ $relationName ];
831
-            $this->_model_relations[ $relationName ] = null;
832
-        } else {
833
-            if (
834
-                $object_to_remove_or_index_into_array instanceof EE_Base_Class
835
-                && $object_to_remove_or_index_into_array->ID()
836
-            ) {
837
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
838
-                if (
839
-                    is_array($this->_model_relations[ $relationName ])
840
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
841
-                ) {
842
-                    $index_found_at = null;
843
-                    // find this object in the array even though it has a different key
844
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
845
-                        /** @noinspection TypeUnsafeComparisonInspection */
846
-                        if (
847
-                            $obj instanceof EE_Base_Class
848
-                            && (
849
-                                $obj == $object_to_remove_or_index_into_array
850
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
851
-                            )
852
-                        ) {
853
-                            $index_found_at = $index;
854
-                            break;
855
-                        }
856
-                    }
857
-                    if ($index_found_at) {
858
-                        $index_in_cache = $index_found_at;
859
-                    } else {
860
-                        // it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
861
-                        // if it wasn't in it to begin with. So we're done
862
-                        return $object_to_remove_or_index_into_array;
863
-                    }
864
-                }
865
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
866
-                // so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
867
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
868
-                    /** @noinspection TypeUnsafeComparisonInspection */
869
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
870
-                        $index_in_cache = $index;
871
-                    }
872
-                }
873
-            } else {
874
-                $index_in_cache = $object_to_remove_or_index_into_array;
875
-            }
876
-            // supposedly we've found it. But it could just be that the client code
877
-            // provided a bad index/object
878
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
879
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
880
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
881
-            } else {
882
-                // that thing was never cached anyways.
883
-                $obj_removed = null;
884
-            }
885
-        }
886
-        return $obj_removed;
887
-    }
888
-
889
-
890
-    /**
891
-     * update_cache_after_object_save
892
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
893
-     * obtained after being saved to the db
894
-     *
895
-     * @param string        $relationName       - the type of object that is cached
896
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
897
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
898
-     * @return boolean TRUE on success, FALSE on fail
899
-     * @throws ReflectionException
900
-     * @throws InvalidArgumentException
901
-     * @throws InvalidInterfaceException
902
-     * @throws InvalidDataTypeException
903
-     * @throws EE_Error
904
-     */
905
-    public function update_cache_after_object_save(
906
-        $relationName,
907
-        EE_Base_Class $newly_saved_object,
908
-        $current_cache_id = ''
909
-    ) {
910
-        // verify that incoming object is of the correct type
911
-        $obj_class = 'EE_' . $relationName;
912
-        if ($newly_saved_object instanceof $obj_class) {
913
-            /* @type EE_Base_Class $newly_saved_object */
914
-            // now get the type of relation
915
-            $relationship_to_model = $this->get_model()->related_settings_for($relationName);
916
-            // if this is a 1:1 relationship
917
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
918
-                // then just replace the cached object with the newly saved object
919
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
920
-                return true;
921
-                // or if it's some kind of sordid feral polyamorous relationship...
922
-            }
923
-            if (
924
-                is_array($this->_model_relations[ $relationName ])
925
-                && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
926
-            ) {
927
-                // then remove the current cached item
928
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
929
-                // and cache the newly saved object using it's new ID
930
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
931
-                return true;
932
-            }
933
-        }
934
-        return false;
935
-    }
936
-
937
-
938
-    /**
939
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
940
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
941
-     *
942
-     * @param string $relationName
943
-     * @return EE_Base_Class
944
-     */
945
-    public function get_one_from_cache($relationName)
946
-    {
947
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
948
-            ? $this->_model_relations[ $relationName ]
949
-            : null;
950
-        if (is_array($cached_array_or_object)) {
951
-            return array_shift($cached_array_or_object);
952
-        }
953
-        return $cached_array_or_object;
954
-    }
955
-
956
-
957
-    /**
958
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
959
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
960
-     *
961
-     * @param string $relationName
962
-     * @throws ReflectionException
963
-     * @throws InvalidArgumentException
964
-     * @throws InvalidInterfaceException
965
-     * @throws InvalidDataTypeException
966
-     * @throws EE_Error
967
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
968
-     */
969
-    public function get_all_from_cache($relationName)
970
-    {
971
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
972
-        // if the result is not an array, but exists, make it an array
973
-        $objects = is_array($objects) ? $objects : array($objects);
974
-        // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
975
-        // basically, if this model object was stored in the session, and these cached model objects
976
-        // already have IDs, let's make sure they're in their model's entity mapper
977
-        // otherwise we will have duplicates next time we call
978
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
979
-        $model = EE_Registry::instance()->load_model($relationName);
980
-        foreach ($objects as $model_object) {
981
-            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
982
-                // ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
983
-                if ($model_object->ID()) {
984
-                    $model->add_to_entity_map($model_object);
985
-                }
986
-            } else {
987
-                throw new EE_Error(
988
-                    sprintf(
989
-                        esc_html__(
990
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
991
-                            'event_espresso'
992
-                        ),
993
-                        $relationName,
994
-                        gettype($model_object)
995
-                    )
996
-                );
997
-            }
998
-        }
999
-        return $objects;
1000
-    }
1001
-
1002
-
1003
-    /**
1004
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1005
-     * matching the given query conditions.
1006
-     *
1007
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1008
-     * @param int   $limit              How many objects to return.
1009
-     * @param array $query_params       Any additional conditions on the query.
1010
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1011
-     *                                  you can indicate just the columns you want returned
1012
-     * @return array|EE_Base_Class[]
1013
-     * @throws ReflectionException
1014
-     * @throws InvalidArgumentException
1015
-     * @throws InvalidInterfaceException
1016
-     * @throws InvalidDataTypeException
1017
-     * @throws EE_Error
1018
-     */
1019
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1020
-    {
1021
-        $model = $this->get_model();
1022
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1023
-            ? $model->get_primary_key_field()->get_name()
1024
-            : $field_to_order_by;
1025
-        $current_value = ! empty($field) ? $this->get($field) : null;
1026
-        if (empty($field) || empty($current_value)) {
1027
-            return array();
1028
-        }
1029
-        return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1030
-    }
1031
-
1032
-
1033
-    /**
1034
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1035
-     * matching the given query conditions.
1036
-     *
1037
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1038
-     * @param int   $limit              How many objects to return.
1039
-     * @param array $query_params       Any additional conditions on the query.
1040
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1041
-     *                                  you can indicate just the columns you want returned
1042
-     * @return array|EE_Base_Class[]
1043
-     * @throws ReflectionException
1044
-     * @throws InvalidArgumentException
1045
-     * @throws InvalidInterfaceException
1046
-     * @throws InvalidDataTypeException
1047
-     * @throws EE_Error
1048
-     */
1049
-    public function previous_x(
1050
-        $field_to_order_by = null,
1051
-        $limit = 1,
1052
-        $query_params = array(),
1053
-        $columns_to_select = null
1054
-    ) {
1055
-        $model = $this->get_model();
1056
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1057
-            ? $model->get_primary_key_field()->get_name()
1058
-            : $field_to_order_by;
1059
-        $current_value = ! empty($field) ? $this->get($field) : null;
1060
-        if (empty($field) || empty($current_value)) {
1061
-            return array();
1062
-        }
1063
-        return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1064
-    }
1065
-
1066
-
1067
-    /**
1068
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1069
-     * matching the given query conditions.
1070
-     *
1071
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1072
-     * @param array $query_params       Any additional conditions on the query.
1073
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1074
-     *                                  you can indicate just the columns you want returned
1075
-     * @return array|EE_Base_Class
1076
-     * @throws ReflectionException
1077
-     * @throws InvalidArgumentException
1078
-     * @throws InvalidInterfaceException
1079
-     * @throws InvalidDataTypeException
1080
-     * @throws EE_Error
1081
-     */
1082
-    public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1083
-    {
1084
-        $model = $this->get_model();
1085
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1086
-            ? $model->get_primary_key_field()->get_name()
1087
-            : $field_to_order_by;
1088
-        $current_value = ! empty($field) ? $this->get($field) : null;
1089
-        if (empty($field) || empty($current_value)) {
1090
-            return array();
1091
-        }
1092
-        return $model->next($current_value, $field, $query_params, $columns_to_select);
1093
-    }
1094
-
1095
-
1096
-    /**
1097
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1098
-     * matching the given query conditions.
1099
-     *
1100
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1101
-     * @param array $query_params       Any additional conditions on the query.
1102
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1103
-     *                                  you can indicate just the column you want returned
1104
-     * @return array|EE_Base_Class
1105
-     * @throws ReflectionException
1106
-     * @throws InvalidArgumentException
1107
-     * @throws InvalidInterfaceException
1108
-     * @throws InvalidDataTypeException
1109
-     * @throws EE_Error
1110
-     */
1111
-    public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1112
-    {
1113
-        $model = $this->get_model();
1114
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1115
-            ? $model->get_primary_key_field()->get_name()
1116
-            : $field_to_order_by;
1117
-        $current_value = ! empty($field) ? $this->get($field) : null;
1118
-        if (empty($field) || empty($current_value)) {
1119
-            return array();
1120
-        }
1121
-        return $model->previous($current_value, $field, $query_params, $columns_to_select);
1122
-    }
1123
-
1124
-
1125
-    /**
1126
-     * Overrides parent because parent expects old models.
1127
-     * This also doesn't do any validation, and won't work for serialized arrays
1128
-     *
1129
-     * @param string $field_name
1130
-     * @param mixed  $field_value_from_db
1131
-     * @throws ReflectionException
1132
-     * @throws InvalidArgumentException
1133
-     * @throws InvalidInterfaceException
1134
-     * @throws InvalidDataTypeException
1135
-     * @throws EE_Error
1136
-     */
1137
-    public function set_from_db($field_name, $field_value_from_db)
1138
-    {
1139
-        $field_obj = $this->get_model()->field_settings_for($field_name);
1140
-        if ($field_obj instanceof EE_Model_Field_Base) {
1141
-            // you would think the DB has no NULLs for non-null label fields right? wrong!
1142
-            // eg, a CPT model object could have an entry in the posts table, but no
1143
-            // entry in the meta table. Meaning that all its columns in the meta table
1144
-            // are null! yikes! so when we find one like that, use defaults for its meta columns
1145
-            if ($field_value_from_db === null) {
1146
-                if ($field_obj->is_nullable()) {
1147
-                    // if the field allows nulls, then let it be null
1148
-                    $field_value = null;
1149
-                } else {
1150
-                    $field_value = $field_obj->get_default_value();
1151
-                }
1152
-            } else {
1153
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1154
-            }
1155
-            $this->_fields[ $field_name ] = $field_value;
1156
-            $this->_clear_cached_property($field_name);
1157
-        }
1158
-    }
1159
-
1160
-
1161
-    /**
1162
-     * verifies that the specified field is of the correct type
1163
-     *
1164
-     * @param string $field_name
1165
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1166
-     *                                (in cases where the same property may be used for different outputs
1167
-     *                                - i.e. datetime, money etc.)
1168
-     * @return mixed
1169
-     * @throws ReflectionException
1170
-     * @throws InvalidArgumentException
1171
-     * @throws InvalidInterfaceException
1172
-     * @throws InvalidDataTypeException
1173
-     * @throws EE_Error
1174
-     */
1175
-    public function get($field_name, $extra_cache_ref = null)
1176
-    {
1177
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1178
-    }
1179
-
1180
-
1181
-    /**
1182
-     * This method simply returns the RAW unprocessed value for the given property in this class
1183
-     *
1184
-     * @param  string $field_name A valid fieldname
1185
-     * @return mixed              Whatever the raw value stored on the property is.
1186
-     * @throws ReflectionException
1187
-     * @throws InvalidArgumentException
1188
-     * @throws InvalidInterfaceException
1189
-     * @throws InvalidDataTypeException
1190
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1191
-     */
1192
-    public function get_raw($field_name)
1193
-    {
1194
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1195
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1196
-            ? $this->_fields[ $field_name ]->format('U')
1197
-            : $this->_fields[ $field_name ];
1198
-    }
1199
-
1200
-
1201
-    /**
1202
-     * This is used to return the internal DateTime object used for a field that is a
1203
-     * EE_Datetime_Field.
1204
-     *
1205
-     * @param string $field_name               The field name retrieving the DateTime object.
1206
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1207
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1208
-     *                                         EE_Datetime_Field and but the field value is null, then
1209
-     *                                         just null is returned (because that indicates that likely
1210
-     *                                         this field is nullable).
1211
-     * @throws InvalidArgumentException
1212
-     * @throws InvalidDataTypeException
1213
-     * @throws InvalidInterfaceException
1214
-     * @throws ReflectionException
1215
-     */
1216
-    public function get_DateTime_object($field_name)
1217
-    {
1218
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1219
-        if (! $field_settings instanceof EE_Datetime_Field) {
1220
-            EE_Error::add_error(
1221
-                sprintf(
1222
-                    esc_html__(
1223
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1224
-                        'event_espresso'
1225
-                    ),
1226
-                    $field_name
1227
-                ),
1228
-                __FILE__,
1229
-                __FUNCTION__,
1230
-                __LINE__
1231
-            );
1232
-            return false;
1233
-        }
1234
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1235
-            ? clone $this->_fields[ $field_name ]
1236
-            : null;
1237
-    }
1238
-
1239
-
1240
-    /**
1241
-     * To be used in template to immediately echo out the value, and format it for output.
1242
-     * Eg, should call stripslashes and whatnot before echoing
1243
-     *
1244
-     * @param string $field_name      the name of the field as it appears in the DB
1245
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1246
-     *                                (in cases where the same property may be used for different outputs
1247
-     *                                - i.e. datetime, money etc.)
1248
-     * @return void
1249
-     * @throws ReflectionException
1250
-     * @throws InvalidArgumentException
1251
-     * @throws InvalidInterfaceException
1252
-     * @throws InvalidDataTypeException
1253
-     * @throws EE_Error
1254
-     */
1255
-    public function e($field_name, $extra_cache_ref = null)
1256
-    {
1257
-        echo wp_kses($this->get_pretty($field_name, $extra_cache_ref), AllowedTags::getWithFormTags());
1258
-    }
1259
-
1260
-
1261
-    /**
1262
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1263
-     * can be easily used as the value of form input.
1264
-     *
1265
-     * @param string $field_name
1266
-     * @return void
1267
-     * @throws ReflectionException
1268
-     * @throws InvalidArgumentException
1269
-     * @throws InvalidInterfaceException
1270
-     * @throws InvalidDataTypeException
1271
-     * @throws EE_Error
1272
-     */
1273
-    public function f($field_name)
1274
-    {
1275
-        $this->e($field_name, 'form_input');
1276
-    }
1277
-
1278
-
1279
-    /**
1280
-     * Same as `f()` but just returns the value instead of echoing it
1281
-     *
1282
-     * @param string $field_name
1283
-     * @return string
1284
-     * @throws ReflectionException
1285
-     * @throws InvalidArgumentException
1286
-     * @throws InvalidInterfaceException
1287
-     * @throws InvalidDataTypeException
1288
-     * @throws EE_Error
1289
-     */
1290
-    public function get_f($field_name)
1291
-    {
1292
-        return (string) $this->get_pretty($field_name, 'form_input');
1293
-    }
1294
-
1295
-
1296
-    /**
1297
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1298
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1299
-     * to see what options are available.
1300
-     *
1301
-     * @param string $field_name
1302
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1303
-     *                                (in cases where the same property may be used for different outputs
1304
-     *                                - i.e. datetime, money etc.)
1305
-     * @return mixed
1306
-     * @throws ReflectionException
1307
-     * @throws InvalidArgumentException
1308
-     * @throws InvalidInterfaceException
1309
-     * @throws InvalidDataTypeException
1310
-     * @throws EE_Error
1311
-     */
1312
-    public function get_pretty($field_name, $extra_cache_ref = null)
1313
-    {
1314
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1315
-    }
1316
-
1317
-
1318
-    /**
1319
-     * This simply returns the datetime for the given field name
1320
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1321
-     * (and the equivalent e_date, e_time, e_datetime).
1322
-     *
1323
-     * @access   protected
1324
-     * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1325
-     * @param string   $dt_frmt      valid datetime format used for date
1326
-     *                               (if '' then we just use the default on the field,
1327
-     *                               if NULL we use the last-used format)
1328
-     * @param string   $tm_frmt      Same as above except this is for time format
1329
-     * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1330
-     * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1331
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1332
-     *                               if field is not a valid dtt field, or void if echoing
1333
-     * @throws ReflectionException
1334
-     * @throws InvalidArgumentException
1335
-     * @throws InvalidInterfaceException
1336
-     * @throws InvalidDataTypeException
1337
-     * @throws EE_Error
1338
-     */
1339
-    protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1340
-    {
1341
-        // clear cached property
1342
-        $this->_clear_cached_property($field_name);
1343
-        // reset format properties because they are used in get()
1344
-        $this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1345
-        $this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1346
-        if ($echo) {
1347
-            $this->e($field_name, $date_or_time);
1348
-            return '';
1349
-        }
1350
-        return $this->get($field_name, $date_or_time);
1351
-    }
1352
-
1353
-
1354
-    /**
1355
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1356
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1357
-     * other echoes the pretty value for dtt)
1358
-     *
1359
-     * @param  string $field_name name of model object datetime field holding the value
1360
-     * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1361
-     * @return string            datetime value formatted
1362
-     * @throws ReflectionException
1363
-     * @throws InvalidArgumentException
1364
-     * @throws InvalidInterfaceException
1365
-     * @throws InvalidDataTypeException
1366
-     * @throws EE_Error
1367
-     */
1368
-    public function get_date($field_name, $format = '')
1369
-    {
1370
-        return $this->_get_datetime($field_name, $format, null, 'D');
1371
-    }
1372
-
1373
-
1374
-    /**
1375
-     * @param        $field_name
1376
-     * @param string $format
1377
-     * @throws ReflectionException
1378
-     * @throws InvalidArgumentException
1379
-     * @throws InvalidInterfaceException
1380
-     * @throws InvalidDataTypeException
1381
-     * @throws EE_Error
1382
-     */
1383
-    public function e_date($field_name, $format = '')
1384
-    {
1385
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1386
-    }
1387
-
1388
-
1389
-    /**
1390
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1391
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1392
-     * other echoes the pretty value for dtt)
1393
-     *
1394
-     * @param  string $field_name name of model object datetime field holding the value
1395
-     * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1396
-     * @return string             datetime value formatted
1397
-     * @throws ReflectionException
1398
-     * @throws InvalidArgumentException
1399
-     * @throws InvalidInterfaceException
1400
-     * @throws InvalidDataTypeException
1401
-     * @throws EE_Error
1402
-     */
1403
-    public function get_time($field_name, $format = '')
1404
-    {
1405
-        return $this->_get_datetime($field_name, null, $format, 'T');
1406
-    }
1407
-
1408
-
1409
-    /**
1410
-     * @param        $field_name
1411
-     * @param string $format
1412
-     * @throws ReflectionException
1413
-     * @throws InvalidArgumentException
1414
-     * @throws InvalidInterfaceException
1415
-     * @throws InvalidDataTypeException
1416
-     * @throws EE_Error
1417
-     */
1418
-    public function e_time($field_name, $format = '')
1419
-    {
1420
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1421
-    }
1422
-
1423
-
1424
-    /**
1425
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1426
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1427
-     * other echoes the pretty value for dtt)
1428
-     *
1429
-     * @param  string $field_name name of model object datetime field holding the value
1430
-     * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1431
-     * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1432
-     * @return string             datetime value formatted
1433
-     * @throws ReflectionException
1434
-     * @throws InvalidArgumentException
1435
-     * @throws InvalidInterfaceException
1436
-     * @throws InvalidDataTypeException
1437
-     * @throws EE_Error
1438
-     */
1439
-    public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1440
-    {
1441
-        return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1442
-    }
1443
-
1444
-
1445
-    /**
1446
-     * @param string $field_name
1447
-     * @param string $dt_frmt
1448
-     * @param string $tm_frmt
1449
-     * @throws ReflectionException
1450
-     * @throws InvalidArgumentException
1451
-     * @throws InvalidInterfaceException
1452
-     * @throws InvalidDataTypeException
1453
-     * @throws EE_Error
1454
-     */
1455
-    public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1456
-    {
1457
-        $this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1458
-    }
1459
-
1460
-
1461
-    /**
1462
-     * Get the i8ln value for a date using the WordPress @see date_i18n function.
1463
-     *
1464
-     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1465
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1466
-     *                           on the object will be used.
1467
-     * @return string Date and time string in set locale or false if no field exists for the given
1468
-     * @throws ReflectionException
1469
-     * @throws InvalidArgumentException
1470
-     * @throws InvalidInterfaceException
1471
-     * @throws InvalidDataTypeException
1472
-     * @throws EE_Error
1473
-     *                           field name.
1474
-     */
1475
-    public function get_i18n_datetime($field_name, $format = '')
1476
-    {
1477
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1478
-        return date_i18n(
1479
-            $format,
1480
-            EEH_DTT_Helper::get_timestamp_with_offset(
1481
-                $this->get_raw($field_name),
1482
-                $this->_timezone
1483
-            )
1484
-        );
1485
-    }
1486
-
1487
-
1488
-    /**
1489
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1490
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1491
-     * thrown.
1492
-     *
1493
-     * @param  string $field_name The field name being checked
1494
-     * @throws ReflectionException
1495
-     * @throws InvalidArgumentException
1496
-     * @throws InvalidInterfaceException
1497
-     * @throws InvalidDataTypeException
1498
-     * @throws EE_Error
1499
-     * @return EE_Datetime_Field
1500
-     */
1501
-    protected function _get_dtt_field_settings($field_name)
1502
-    {
1503
-        $field = $this->get_model()->field_settings_for($field_name);
1504
-        // check if field is dtt
1505
-        if ($field instanceof EE_Datetime_Field) {
1506
-            return $field;
1507
-        }
1508
-        throw new EE_Error(
1509
-            sprintf(
1510
-                esc_html__(
1511
-                    'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1512
-                    'event_espresso'
1513
-                ),
1514
-                $field_name,
1515
-                self::_get_model_classname(get_class($this))
1516
-            )
1517
-        );
1518
-    }
1519
-
1520
-
1521
-
1522
-
1523
-    /**
1524
-     * NOTE ABOUT BELOW:
1525
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1526
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1527
-     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1528
-     * method and make sure you send the entire datetime value for setting.
1529
-     */
1530
-    /**
1531
-     * sets the time on a datetime property
1532
-     *
1533
-     * @access protected
1534
-     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1535
-     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1536
-     * @throws ReflectionException
1537
-     * @throws InvalidArgumentException
1538
-     * @throws InvalidInterfaceException
1539
-     * @throws InvalidDataTypeException
1540
-     * @throws EE_Error
1541
-     */
1542
-    protected function _set_time_for($time, $fieldname)
1543
-    {
1544
-        $this->_set_date_time('T', $time, $fieldname);
1545
-    }
1546
-
1547
-
1548
-    /**
1549
-     * sets the date on a datetime property
1550
-     *
1551
-     * @access protected
1552
-     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1553
-     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1554
-     * @throws ReflectionException
1555
-     * @throws InvalidArgumentException
1556
-     * @throws InvalidInterfaceException
1557
-     * @throws InvalidDataTypeException
1558
-     * @throws EE_Error
1559
-     */
1560
-    protected function _set_date_for($date, $fieldname)
1561
-    {
1562
-        $this->_set_date_time('D', $date, $fieldname);
1563
-    }
1564
-
1565
-
1566
-    /**
1567
-     * This takes care of setting a date or time independently on a given model object property. This method also
1568
-     * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1569
-     *
1570
-     * @access protected
1571
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1572
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1573
-     * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1574
-     *                                        EE_Datetime_Field property)
1575
-     * @throws ReflectionException
1576
-     * @throws InvalidArgumentException
1577
-     * @throws InvalidInterfaceException
1578
-     * @throws InvalidDataTypeException
1579
-     * @throws EE_Error
1580
-     */
1581
-    protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1582
-    {
1583
-        $field = $this->_get_dtt_field_settings($fieldname);
1584
-        $field->set_timezone($this->_timezone);
1585
-        $field->set_date_format($this->_dt_frmt);
1586
-        $field->set_time_format($this->_tm_frmt);
1587
-        switch ($what) {
1588
-            case 'T':
1589
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1590
-                    $datetime_value,
1591
-                    $this->_fields[ $fieldname ]
1592
-                );
1593
-                $this->_has_changes = true;
1594
-                break;
1595
-            case 'D':
1596
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1597
-                    $datetime_value,
1598
-                    $this->_fields[ $fieldname ]
1599
-                );
1600
-                $this->_has_changes = true;
1601
-                break;
1602
-            case 'B':
1603
-                $this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1604
-                $this->_has_changes = true;
1605
-                break;
1606
-        }
1607
-        $this->_clear_cached_property($fieldname);
1608
-    }
1609
-
1610
-
1611
-    /**
1612
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1613
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1614
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1615
-     * that could lead to some unexpected results!
1616
-     *
1617
-     * @access public
1618
-     * @param string $field_name               This is the name of the field on the object that contains the date/time
1619
-     *                                         value being returned.
1620
-     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1621
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1622
-     * @param string $prepend                  You can include something to prepend on the timestamp
1623
-     * @param string $append                   You can include something to append on the timestamp
1624
-     * @throws ReflectionException
1625
-     * @throws InvalidArgumentException
1626
-     * @throws InvalidInterfaceException
1627
-     * @throws InvalidDataTypeException
1628
-     * @throws EE_Error
1629
-     * @return string timestamp
1630
-     */
1631
-    public function display_in_my_timezone(
1632
-        $field_name,
1633
-        $callback = 'get_datetime',
1634
-        $args = null,
1635
-        $prepend = '',
1636
-        $append = ''
1637
-    ) {
1638
-        $timezone = EEH_DTT_Helper::get_timezone();
1639
-        if ($timezone === $this->_timezone) {
1640
-            return '';
1641
-        }
1642
-        $original_timezone = $this->_timezone;
1643
-        $this->set_timezone($timezone);
1644
-        $fn = (array) $field_name;
1645
-        $args = array_merge($fn, (array) $args);
1646
-        if (! method_exists($this, $callback)) {
1647
-            throw new EE_Error(
1648
-                sprintf(
1649
-                    esc_html__(
1650
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1651
-                        'event_espresso'
1652
-                    ),
1653
-                    $callback
1654
-                )
1655
-            );
1656
-        }
1657
-        $args = (array) $args;
1658
-        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1659
-        $this->set_timezone($original_timezone);
1660
-        return $return;
1661
-    }
1662
-
1663
-
1664
-    /**
1665
-     * Deletes this model object.
1666
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1667
-     * override
1668
-     * `EE_Base_Class::_delete` NOT this class.
1669
-     *
1670
-     * @return boolean | int
1671
-     * @throws ReflectionException
1672
-     * @throws InvalidArgumentException
1673
-     * @throws InvalidInterfaceException
1674
-     * @throws InvalidDataTypeException
1675
-     * @throws EE_Error
1676
-     */
1677
-    public function delete()
1678
-    {
1679
-        /**
1680
-         * Called just before the `EE_Base_Class::_delete` method call.
1681
-         * Note:
1682
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1683
-         * should be aware that `_delete` may not always result in a permanent delete.
1684
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
1685
-         * soft deletes (trash) the object and does not permanently delete it.
1686
-         *
1687
-         * @param EE_Base_Class $model_object about to be 'deleted'
1688
-         */
1689
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
1690
-        $result = $this->_delete();
1691
-        /**
1692
-         * Called just after the `EE_Base_Class::_delete` method call.
1693
-         * Note:
1694
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1695
-         * should be aware that `_delete` may not always result in a permanent delete.
1696
-         * For example `EE_Soft_Base_Class::_delete`
1697
-         * soft deletes (trash) the object and does not permanently delete it.
1698
-         *
1699
-         * @param EE_Base_Class $model_object that was just 'deleted'
1700
-         * @param boolean       $result
1701
-         */
1702
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1703
-        return $result;
1704
-    }
1705
-
1706
-
1707
-    /**
1708
-     * Calls the specific delete method for the instantiated class.
1709
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1710
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1711
-     * `EE_Base_Class::delete`
1712
-     *
1713
-     * @return bool|int
1714
-     * @throws ReflectionException
1715
-     * @throws InvalidArgumentException
1716
-     * @throws InvalidInterfaceException
1717
-     * @throws InvalidDataTypeException
1718
-     * @throws EE_Error
1719
-     */
1720
-    protected function _delete()
1721
-    {
1722
-        return $this->delete_permanently();
1723
-    }
1724
-
1725
-
1726
-    /**
1727
-     * Deletes this model object permanently from db
1728
-     * (but keep in mind related models may block the delete and return an error)
1729
-     *
1730
-     * @return bool | int
1731
-     * @throws ReflectionException
1732
-     * @throws InvalidArgumentException
1733
-     * @throws InvalidInterfaceException
1734
-     * @throws InvalidDataTypeException
1735
-     * @throws EE_Error
1736
-     */
1737
-    public function delete_permanently()
1738
-    {
1739
-        /**
1740
-         * Called just before HARD deleting a model object
1741
-         *
1742
-         * @param EE_Base_Class $model_object about to be 'deleted'
1743
-         */
1744
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1745
-        $model = $this->get_model();
1746
-        $result = $model->delete_permanently_by_ID($this->ID());
1747
-        $this->refresh_cache_of_related_objects();
1748
-        /**
1749
-         * Called just after HARD deleting a model object
1750
-         *
1751
-         * @param EE_Base_Class $model_object that was just 'deleted'
1752
-         * @param boolean       $result
1753
-         */
1754
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1755
-        return $result;
1756
-    }
1757
-
1758
-
1759
-    /**
1760
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1761
-     * related model objects
1762
-     *
1763
-     * @throws ReflectionException
1764
-     * @throws InvalidArgumentException
1765
-     * @throws InvalidInterfaceException
1766
-     * @throws InvalidDataTypeException
1767
-     * @throws EE_Error
1768
-     */
1769
-    public function refresh_cache_of_related_objects()
1770
-    {
1771
-        $model = $this->get_model();
1772
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1773
-            if (! empty($this->_model_relations[ $relation_name ])) {
1774
-                $related_objects = $this->_model_relations[ $relation_name ];
1775
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1776
-                    // this relation only stores a single model object, not an array
1777
-                    // but let's make it consistent
1778
-                    $related_objects = array($related_objects);
1779
-                }
1780
-                foreach ($related_objects as $related_object) {
1781
-                    // only refresh their cache if they're in memory
1782
-                    if ($related_object instanceof EE_Base_Class) {
1783
-                        $related_object->clear_cache(
1784
-                            $model->get_this_model_name(),
1785
-                            $this
1786
-                        );
1787
-                    }
1788
-                }
1789
-            }
1790
-        }
1791
-    }
1792
-
1793
-
1794
-    /**
1795
-     *        Saves this object to the database. An array may be supplied to set some values on this
1796
-     * object just before saving.
1797
-     *
1798
-     * @access public
1799
-     * @param array $set_cols_n_values keys are field names, values are their new values,
1800
-     *                                 if provided during the save() method (often client code will change the fields'
1801
-     *                                 values before calling save)
1802
-     * @return bool|int|string         1 on a successful update
1803
-     *                                 the ID of the new entry on insert
1804
-     *                                 0 on failure or if the model object isn't allowed to persist
1805
-     *                                 (as determined by EE_Base_Class::allow_persist())
1806
-     * @throws InvalidInterfaceException
1807
-     * @throws InvalidDataTypeException
1808
-     * @throws EE_Error
1809
-     * @throws InvalidArgumentException
1810
-     * @throws ReflectionException
1811
-     * @throws ReflectionException
1812
-     * @throws ReflectionException
1813
-     */
1814
-    public function save($set_cols_n_values = array())
1815
-    {
1816
-        $model = $this->get_model();
1817
-        /**
1818
-         * Filters the fields we're about to save on the model object
1819
-         *
1820
-         * @param array         $set_cols_n_values
1821
-         * @param EE_Base_Class $model_object
1822
-         */
1823
-        $set_cols_n_values = (array) apply_filters(
1824
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
1825
-            $set_cols_n_values,
1826
-            $this
1827
-        );
1828
-        // set attributes as provided in $set_cols_n_values
1829
-        foreach ($set_cols_n_values as $column => $value) {
1830
-            $this->set($column, $value);
1831
-        }
1832
-        // no changes ? then don't do anything
1833
-        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1834
-            return 0;
1835
-        }
1836
-        /**
1837
-         * Saving a model object.
1838
-         * Before we perform a save, this action is fired.
1839
-         *
1840
-         * @param EE_Base_Class $model_object the model object about to be saved.
1841
-         */
1842
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
1843
-        if (! $this->allow_persist()) {
1844
-            return 0;
1845
-        }
1846
-        // now get current attribute values
1847
-        $save_cols_n_values = $this->_fields;
1848
-        // if the object already has an ID, update it. Otherwise, insert it
1849
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
1850
-        // They have been
1851
-        $old_assumption_concerning_value_preparation = $model
1852
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
1853
-        $model->assume_values_already_prepared_by_model_object(true);
1854
-        // does this model have an autoincrement PK?
1855
-        if ($model->has_primary_key_field()) {
1856
-            if ($model->get_primary_key_field()->is_auto_increment()) {
1857
-                // ok check if it's set, if so: update; if not, insert
1858
-                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1859
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1860
-                } else {
1861
-                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1862
-                    $results = $model->insert($save_cols_n_values);
1863
-                    if ($results) {
1864
-                        // if successful, set the primary key
1865
-                        // but don't use the normal SET method, because it will check if
1866
-                        // an item with the same ID exists in the mapper & db, then
1867
-                        // will find it in the db (because we just added it) and THAT object
1868
-                        // will get added to the mapper before we can add this one!
1869
-                        // but if we just avoid using the SET method, all that headache can be avoided
1870
-                        $pk_field_name = $model->primary_key_name();
1871
-                        $this->_fields[ $pk_field_name ] = $results;
1872
-                        $this->_clear_cached_property($pk_field_name);
1873
-                        $model->add_to_entity_map($this);
1874
-                        $this->_update_cached_related_model_objs_fks();
1875
-                    }
1876
-                }
1877
-            } else {// PK is NOT auto-increment
1878
-                // so check if one like it already exists in the db
1879
-                if ($model->exists_by_ID($this->ID())) {
1880
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
1881
-                        throw new EE_Error(
1882
-                            sprintf(
1883
-                                esc_html__(
1884
-                                    'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1885
-                                    'event_espresso'
1886
-                                ),
1887
-                                get_class($this),
1888
-                                get_class($model) . '::instance()->add_to_entity_map()',
1889
-                                get_class($model) . '::instance()->get_one_by_ID()',
1890
-                                '<br />'
1891
-                            )
1892
-                        );
1893
-                    }
1894
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1895
-                } else {
1896
-                    $results = $model->insert($save_cols_n_values);
1897
-                    $this->_update_cached_related_model_objs_fks();
1898
-                }
1899
-            }
1900
-        } else {// there is NO primary key
1901
-            $already_in_db = false;
1902
-            foreach ($model->unique_indexes() as $index) {
1903
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1904
-                if ($model->exists(array($uniqueness_where_params))) {
1905
-                    $already_in_db = true;
1906
-                }
1907
-            }
1908
-            if ($already_in_db) {
1909
-                $combined_pk_fields_n_values = array_intersect_key(
1910
-                    $save_cols_n_values,
1911
-                    $model->get_combined_primary_key_fields()
1912
-                );
1913
-                $results = $model->update(
1914
-                    $save_cols_n_values,
1915
-                    $combined_pk_fields_n_values
1916
-                );
1917
-            } else {
1918
-                $results = $model->insert($save_cols_n_values);
1919
-            }
1920
-        }
1921
-        // restore the old assumption about values being prepared by the model object
1922
-        $model->assume_values_already_prepared_by_model_object(
1923
-            $old_assumption_concerning_value_preparation
1924
-        );
1925
-        /**
1926
-         * After saving the model object this action is called
1927
-         *
1928
-         * @param EE_Base_Class $model_object which was just saved
1929
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1930
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1931
-         */
1932
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1933
-        $this->_has_changes = false;
1934
-        return $results;
1935
-    }
1936
-
1937
-
1938
-    /**
1939
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1940
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1941
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1942
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1943
-     * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1944
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1945
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1946
-     *
1947
-     * @return void
1948
-     * @throws ReflectionException
1949
-     * @throws InvalidArgumentException
1950
-     * @throws InvalidInterfaceException
1951
-     * @throws InvalidDataTypeException
1952
-     * @throws EE_Error
1953
-     */
1954
-    protected function _update_cached_related_model_objs_fks()
1955
-    {
1956
-        $model = $this->get_model();
1957
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1958
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1959
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1960
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1961
-                        $model->get_this_model_name()
1962
-                    );
1963
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1964
-                    if ($related_model_obj_in_cache->ID()) {
1965
-                        $related_model_obj_in_cache->save();
1966
-                    }
1967
-                }
1968
-            }
1969
-        }
1970
-    }
1971
-
1972
-
1973
-    /**
1974
-     * Saves this model object and its NEW cached relations to the database.
1975
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1976
-     * In order for that to work, we would need to mark model objects as dirty/clean...
1977
-     * because otherwise, there's a potential for infinite looping of saving
1978
-     * Saves the cached related model objects, and ensures the relation between them
1979
-     * and this object and properly setup
1980
-     *
1981
-     * @return int ID of new model object on save; 0 on failure+
1982
-     * @throws ReflectionException
1983
-     * @throws InvalidArgumentException
1984
-     * @throws InvalidInterfaceException
1985
-     * @throws InvalidDataTypeException
1986
-     * @throws EE_Error
1987
-     */
1988
-    public function save_new_cached_related_model_objs()
1989
-    {
1990
-        // make sure this has been saved
1991
-        if (! $this->ID()) {
1992
-            $id = $this->save();
1993
-        } else {
1994
-            $id = $this->ID();
1995
-        }
1996
-        // now save all the NEW cached model objects  (ie they don't exist in the DB)
1997
-        foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1998
-            if ($this->_model_relations[ $relationName ]) {
1999
-                // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2000
-                // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2001
-                /* @var $related_model_obj EE_Base_Class */
2002
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
2003
-                    // add a relation to that relation type (which saves the appropriate thing in the process)
2004
-                    // but ONLY if it DOES NOT exist in the DB
2005
-                    $related_model_obj = $this->_model_relations[ $relationName ];
2006
-                    // if( ! $related_model_obj->ID()){
2007
-                    $this->_add_relation_to($related_model_obj, $relationName);
2008
-                    $related_model_obj->save_new_cached_related_model_objs();
2009
-                    // }
2010
-                } else {
2011
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2012
-                        // add a relation to that relation type (which saves the appropriate thing in the process)
2013
-                        // but ONLY if it DOES NOT exist in the DB
2014
-                        // if( ! $related_model_obj->ID()){
2015
-                        $this->_add_relation_to($related_model_obj, $relationName);
2016
-                        $related_model_obj->save_new_cached_related_model_objs();
2017
-                        // }
2018
-                    }
2019
-                }
2020
-            }
2021
-        }
2022
-        return $id;
2023
-    }
2024
-
2025
-
2026
-    /**
2027
-     * for getting a model while instantiated.
2028
-     *
2029
-     * @return EEM_Base | EEM_CPT_Base
2030
-     * @throws ReflectionException
2031
-     * @throws InvalidArgumentException
2032
-     * @throws InvalidInterfaceException
2033
-     * @throws InvalidDataTypeException
2034
-     * @throws EE_Error
2035
-     */
2036
-    public function get_model()
2037
-    {
2038
-        if (! $this->_model) {
2039
-            $modelName = self::_get_model_classname(get_class($this));
2040
-            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2041
-        } else {
2042
-            $this->_model->set_timezone($this->_timezone);
2043
-        }
2044
-        return $this->_model;
2045
-    }
2046
-
2047
-
2048
-    /**
2049
-     * @param $props_n_values
2050
-     * @param $classname
2051
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2052
-     * @throws ReflectionException
2053
-     * @throws InvalidArgumentException
2054
-     * @throws InvalidInterfaceException
2055
-     * @throws InvalidDataTypeException
2056
-     * @throws EE_Error
2057
-     */
2058
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2059
-    {
2060
-        // TODO: will not work for Term_Relationships because they have no PK!
2061
-        $primary_id_ref = self::_get_primary_key_name($classname);
2062
-        if (
2063
-            array_key_exists($primary_id_ref, $props_n_values)
2064
-            && ! empty($props_n_values[ $primary_id_ref ])
2065
-        ) {
2066
-            $id = $props_n_values[ $primary_id_ref ];
2067
-            return self::_get_model($classname)->get_from_entity_map($id);
2068
-        }
2069
-        return false;
2070
-    }
2071
-
2072
-
2073
-    /**
2074
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2075
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2076
-     * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2077
-     * we return false.
2078
-     *
2079
-     * @param  array  $props_n_values   incoming array of properties and their values
2080
-     * @param  string $classname        the classname of the child class
2081
-     * @param null    $timezone
2082
-     * @param array   $date_formats     incoming date_formats in an array where the first value is the
2083
-     *                                  date_format and the second value is the time format
2084
-     * @return mixed (EE_Base_Class|bool)
2085
-     * @throws InvalidArgumentException
2086
-     * @throws InvalidInterfaceException
2087
-     * @throws InvalidDataTypeException
2088
-     * @throws EE_Error
2089
-     * @throws ReflectionException
2090
-     * @throws ReflectionException
2091
-     * @throws ReflectionException
2092
-     */
2093
-    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2094
-    {
2095
-        $existing = null;
2096
-        $model = self::_get_model($classname, $timezone);
2097
-        if ($model->has_primary_key_field()) {
2098
-            $primary_id_ref = self::_get_primary_key_name($classname);
2099
-            if (
2100
-                array_key_exists($primary_id_ref, $props_n_values)
2101
-                && ! empty($props_n_values[ $primary_id_ref ])
2102
-            ) {
2103
-                $existing = $model->get_one_by_ID(
2104
-                    $props_n_values[ $primary_id_ref ]
2105
-                );
2106
-            }
2107
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2108
-            // no primary key on this model, but there's still a matching item in the DB
2109
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2110
-                self::_get_model($classname, $timezone)
2111
-                    ->get_index_primary_key_string($props_n_values)
2112
-            );
2113
-        }
2114
-        if ($existing) {
2115
-            // set date formats if present before setting values
2116
-            if (! empty($date_formats) && is_array($date_formats)) {
2117
-                $existing->set_date_format($date_formats[0]);
2118
-                $existing->set_time_format($date_formats[1]);
2119
-            } else {
2120
-                // set default formats for date and time
2121
-                $existing->set_date_format(get_option('date_format'));
2122
-                $existing->set_time_format(get_option('time_format'));
2123
-            }
2124
-            foreach ($props_n_values as $property => $field_value) {
2125
-                $existing->set($property, $field_value);
2126
-            }
2127
-            return $existing;
2128
-        }
2129
-        return false;
2130
-    }
2131
-
2132
-
2133
-    /**
2134
-     * Gets the EEM_*_Model for this class
2135
-     *
2136
-     * @access public now, as this is more convenient
2137
-     * @param      $classname
2138
-     * @param null $timezone
2139
-     * @throws ReflectionException
2140
-     * @throws InvalidArgumentException
2141
-     * @throws InvalidInterfaceException
2142
-     * @throws InvalidDataTypeException
2143
-     * @throws EE_Error
2144
-     * @return EEM_Base
2145
-     */
2146
-    protected static function _get_model($classname, $timezone = null)
2147
-    {
2148
-        // find model for this class
2149
-        if (! $classname) {
2150
-            throw new EE_Error(
2151
-                sprintf(
2152
-                    esc_html__(
2153
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2154
-                        'event_espresso'
2155
-                    ),
2156
-                    $classname
2157
-                )
2158
-            );
2159
-        }
2160
-        $modelName = self::_get_model_classname($classname);
2161
-        return self::_get_model_instance_with_name($modelName, $timezone);
2162
-    }
2163
-
2164
-
2165
-    /**
2166
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2167
-     *
2168
-     * @param string $model_classname
2169
-     * @param null   $timezone
2170
-     * @return EEM_Base
2171
-     * @throws ReflectionException
2172
-     * @throws InvalidArgumentException
2173
-     * @throws InvalidInterfaceException
2174
-     * @throws InvalidDataTypeException
2175
-     * @throws EE_Error
2176
-     */
2177
-    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2178
-    {
2179
-        $model_classname = str_replace('EEM_', '', $model_classname);
2180
-        $model = EE_Registry::instance()->load_model($model_classname);
2181
-        $model->set_timezone($timezone);
2182
-        return $model;
2183
-    }
2184
-
2185
-
2186
-    /**
2187
-     * If a model name is provided (eg Registration), gets the model classname for that model.
2188
-     * Also works if a model class's classname is provided (eg EE_Registration).
2189
-     *
2190
-     * @param null $model_name
2191
-     * @return string like EEM_Attendee
2192
-     */
2193
-    private static function _get_model_classname($model_name = null)
2194
-    {
2195
-        if (strpos($model_name, 'EE_') === 0) {
2196
-            $model_classname = str_replace('EE_', 'EEM_', $model_name);
2197
-        } else {
2198
-            $model_classname = 'EEM_' . $model_name;
2199
-        }
2200
-        return $model_classname;
2201
-    }
2202
-
2203
-
2204
-    /**
2205
-     * returns the name of the primary key attribute
2206
-     *
2207
-     * @param null $classname
2208
-     * @throws ReflectionException
2209
-     * @throws InvalidArgumentException
2210
-     * @throws InvalidInterfaceException
2211
-     * @throws InvalidDataTypeException
2212
-     * @throws EE_Error
2213
-     * @return string
2214
-     */
2215
-    protected static function _get_primary_key_name($classname = null)
2216
-    {
2217
-        if (! $classname) {
2218
-            throw new EE_Error(
2219
-                sprintf(
2220
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2221
-                    $classname
2222
-                )
2223
-            );
2224
-        }
2225
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
2226
-    }
2227
-
2228
-
2229
-    /**
2230
-     * Gets the value of the primary key.
2231
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
2232
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2233
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2234
-     *
2235
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2236
-     * @throws ReflectionException
2237
-     * @throws InvalidArgumentException
2238
-     * @throws InvalidInterfaceException
2239
-     * @throws InvalidDataTypeException
2240
-     * @throws EE_Error
2241
-     */
2242
-    public function ID()
2243
-    {
2244
-        $model = $this->get_model();
2245
-        // now that we know the name of the variable, use a variable variable to get its value and return its
2246
-        if ($model->has_primary_key_field()) {
2247
-            return $this->_fields[ $model->primary_key_name() ];
2248
-        }
2249
-        return $model->get_index_primary_key_string($this->_fields);
2250
-    }
2251
-
2252
-
2253
-    /**
2254
-     * @param EE_Base_Class|int|string $otherModelObjectOrID
2255
-     * @param string                   $relationName
2256
-     * @return bool
2257
-     * @throws EE_Error
2258
-     * @throws ReflectionException
2259
-     * @since   $VID:$
2260
-     */
2261
-    public function hasRelation($otherModelObjectOrID, string $relationName): bool
2262
-    {
2263
-        $other_model = self::_get_model_instance_with_name(
2264
-            self::_get_model_classname($relationName),
2265
-            $this->_timezone
2266
-        );
2267
-        $primary_key = $other_model->primary_key_name();
2268
-        /** @var EE_Base_Class $otherModelObject */
2269
-        $otherModelObject = $other_model->ensure_is_obj($otherModelObjectOrID, $relationName);
2270
-        return $this->count_related($relationName, [[$primary_key => $otherModelObject->ID()]]) > 0;
2271
-    }
2272
-
2273
-
2274
-    /**
2275
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2276
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2277
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2278
-     *
2279
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2280
-     * @param string $relationName                     eg 'Events','Question',etc.
2281
-     *                                                 an attendee to a group, you also want to specify which role they
2282
-     *                                                 will have in that group. So you would use this parameter to
2283
-     *                                                 specify array('role-column-name'=>'role-id')
2284
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2285
-     *                                                 allow you to further constrict the relation to being added.
2286
-     *                                                 However, keep in mind that the columns (keys) given must match a
2287
-     *                                                 column on the JOIN table and currently only the HABTM models
2288
-     *                                                 accept these additional conditions.  Also remember that if an
2289
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2290
-     *                                                 NEW row is created in the join table.
2291
-     * @param null   $cache_id
2292
-     * @throws ReflectionException
2293
-     * @throws InvalidArgumentException
2294
-     * @throws InvalidInterfaceException
2295
-     * @throws InvalidDataTypeException
2296
-     * @throws EE_Error
2297
-     * @return EE_Base_Class the object the relation was added to
2298
-     */
2299
-    public function _add_relation_to(
2300
-        $otherObjectModelObjectOrID,
2301
-        $relationName,
2302
-        $extra_join_model_fields_n_values = array(),
2303
-        $cache_id = null
2304
-    ) {
2305
-        $model = $this->get_model();
2306
-        // if this thing exists in the DB, save the relation to the DB
2307
-        if ($this->ID()) {
2308
-            $otherObject = $model->add_relationship_to(
2309
-                $this,
2310
-                $otherObjectModelObjectOrID,
2311
-                $relationName,
2312
-                $extra_join_model_fields_n_values
2313
-            );
2314
-            // clear cache so future get_many_related and get_first_related() return new results.
2315
-            $this->clear_cache($relationName, $otherObject, true);
2316
-            if ($otherObject instanceof EE_Base_Class) {
2317
-                $otherObject->clear_cache($model->get_this_model_name(), $this);
2318
-            }
2319
-        } else {
2320
-            // this thing doesn't exist in the DB,  so just cache it
2321
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2322
-                throw new EE_Error(
2323
-                    sprintf(
2324
-                        esc_html__(
2325
-                            'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2326
-                            'event_espresso'
2327
-                        ),
2328
-                        $otherObjectModelObjectOrID,
2329
-                        get_class($this)
2330
-                    )
2331
-                );
2332
-            }
2333
-            $otherObject = $otherObjectModelObjectOrID;
2334
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2335
-        }
2336
-        if ($otherObject instanceof EE_Base_Class) {
2337
-            // fix the reciprocal relation too
2338
-            if ($otherObject->ID()) {
2339
-                // its saved so assumed relations exist in the DB, so we can just
2340
-                // clear the cache so future queries use the updated info in the DB
2341
-                $otherObject->clear_cache(
2342
-                    $model->get_this_model_name(),
2343
-                    null,
2344
-                    true
2345
-                );
2346
-            } else {
2347
-                // it's not saved, so it caches relations like this
2348
-                $otherObject->cache($model->get_this_model_name(), $this);
2349
-            }
2350
-        }
2351
-        return $otherObject;
2352
-    }
2353
-
2354
-
2355
-    /**
2356
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2357
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2358
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2359
-     * from the cache
2360
-     *
2361
-     * @param mixed  $otherObjectModelObjectOrID
2362
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2363
-     *                to the DB yet
2364
-     * @param string $relationName
2365
-     * @param array  $where_query
2366
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2367
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2368
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2369
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2370
-     *                deleted.
2371
-     * @return EE_Base_Class the relation was removed from
2372
-     * @throws ReflectionException
2373
-     * @throws InvalidArgumentException
2374
-     * @throws InvalidInterfaceException
2375
-     * @throws InvalidDataTypeException
2376
-     * @throws EE_Error
2377
-     */
2378
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2379
-    {
2380
-        if ($this->ID()) {
2381
-            // if this exists in the DB, save the relation change to the DB too
2382
-            $otherObject = $this->get_model()->remove_relationship_to(
2383
-                $this,
2384
-                $otherObjectModelObjectOrID,
2385
-                $relationName,
2386
-                $where_query
2387
-            );
2388
-            $this->clear_cache(
2389
-                $relationName,
2390
-                $otherObject
2391
-            );
2392
-        } else {
2393
-            // this doesn't exist in the DB, just remove it from the cache
2394
-            $otherObject = $this->clear_cache(
2395
-                $relationName,
2396
-                $otherObjectModelObjectOrID
2397
-            );
2398
-        }
2399
-        if ($otherObject instanceof EE_Base_Class) {
2400
-            $otherObject->clear_cache(
2401
-                $this->get_model()->get_this_model_name(),
2402
-                $this
2403
-            );
2404
-        }
2405
-        return $otherObject;
2406
-    }
2407
-
2408
-
2409
-    /**
2410
-     * Removes ALL the related things for the $relationName.
2411
-     *
2412
-     * @param string $relationName
2413
-     * @param array  $where_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2414
-     * @return EE_Base_Class
2415
-     * @throws ReflectionException
2416
-     * @throws InvalidArgumentException
2417
-     * @throws InvalidInterfaceException
2418
-     * @throws InvalidDataTypeException
2419
-     * @throws EE_Error
2420
-     */
2421
-    public function _remove_relations($relationName, $where_query_params = array())
2422
-    {
2423
-        if ($this->ID()) {
2424
-            // if this exists in the DB, save the relation change to the DB too
2425
-            $otherObjects = $this->get_model()->remove_relations(
2426
-                $this,
2427
-                $relationName,
2428
-                $where_query_params
2429
-            );
2430
-            $this->clear_cache(
2431
-                $relationName,
2432
-                null,
2433
-                true
2434
-            );
2435
-        } else {
2436
-            // this doesn't exist in the DB, just remove it from the cache
2437
-            $otherObjects = $this->clear_cache(
2438
-                $relationName,
2439
-                null,
2440
-                true
2441
-            );
2442
-        }
2443
-        if (is_array($otherObjects)) {
2444
-            foreach ($otherObjects as $otherObject) {
2445
-                $otherObject->clear_cache(
2446
-                    $this->get_model()->get_this_model_name(),
2447
-                    $this
2448
-                );
2449
-            }
2450
-        }
2451
-        return $otherObjects;
2452
-    }
2453
-
2454
-
2455
-    /**
2456
-     * Gets all the related model objects of the specified type. Eg, if the current class if
2457
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2458
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2459
-     * because we want to get even deleted items etc.
2460
-     *
2461
-     * @param string $relationName key in the model's _model_relations array
2462
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2463
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2464
-     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2465
-     *                             results if you want IDs
2466
-     * @throws ReflectionException
2467
-     * @throws InvalidArgumentException
2468
-     * @throws InvalidInterfaceException
2469
-     * @throws InvalidDataTypeException
2470
-     * @throws EE_Error
2471
-     */
2472
-    public function get_many_related($relationName, $query_params = array())
2473
-    {
2474
-        if ($this->ID()) {
2475
-            // this exists in the DB, so get the related things from either the cache or the DB
2476
-            // if there are query parameters, forget about caching the related model objects.
2477
-            if ($query_params) {
2478
-                $related_model_objects = $this->get_model()->get_all_related(
2479
-                    $this,
2480
-                    $relationName,
2481
-                    $query_params
2482
-                );
2483
-            } else {
2484
-                // did we already cache the result of this query?
2485
-                $cached_results = $this->get_all_from_cache($relationName);
2486
-                if (! $cached_results) {
2487
-                    $related_model_objects = $this->get_model()->get_all_related(
2488
-                        $this,
2489
-                        $relationName,
2490
-                        $query_params
2491
-                    );
2492
-                    // if no query parameters were passed, then we got all the related model objects
2493
-                    // for that relation. We can cache them then.
2494
-                    foreach ($related_model_objects as $related_model_object) {
2495
-                        $this->cache($relationName, $related_model_object);
2496
-                    }
2497
-                } else {
2498
-                    $related_model_objects = $cached_results;
2499
-                }
2500
-            }
2501
-        } else {
2502
-            // this doesn't exist in the DB, so just get the related things from the cache
2503
-            $related_model_objects = $this->get_all_from_cache($relationName);
2504
-        }
2505
-        return $related_model_objects;
2506
-    }
2507
-
2508
-
2509
-    /**
2510
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2511
-     * unless otherwise specified in the $query_params
2512
-     *
2513
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2514
-     * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2515
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2516
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2517
-     *                               that by the setting $distinct to TRUE;
2518
-     * @return int
2519
-     * @throws ReflectionException
2520
-     * @throws InvalidArgumentException
2521
-     * @throws InvalidInterfaceException
2522
-     * @throws InvalidDataTypeException
2523
-     * @throws EE_Error
2524
-     */
2525
-    public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2526
-    {
2527
-        return $this->get_model()->count_related(
2528
-            $this,
2529
-            $relation_name,
2530
-            $query_params,
2531
-            $field_to_count,
2532
-            $distinct
2533
-        );
2534
-    }
2535
-
2536
-
2537
-    /**
2538
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2539
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2540
-     *
2541
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2542
-     * @param array  $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2543
-     * @param string $field_to_sum  name of field to count by.
2544
-     *                              By default, uses primary key
2545
-     *                              (which doesn't make much sense, so you should probably change it)
2546
-     * @return int
2547
-     * @throws ReflectionException
2548
-     * @throws InvalidArgumentException
2549
-     * @throws InvalidInterfaceException
2550
-     * @throws InvalidDataTypeException
2551
-     * @throws EE_Error
2552
-     */
2553
-    public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2554
-    {
2555
-        return $this->get_model()->sum_related(
2556
-            $this,
2557
-            $relation_name,
2558
-            $query_params,
2559
-            $field_to_sum
2560
-        );
2561
-    }
2562
-
2563
-
2564
-    /**
2565
-     * Gets the first (ie, one) related model object of the specified type.
2566
-     *
2567
-     * @param string $relationName key in the model's _model_relations array
2568
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2569
-     * @return EE_Base_Class (not an array, a single object)
2570
-     * @throws ReflectionException
2571
-     * @throws InvalidArgumentException
2572
-     * @throws InvalidInterfaceException
2573
-     * @throws InvalidDataTypeException
2574
-     * @throws EE_Error
2575
-     */
2576
-    public function get_first_related($relationName, $query_params = array())
2577
-    {
2578
-        $model = $this->get_model();
2579
-        if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2580
-            // if they've provided some query parameters, don't bother trying to cache the result
2581
-            // also make sure we're not caching the result of get_first_related
2582
-            // on a relation which should have an array of objects (because the cache might have an array of objects)
2583
-            if (
2584
-                $query_params
2585
-                || ! $model->related_settings_for($relationName)
2586
-                     instanceof
2587
-                     EE_Belongs_To_Relation
2588
-            ) {
2589
-                $related_model_object = $model->get_first_related(
2590
-                    $this,
2591
-                    $relationName,
2592
-                    $query_params
2593
-                );
2594
-            } else {
2595
-                // first, check if we've already cached the result of this query
2596
-                $cached_result = $this->get_one_from_cache($relationName);
2597
-                if (! $cached_result) {
2598
-                    $related_model_object = $model->get_first_related(
2599
-                        $this,
2600
-                        $relationName,
2601
-                        $query_params
2602
-                    );
2603
-                    $this->cache($relationName, $related_model_object);
2604
-                } else {
2605
-                    $related_model_object = $cached_result;
2606
-                }
2607
-            }
2608
-        } else {
2609
-            $related_model_object = null;
2610
-            // this doesn't exist in the Db,
2611
-            // but maybe the relation is of type belongs to, and so the related thing might
2612
-            if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2613
-                $related_model_object = $model->get_first_related(
2614
-                    $this,
2615
-                    $relationName,
2616
-                    $query_params
2617
-                );
2618
-            }
2619
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2620
-            // just get what's cached on this object
2621
-            if (! $related_model_object) {
2622
-                $related_model_object = $this->get_one_from_cache($relationName);
2623
-            }
2624
-        }
2625
-        return $related_model_object;
2626
-    }
2627
-
2628
-
2629
-    /**
2630
-     * Does a delete on all related objects of type $relationName and removes
2631
-     * the current model object's relation to them. If they can't be deleted (because
2632
-     * of blocking related model objects) does nothing. If the related model objects are
2633
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2634
-     * If this model object doesn't exist yet in the DB, just removes its related things
2635
-     *
2636
-     * @param string $relationName
2637
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2638
-     * @return int how many deleted
2639
-     * @throws ReflectionException
2640
-     * @throws InvalidArgumentException
2641
-     * @throws InvalidInterfaceException
2642
-     * @throws InvalidDataTypeException
2643
-     * @throws EE_Error
2644
-     */
2645
-    public function delete_related($relationName, $query_params = array())
2646
-    {
2647
-        if ($this->ID()) {
2648
-            $count = $this->get_model()->delete_related(
2649
-                $this,
2650
-                $relationName,
2651
-                $query_params
2652
-            );
2653
-        } else {
2654
-            $count = count($this->get_all_from_cache($relationName));
2655
-            $this->clear_cache($relationName, null, true);
2656
-        }
2657
-        return $count;
2658
-    }
2659
-
2660
-
2661
-    /**
2662
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2663
-     * the current model object's relation to them. If they can't be deleted (because
2664
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2665
-     * If the related thing isn't a soft-deletable model object, this function is identical
2666
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2667
-     *
2668
-     * @param string $relationName
2669
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2670
-     * @return int how many deleted (including those soft deleted)
2671
-     * @throws ReflectionException
2672
-     * @throws InvalidArgumentException
2673
-     * @throws InvalidInterfaceException
2674
-     * @throws InvalidDataTypeException
2675
-     * @throws EE_Error
2676
-     */
2677
-    public function delete_related_permanently($relationName, $query_params = array())
2678
-    {
2679
-        if ($this->ID()) {
2680
-            $count = $this->get_model()->delete_related_permanently(
2681
-                $this,
2682
-                $relationName,
2683
-                $query_params
2684
-            );
2685
-        } else {
2686
-            $count = count($this->get_all_from_cache($relationName));
2687
-        }
2688
-        $this->clear_cache($relationName, null, true);
2689
-        return $count;
2690
-    }
2691
-
2692
-
2693
-    /**
2694
-     * is_set
2695
-     * Just a simple utility function children can use for checking if property exists
2696
-     *
2697
-     * @access  public
2698
-     * @param  string $field_name property to check
2699
-     * @return bool                              TRUE if existing,FALSE if not.
2700
-     */
2701
-    public function is_set($field_name)
2702
-    {
2703
-        return isset($this->_fields[ $field_name ]);
2704
-    }
2705
-
2706
-
2707
-    /**
2708
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2709
-     * EE_Error exception if they don't
2710
-     *
2711
-     * @param  mixed (string|array) $properties properties to check
2712
-     * @throws EE_Error
2713
-     * @return bool                              TRUE if existing, throw EE_Error if not.
2714
-     */
2715
-    protected function _property_exists($properties)
2716
-    {
2717
-        foreach ((array) $properties as $property_name) {
2718
-            // first make sure this property exists
2719
-            if (! $this->_fields[ $property_name ]) {
2720
-                throw new EE_Error(
2721
-                    sprintf(
2722
-                        esc_html__(
2723
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2724
-                            'event_espresso'
2725
-                        ),
2726
-                        $property_name
2727
-                    )
2728
-                );
2729
-            }
2730
-        }
2731
-        return true;
2732
-    }
2733
-
2734
-
2735
-    /**
2736
-     * This simply returns an array of model fields for this object
2737
-     *
2738
-     * @return array
2739
-     * @throws ReflectionException
2740
-     * @throws InvalidArgumentException
2741
-     * @throws InvalidInterfaceException
2742
-     * @throws InvalidDataTypeException
2743
-     * @throws EE_Error
2744
-     */
2745
-    public function model_field_array()
2746
-    {
2747
-        $fields = $this->get_model()->field_settings(false);
2748
-        $properties = array();
2749
-        // remove prepended underscore
2750
-        foreach ($fields as $field_name => $settings) {
2751
-            $properties[ $field_name ] = $this->get($field_name);
2752
-        }
2753
-        return $properties;
2754
-    }
2755
-
2756
-
2757
-    /**
2758
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2759
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2760
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2761
-     * Instead of requiring a plugin to extend the EE_Base_Class
2762
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2763
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2764
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2765
-     * and accepts 2 arguments: the object on which the function was called,
2766
-     * and an array of the original arguments passed to the function.
2767
-     * Whatever their callback function returns will be returned by this function.
2768
-     * Example: in functions.php (or in a plugin):
2769
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2770
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2771
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2772
-     *          return $previousReturnValue.$returnString;
2773
-     *      }
2774
-     * require('EE_Answer.class.php');
2775
-     * echo EE_Answer::new_instance(['REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'])
2776
-     *      ->my_callback('monkeys',100);
2777
-     * // will output "you called my_callback! and passed args:monkeys,100"
2778
-     *
2779
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2780
-     * @param array  $args       array of original arguments passed to the function
2781
-     * @throws EE_Error
2782
-     * @return mixed whatever the plugin which calls add_filter decides
2783
-     */
2784
-    public function __call($methodName, $args)
2785
-    {
2786
-        $className = get_class($this);
2787
-        $tagName = "FHEE__{$className}__{$methodName}";
2788
-        if (! has_filter($tagName)) {
2789
-            throw new EE_Error(
2790
-                sprintf(
2791
-                    esc_html__(
2792
-                        "Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2793
-                        'event_espresso'
2794
-                    ),
2795
-                    $methodName,
2796
-                    $className,
2797
-                    $tagName
2798
-                )
2799
-            );
2800
-        }
2801
-        return apply_filters($tagName, null, $this, $args);
2802
-    }
2803
-
2804
-
2805
-    /**
2806
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2807
-     * A $previous_value can be specified in case there are many meta rows with the same key
2808
-     *
2809
-     * @param string $meta_key
2810
-     * @param mixed  $meta_value
2811
-     * @param mixed  $previous_value
2812
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2813
-     *                  NOTE: if the values haven't changed, returns 0
2814
-     * @throws InvalidArgumentException
2815
-     * @throws InvalidInterfaceException
2816
-     * @throws InvalidDataTypeException
2817
-     * @throws EE_Error
2818
-     * @throws ReflectionException
2819
-     */
2820
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2821
-    {
2822
-        $query_params = array(
2823
-            array(
2824
-                'EXM_key'  => $meta_key,
2825
-                'OBJ_ID'   => $this->ID(),
2826
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2827
-            ),
2828
-        );
2829
-        if ($previous_value !== null) {
2830
-            $query_params[0]['EXM_value'] = $meta_value;
2831
-        }
2832
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2833
-        if (! $existing_rows_like_that) {
2834
-            return $this->add_extra_meta($meta_key, $meta_value);
2835
-        }
2836
-        foreach ($existing_rows_like_that as $existing_row) {
2837
-            $existing_row->save(array('EXM_value' => $meta_value));
2838
-        }
2839
-        return count($existing_rows_like_that);
2840
-    }
2841
-
2842
-
2843
-    /**
2844
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2845
-     * no other extra meta for this model object have the same key. Returns TRUE if the
2846
-     * extra meta row was entered, false if not
2847
-     *
2848
-     * @param string  $meta_key
2849
-     * @param mixed   $meta_value
2850
-     * @param boolean $unique
2851
-     * @return boolean
2852
-     * @throws InvalidArgumentException
2853
-     * @throws InvalidInterfaceException
2854
-     * @throws InvalidDataTypeException
2855
-     * @throws EE_Error
2856
-     * @throws ReflectionException
2857
-     * @throws ReflectionException
2858
-     */
2859
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2860
-    {
2861
-        if ($unique) {
2862
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2863
-                array(
2864
-                    array(
2865
-                        'EXM_key'  => $meta_key,
2866
-                        'OBJ_ID'   => $this->ID(),
2867
-                        'EXM_type' => $this->get_model()->get_this_model_name(),
2868
-                    ),
2869
-                )
2870
-            );
2871
-            if ($existing_extra_meta) {
2872
-                return false;
2873
-            }
2874
-        }
2875
-        $new_extra_meta = EE_Extra_Meta::new_instance(
2876
-            array(
2877
-                'EXM_key'   => $meta_key,
2878
-                'EXM_value' => $meta_value,
2879
-                'OBJ_ID'    => $this->ID(),
2880
-                'EXM_type'  => $this->get_model()->get_this_model_name(),
2881
-            )
2882
-        );
2883
-        $new_extra_meta->save();
2884
-        return true;
2885
-    }
2886
-
2887
-
2888
-    /**
2889
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2890
-     * is specified, only deletes extra meta records with that value.
2891
-     *
2892
-     * @param string $meta_key
2893
-     * @param mixed  $meta_value
2894
-     * @return int number of extra meta rows deleted
2895
-     * @throws InvalidArgumentException
2896
-     * @throws InvalidInterfaceException
2897
-     * @throws InvalidDataTypeException
2898
-     * @throws EE_Error
2899
-     * @throws ReflectionException
2900
-     */
2901
-    public function delete_extra_meta($meta_key, $meta_value = null)
2902
-    {
2903
-        $query_params = array(
2904
-            array(
2905
-                'EXM_key'  => $meta_key,
2906
-                'OBJ_ID'   => $this->ID(),
2907
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2908
-            ),
2909
-        );
2910
-        if ($meta_value !== null) {
2911
-            $query_params[0]['EXM_value'] = $meta_value;
2912
-        }
2913
-        return EEM_Extra_Meta::instance()->delete($query_params);
2914
-    }
2915
-
2916
-
2917
-    /**
2918
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2919
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2920
-     * You can specify $default is case you haven't found the extra meta
2921
-     *
2922
-     * @param string  $meta_key
2923
-     * @param boolean $single
2924
-     * @param mixed   $default if we don't find anything, what should we return?
2925
-     * @return mixed single value if $single; array if ! $single
2926
-     * @throws ReflectionException
2927
-     * @throws InvalidArgumentException
2928
-     * @throws InvalidInterfaceException
2929
-     * @throws InvalidDataTypeException
2930
-     * @throws EE_Error
2931
-     */
2932
-    public function get_extra_meta($meta_key, $single = false, $default = null)
2933
-    {
2934
-        if ($single) {
2935
-            $result = $this->get_first_related(
2936
-                'Extra_Meta',
2937
-                array(array('EXM_key' => $meta_key))
2938
-            );
2939
-            if ($result instanceof EE_Extra_Meta) {
2940
-                return $result->value();
2941
-            }
2942
-        } else {
2943
-            $results = $this->get_many_related(
2944
-                'Extra_Meta',
2945
-                array(array('EXM_key' => $meta_key))
2946
-            );
2947
-            if ($results) {
2948
-                $values = array();
2949
-                foreach ($results as $result) {
2950
-                    if ($result instanceof EE_Extra_Meta) {
2951
-                        $values[ $result->ID() ] = $result->value();
2952
-                    }
2953
-                }
2954
-                return $values;
2955
-            }
2956
-        }
2957
-        // if nothing discovered yet return default.
2958
-        return apply_filters(
2959
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2960
-            $default,
2961
-            $meta_key,
2962
-            $single,
2963
-            $this
2964
-        );
2965
-    }
2966
-
2967
-
2968
-    /**
2969
-     * Returns a simple array of all the extra meta associated with this model object.
2970
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2971
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2972
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2973
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2974
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2975
-     * finally the extra meta's value as each sub-value. (eg
2976
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2977
-     *
2978
-     * @param boolean $one_of_each_key
2979
-     * @return array
2980
-     * @throws ReflectionException
2981
-     * @throws InvalidArgumentException
2982
-     * @throws InvalidInterfaceException
2983
-     * @throws InvalidDataTypeException
2984
-     * @throws EE_Error
2985
-     */
2986
-    public function all_extra_meta_array($one_of_each_key = true)
2987
-    {
2988
-        $return_array = array();
2989
-        if ($one_of_each_key) {
2990
-            $extra_meta_objs = $this->get_many_related(
2991
-                'Extra_Meta',
2992
-                array('group_by' => 'EXM_key')
2993
-            );
2994
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2995
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2996
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2997
-                }
2998
-            }
2999
-        } else {
3000
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
3001
-            foreach ($extra_meta_objs as $extra_meta_obj) {
3002
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
3003
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
3004
-                        $return_array[ $extra_meta_obj->key() ] = array();
3005
-                    }
3006
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
3007
-                }
3008
-            }
3009
-        }
3010
-        return $return_array;
3011
-    }
3012
-
3013
-
3014
-    /**
3015
-     * Gets a pretty nice displayable nice for this model object. Often overridden
3016
-     *
3017
-     * @return string
3018
-     * @throws ReflectionException
3019
-     * @throws InvalidArgumentException
3020
-     * @throws InvalidInterfaceException
3021
-     * @throws InvalidDataTypeException
3022
-     * @throws EE_Error
3023
-     */
3024
-    public function name()
3025
-    {
3026
-        // find a field that's not a text field
3027
-        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
3028
-        if ($field_we_can_use) {
3029
-            return $this->get($field_we_can_use->get_name());
3030
-        }
3031
-        $first_few_properties = $this->model_field_array();
3032
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
3033
-        $name_parts = array();
3034
-        foreach ($first_few_properties as $name => $value) {
3035
-            $name_parts[] = "$name:$value";
3036
-        }
3037
-        return implode(',', $name_parts);
3038
-    }
3039
-
3040
-
3041
-    /**
3042
-     * in_entity_map
3043
-     * Checks if this model object has been proven to already be in the entity map
3044
-     *
3045
-     * @return boolean
3046
-     * @throws ReflectionException
3047
-     * @throws InvalidArgumentException
3048
-     * @throws InvalidInterfaceException
3049
-     * @throws InvalidDataTypeException
3050
-     * @throws EE_Error
3051
-     */
3052
-    public function in_entity_map()
3053
-    {
3054
-        // well, if we looked, did we find it in the entity map?
3055
-        return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3056
-    }
3057
-
3058
-
3059
-    /**
3060
-     * refresh_from_db
3061
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
3062
-     *
3063
-     * @throws ReflectionException
3064
-     * @throws InvalidArgumentException
3065
-     * @throws InvalidInterfaceException
3066
-     * @throws InvalidDataTypeException
3067
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3068
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3069
-     */
3070
-    public function refresh_from_db()
3071
-    {
3072
-        if ($this->ID() && $this->in_entity_map()) {
3073
-            $this->get_model()->refresh_entity_map_from_db($this->ID());
3074
-        } else {
3075
-            // if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3076
-            // if it has an ID but it's not in the map, and you're asking me to refresh it
3077
-            // that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3078
-            // absolutely nothing in it for this ID
3079
-            if (WP_DEBUG) {
3080
-                throw new EE_Error(
3081
-                    sprintf(
3082
-                        esc_html__(
3083
-                            'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3084
-                            'event_espresso'
3085
-                        ),
3086
-                        $this->ID(),
3087
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3088
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3089
-                    )
3090
-                );
3091
-            }
3092
-        }
3093
-    }
3094
-
3095
-
3096
-    /**
3097
-     * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3098
-     *
3099
-     * @since 4.9.80.p
3100
-     * @param EE_Model_Field_Base[] $fields
3101
-     * @param string $new_value_sql
3102
-     *      example: 'column_name=123',
3103
-     *      or 'column_name=column_name+1',
3104
-     *      or 'column_name= CASE
3105
-     *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3106
-     *          THEN `column_name` + 5
3107
-     *          ELSE `column_name`
3108
-     *      END'
3109
-     *      Also updates $field on this model object with the latest value from the database.
3110
-     * @return bool
3111
-     * @throws EE_Error
3112
-     * @throws InvalidArgumentException
3113
-     * @throws InvalidDataTypeException
3114
-     * @throws InvalidInterfaceException
3115
-     * @throws ReflectionException
3116
-     */
3117
-    protected function updateFieldsInDB($fields, $new_value_sql)
3118
-    {
3119
-        // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3120
-        // if it wasn't even there to start off.
3121
-        if (! $this->ID()) {
3122
-            $this->save();
3123
-        }
3124
-        global $wpdb;
3125
-        if (empty($fields)) {
3126
-            throw new InvalidArgumentException(
3127
-                esc_html__(
3128
-                    'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3129
-                    'event_espresso'
3130
-                )
3131
-            );
3132
-        }
3133
-        $first_field = reset($fields);
3134
-        $table_alias = $first_field->get_table_alias();
3135
-        foreach ($fields as $field) {
3136
-            if ($table_alias !== $field->get_table_alias()) {
3137
-                throw new InvalidArgumentException(
3138
-                    sprintf(
3139
-                        esc_html__(
3140
-                            // @codingStandardsIgnoreStart
3141
-                            'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
3142
-                            // @codingStandardsIgnoreEnd
3143
-                            'event_espresso'
3144
-                        ),
3145
-                        $table_alias,
3146
-                        $field->get_table_alias()
3147
-                    )
3148
-                );
3149
-            }
3150
-        }
3151
-        // Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3152
-        $table_obj = $this->get_model()->get_table_obj_by_alias($table_alias);
3153
-        $table_pk_value = $this->ID();
3154
-        $table_name = $table_obj->get_table_name();
3155
-        if ($table_obj instanceof EE_Secondary_Table) {
3156
-            $table_pk_field_name = $table_obj->get_fk_on_table();
3157
-        } else {
3158
-            $table_pk_field_name = $table_obj->get_pk_column();
3159
-        }
3160
-
3161
-        $query =
3162
-            "UPDATE `{$table_name}`
337
+				$this->_props_n_values_provided_in_constructor
338
+				&& $field_value
339
+				&& $field_name === $model->primary_key_name()
340
+			) {
341
+				// if so, we want all this object's fields to be filled either with
342
+				// what we've explicitly set on this model
343
+				// or what we have in the db
344
+				// echo "setting primary key!";
345
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
346
+				$obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
347
+				foreach ($fields_on_model as $field_obj) {
348
+					if (
349
+						! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
350
+						&& $field_obj->get_name() !== $field_name
351
+					) {
352
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
353
+					}
354
+				}
355
+				// oh this model object has an ID? well make sure its in the entity mapper
356
+				$model->add_to_entity_map($this);
357
+			}
358
+			// let's unset any cache for this field_name from the $_cached_properties property.
359
+			$this->_clear_cached_property($field_name);
360
+		} else {
361
+			throw new EE_Error(
362
+				sprintf(
363
+					esc_html__(
364
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
365
+						'event_espresso'
366
+					),
367
+					$field_name
368
+				)
369
+			);
370
+		}
371
+	}
372
+
373
+
374
+	/**
375
+	 * Set custom select values for model.
376
+	 *
377
+	 * @param array $custom_select_values
378
+	 */
379
+	public function setCustomSelectsValues(array $custom_select_values)
380
+	{
381
+		$this->custom_selection_results = $custom_select_values;
382
+	}
383
+
384
+
385
+	/**
386
+	 * Returns the custom select value for the provided alias if its set.
387
+	 * If not set, returns null.
388
+	 *
389
+	 * @param string $alias
390
+	 * @return string|int|float|null
391
+	 */
392
+	public function getCustomSelect($alias)
393
+	{
394
+		return isset($this->custom_selection_results[ $alias ])
395
+			? $this->custom_selection_results[ $alias ]
396
+			: null;
397
+	}
398
+
399
+
400
+	/**
401
+	 * This sets the field value on the db column if it exists for the given $column_name or
402
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
403
+	 *
404
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
405
+	 * @param string $field_name  Must be the exact column name.
406
+	 * @param mixed  $field_value The value to set.
407
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
408
+	 * @throws InvalidArgumentException
409
+	 * @throws InvalidInterfaceException
410
+	 * @throws InvalidDataTypeException
411
+	 * @throws EE_Error
412
+	 * @throws ReflectionException
413
+	 */
414
+	public function set_field_or_extra_meta($field_name, $field_value)
415
+	{
416
+		if ($this->get_model()->has_field($field_name)) {
417
+			$this->set($field_name, $field_value);
418
+			return true;
419
+		}
420
+		// ensure this object is saved first so that extra meta can be properly related.
421
+		$this->save();
422
+		return $this->update_extra_meta($field_name, $field_value);
423
+	}
424
+
425
+
426
+	/**
427
+	 * This retrieves the value of the db column set on this class or if that's not present
428
+	 * it will attempt to retrieve from extra_meta if found.
429
+	 * Example Usage:
430
+	 * Via EE_Message child class:
431
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
432
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
433
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
434
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
435
+	 * value for those extra fields dynamically via the EE_message object.
436
+	 *
437
+	 * @param  string $field_name expecting the fully qualified field name.
438
+	 * @return mixed|null  value for the field if found.  null if not found.
439
+	 * @throws ReflectionException
440
+	 * @throws InvalidArgumentException
441
+	 * @throws InvalidInterfaceException
442
+	 * @throws InvalidDataTypeException
443
+	 * @throws EE_Error
444
+	 */
445
+	public function get_field_or_extra_meta($field_name)
446
+	{
447
+		if ($this->get_model()->has_field($field_name)) {
448
+			$column_value = $this->get($field_name);
449
+		} else {
450
+			// This isn't a column in the main table, let's see if it is in the extra meta.
451
+			$column_value = $this->get_extra_meta($field_name, true, null);
452
+		}
453
+		return $column_value;
454
+	}
455
+
456
+
457
+	/**
458
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
459
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
460
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
461
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
462
+	 *
463
+	 * @access public
464
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
465
+	 * @return void
466
+	 * @throws InvalidArgumentException
467
+	 * @throws InvalidInterfaceException
468
+	 * @throws InvalidDataTypeException
469
+	 * @throws EE_Error
470
+	 * @throws ReflectionException
471
+	 */
472
+	public function set_timezone($timezone = '')
473
+	{
474
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
475
+		// make sure we clear all cached properties because they won't be relevant now
476
+		$this->_clear_cached_properties();
477
+		// make sure we update field settings and the date for all EE_Datetime_Fields
478
+		$model_fields = $this->get_model()->field_settings(false);
479
+		foreach ($model_fields as $field_name => $field_obj) {
480
+			if ($field_obj instanceof EE_Datetime_Field) {
481
+				$field_obj->set_timezone($this->_timezone);
482
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
483
+					EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
484
+				}
485
+			}
486
+		}
487
+	}
488
+
489
+
490
+	/**
491
+	 * This just returns whatever is set for the current timezone.
492
+	 *
493
+	 * @access public
494
+	 * @return string timezone string
495
+	 */
496
+	public function get_timezone()
497
+	{
498
+		return $this->_timezone;
499
+	}
500
+
501
+
502
+	/**
503
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
504
+	 * internally instead of wp set date format options
505
+	 *
506
+	 * @since 4.6
507
+	 * @param string $format should be a format recognizable by PHP date() functions.
508
+	 */
509
+	public function set_date_format($format)
510
+	{
511
+		$this->_dt_frmt = $format;
512
+		// clear cached_properties because they won't be relevant now.
513
+		$this->_clear_cached_properties();
514
+	}
515
+
516
+
517
+	/**
518
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
519
+	 * class internally instead of wp set time format options.
520
+	 *
521
+	 * @since 4.6
522
+	 * @param string $format should be a format recognizable by PHP date() functions.
523
+	 */
524
+	public function set_time_format($format)
525
+	{
526
+		$this->_tm_frmt = $format;
527
+		// clear cached_properties because they won't be relevant now.
528
+		$this->_clear_cached_properties();
529
+	}
530
+
531
+
532
+	/**
533
+	 * This returns the current internal set format for the date and time formats.
534
+	 *
535
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
536
+	 *                             where the first value is the date format and the second value is the time format.
537
+	 * @return mixed string|array
538
+	 */
539
+	public function get_format($full = true)
540
+	{
541
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
542
+	}
543
+
544
+
545
+	/**
546
+	 * cache
547
+	 * stores the passed model object on the current model object.
548
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
549
+	 *
550
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
551
+	 *                                       'Registration' associated with this model object
552
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
553
+	 *                                       that could be a payment or a registration)
554
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
555
+	 *                                       items which will be stored in an array on this object
556
+	 * @throws ReflectionException
557
+	 * @throws InvalidArgumentException
558
+	 * @throws InvalidInterfaceException
559
+	 * @throws InvalidDataTypeException
560
+	 * @throws EE_Error
561
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
562
+	 *                                       related thing, no array)
563
+	 */
564
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
565
+	{
566
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
567
+		if (! $object_to_cache instanceof EE_Base_Class) {
568
+			return false;
569
+		}
570
+		// also get "how" the object is related, or throw an error
571
+		if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
572
+			throw new EE_Error(
573
+				sprintf(
574
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
575
+					$relationName,
576
+					get_class($this)
577
+				)
578
+			);
579
+		}
580
+		// how many things are related ?
581
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
582
+			// if it's a "belongs to" relationship, then there's only one related model object
583
+			// eg, if this is a registration, there's only 1 attendee for it
584
+			// so for these model objects just set it to be cached
585
+			$this->_model_relations[ $relationName ] = $object_to_cache;
586
+			$return = true;
587
+		} else {
588
+			// otherwise, this is the "many" side of a one to many relationship,
589
+			// so we'll add the object to the array of related objects for that type.
590
+			// eg: if this is an event, there are many registrations for that event,
591
+			// so we cache the registrations in an array
592
+			if (! is_array($this->_model_relations[ $relationName ])) {
593
+				// if for some reason, the cached item is a model object,
594
+				// then stick that in the array, otherwise start with an empty array
595
+				$this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
596
+														   instanceof
597
+														   EE_Base_Class
598
+					? array($this->_model_relations[ $relationName ]) : array();
599
+			}
600
+			// first check for a cache_id which is normally empty
601
+			if (! empty($cache_id)) {
602
+				// if the cache_id exists, then it means we are purposely trying to cache this
603
+				// with a known key that can then be used to retrieve the object later on
604
+				$this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
605
+				$return = $cache_id;
606
+			} elseif ($object_to_cache->ID()) {
607
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
608
+				$this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
609
+				$return = $object_to_cache->ID();
610
+			} else {
611
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
612
+				$this->_model_relations[ $relationName ][] = $object_to_cache;
613
+				// move the internal pointer to the end of the array
614
+				end($this->_model_relations[ $relationName ]);
615
+				// and grab the key so that we can return it
616
+				$return = key($this->_model_relations[ $relationName ]);
617
+			}
618
+		}
619
+		return $return;
620
+	}
621
+
622
+
623
+	/**
624
+	 * For adding an item to the cached_properties property.
625
+	 *
626
+	 * @access protected
627
+	 * @param string      $fieldname the property item the corresponding value is for.
628
+	 * @param mixed       $value     The value we are caching.
629
+	 * @param string|null $cache_type
630
+	 * @return void
631
+	 * @throws ReflectionException
632
+	 * @throws InvalidArgumentException
633
+	 * @throws InvalidInterfaceException
634
+	 * @throws InvalidDataTypeException
635
+	 * @throws EE_Error
636
+	 */
637
+	protected function _set_cached_property($fieldname, $value, $cache_type = null)
638
+	{
639
+		// first make sure this property exists
640
+		$this->get_model()->field_settings_for($fieldname);
641
+		$cache_type = empty($cache_type) ? 'standard' : $cache_type;
642
+		$this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
643
+	}
644
+
645
+
646
+	/**
647
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
648
+	 * This also SETS the cache if we return the actual property!
649
+	 *
650
+	 * @param string $fieldname        the name of the property we're trying to retrieve
651
+	 * @param bool   $pretty
652
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
653
+	 *                                 (in cases where the same property may be used for different outputs
654
+	 *                                 - i.e. datetime, money etc.)
655
+	 *                                 It can also accept certain pre-defined "schema" strings
656
+	 *                                 to define how to output the property.
657
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
658
+	 * @return mixed                   whatever the value for the property is we're retrieving
659
+	 * @throws ReflectionException
660
+	 * @throws InvalidArgumentException
661
+	 * @throws InvalidInterfaceException
662
+	 * @throws InvalidDataTypeException
663
+	 * @throws EE_Error
664
+	 */
665
+	protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
666
+	{
667
+		// verify the field exists
668
+		$model = $this->get_model();
669
+		$model->field_settings_for($fieldname);
670
+		$cache_type = $pretty ? 'pretty' : 'standard';
671
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
672
+		if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
673
+			return $this->_cached_properties[ $fieldname ][ $cache_type ];
674
+		}
675
+		$value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
676
+		$this->_set_cached_property($fieldname, $value, $cache_type);
677
+		return $value;
678
+	}
679
+
680
+
681
+	/**
682
+	 * If the cache didn't fetch the needed item, this fetches it.
683
+	 *
684
+	 * @param string $fieldname
685
+	 * @param bool   $pretty
686
+	 * @param string $extra_cache_ref
687
+	 * @return mixed
688
+	 * @throws InvalidArgumentException
689
+	 * @throws InvalidInterfaceException
690
+	 * @throws InvalidDataTypeException
691
+	 * @throws EE_Error
692
+	 * @throws ReflectionException
693
+	 */
694
+	protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
695
+	{
696
+		$field_obj = $this->get_model()->field_settings_for($fieldname);
697
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
698
+		if ($field_obj instanceof EE_Datetime_Field) {
699
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
700
+		}
701
+		if (! isset($this->_fields[ $fieldname ])) {
702
+			$this->_fields[ $fieldname ] = null;
703
+		}
704
+		$value = $pretty
705
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
706
+			: $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
707
+		return $value;
708
+	}
709
+
710
+
711
+	/**
712
+	 * set timezone, formats, and output for EE_Datetime_Field objects
713
+	 *
714
+	 * @param \EE_Datetime_Field $datetime_field
715
+	 * @param bool               $pretty
716
+	 * @param null               $date_or_time
717
+	 * @return void
718
+	 * @throws InvalidArgumentException
719
+	 * @throws InvalidInterfaceException
720
+	 * @throws InvalidDataTypeException
721
+	 * @throws EE_Error
722
+	 */
723
+	protected function _prepare_datetime_field(
724
+		EE_Datetime_Field $datetime_field,
725
+		$pretty = false,
726
+		$date_or_time = null
727
+	) {
728
+		$datetime_field->set_timezone($this->_timezone);
729
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
730
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
731
+		// set the output returned
732
+		switch ($date_or_time) {
733
+			case 'D':
734
+				$datetime_field->set_date_time_output('date');
735
+				break;
736
+			case 'T':
737
+				$datetime_field->set_date_time_output('time');
738
+				break;
739
+			default:
740
+				$datetime_field->set_date_time_output();
741
+		}
742
+	}
743
+
744
+
745
+	/**
746
+	 * This just takes care of clearing out the cached_properties
747
+	 *
748
+	 * @return void
749
+	 */
750
+	protected function _clear_cached_properties()
751
+	{
752
+		$this->_cached_properties = array();
753
+	}
754
+
755
+
756
+	/**
757
+	 * This just clears out ONE property if it exists in the cache
758
+	 *
759
+	 * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
760
+	 * @return void
761
+	 */
762
+	protected function _clear_cached_property($property_name)
763
+	{
764
+		if (isset($this->_cached_properties[ $property_name ])) {
765
+			unset($this->_cached_properties[ $property_name ]);
766
+		}
767
+	}
768
+
769
+
770
+	/**
771
+	 * Ensures that this related thing is a model object.
772
+	 *
773
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
774
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
775
+	 * @return EE_Base_Class
776
+	 * @throws ReflectionException
777
+	 * @throws InvalidArgumentException
778
+	 * @throws InvalidInterfaceException
779
+	 * @throws InvalidDataTypeException
780
+	 * @throws EE_Error
781
+	 */
782
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
783
+	{
784
+		$other_model_instance = self::_get_model_instance_with_name(
785
+			self::_get_model_classname($model_name),
786
+			$this->_timezone
787
+		);
788
+		return $other_model_instance->ensure_is_obj($object_or_id);
789
+	}
790
+
791
+
792
+	/**
793
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
794
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
795
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
796
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
797
+	 *
798
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
799
+	 *                                                     Eg 'Registration'
800
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
801
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
802
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
803
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
804
+	 *                                                     this is HasMany or HABTM.
805
+	 * @throws ReflectionException
806
+	 * @throws InvalidArgumentException
807
+	 * @throws InvalidInterfaceException
808
+	 * @throws InvalidDataTypeException
809
+	 * @throws EE_Error
810
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
811
+	 *                                                     relation from all
812
+	 */
813
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
814
+	{
815
+		$relationship_to_model = $this->get_model()->related_settings_for($relationName);
816
+		$index_in_cache = '';
817
+		if (! $relationship_to_model) {
818
+			throw new EE_Error(
819
+				sprintf(
820
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
821
+					$relationName,
822
+					get_class($this)
823
+				)
824
+			);
825
+		}
826
+		if ($clear_all) {
827
+			$obj_removed = true;
828
+			$this->_model_relations[ $relationName ] = null;
829
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
830
+			$obj_removed = $this->_model_relations[ $relationName ];
831
+			$this->_model_relations[ $relationName ] = null;
832
+		} else {
833
+			if (
834
+				$object_to_remove_or_index_into_array instanceof EE_Base_Class
835
+				&& $object_to_remove_or_index_into_array->ID()
836
+			) {
837
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
838
+				if (
839
+					is_array($this->_model_relations[ $relationName ])
840
+					&& ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
841
+				) {
842
+					$index_found_at = null;
843
+					// find this object in the array even though it has a different key
844
+					foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
845
+						/** @noinspection TypeUnsafeComparisonInspection */
846
+						if (
847
+							$obj instanceof EE_Base_Class
848
+							&& (
849
+								$obj == $object_to_remove_or_index_into_array
850
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
851
+							)
852
+						) {
853
+							$index_found_at = $index;
854
+							break;
855
+						}
856
+					}
857
+					if ($index_found_at) {
858
+						$index_in_cache = $index_found_at;
859
+					} else {
860
+						// it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
861
+						// if it wasn't in it to begin with. So we're done
862
+						return $object_to_remove_or_index_into_array;
863
+					}
864
+				}
865
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
866
+				// so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
867
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
868
+					/** @noinspection TypeUnsafeComparisonInspection */
869
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
870
+						$index_in_cache = $index;
871
+					}
872
+				}
873
+			} else {
874
+				$index_in_cache = $object_to_remove_or_index_into_array;
875
+			}
876
+			// supposedly we've found it. But it could just be that the client code
877
+			// provided a bad index/object
878
+			if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
879
+				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
880
+				unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
881
+			} else {
882
+				// that thing was never cached anyways.
883
+				$obj_removed = null;
884
+			}
885
+		}
886
+		return $obj_removed;
887
+	}
888
+
889
+
890
+	/**
891
+	 * update_cache_after_object_save
892
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
893
+	 * obtained after being saved to the db
894
+	 *
895
+	 * @param string        $relationName       - the type of object that is cached
896
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
897
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
898
+	 * @return boolean TRUE on success, FALSE on fail
899
+	 * @throws ReflectionException
900
+	 * @throws InvalidArgumentException
901
+	 * @throws InvalidInterfaceException
902
+	 * @throws InvalidDataTypeException
903
+	 * @throws EE_Error
904
+	 */
905
+	public function update_cache_after_object_save(
906
+		$relationName,
907
+		EE_Base_Class $newly_saved_object,
908
+		$current_cache_id = ''
909
+	) {
910
+		// verify that incoming object is of the correct type
911
+		$obj_class = 'EE_' . $relationName;
912
+		if ($newly_saved_object instanceof $obj_class) {
913
+			/* @type EE_Base_Class $newly_saved_object */
914
+			// now get the type of relation
915
+			$relationship_to_model = $this->get_model()->related_settings_for($relationName);
916
+			// if this is a 1:1 relationship
917
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
918
+				// then just replace the cached object with the newly saved object
919
+				$this->_model_relations[ $relationName ] = $newly_saved_object;
920
+				return true;
921
+				// or if it's some kind of sordid feral polyamorous relationship...
922
+			}
923
+			if (
924
+				is_array($this->_model_relations[ $relationName ])
925
+				&& isset($this->_model_relations[ $relationName ][ $current_cache_id ])
926
+			) {
927
+				// then remove the current cached item
928
+				unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
929
+				// and cache the newly saved object using it's new ID
930
+				$this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
931
+				return true;
932
+			}
933
+		}
934
+		return false;
935
+	}
936
+
937
+
938
+	/**
939
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
940
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
941
+	 *
942
+	 * @param string $relationName
943
+	 * @return EE_Base_Class
944
+	 */
945
+	public function get_one_from_cache($relationName)
946
+	{
947
+		$cached_array_or_object = isset($this->_model_relations[ $relationName ])
948
+			? $this->_model_relations[ $relationName ]
949
+			: null;
950
+		if (is_array($cached_array_or_object)) {
951
+			return array_shift($cached_array_or_object);
952
+		}
953
+		return $cached_array_or_object;
954
+	}
955
+
956
+
957
+	/**
958
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
959
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
960
+	 *
961
+	 * @param string $relationName
962
+	 * @throws ReflectionException
963
+	 * @throws InvalidArgumentException
964
+	 * @throws InvalidInterfaceException
965
+	 * @throws InvalidDataTypeException
966
+	 * @throws EE_Error
967
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
968
+	 */
969
+	public function get_all_from_cache($relationName)
970
+	{
971
+		$objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
972
+		// if the result is not an array, but exists, make it an array
973
+		$objects = is_array($objects) ? $objects : array($objects);
974
+		// bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
975
+		// basically, if this model object was stored in the session, and these cached model objects
976
+		// already have IDs, let's make sure they're in their model's entity mapper
977
+		// otherwise we will have duplicates next time we call
978
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
979
+		$model = EE_Registry::instance()->load_model($relationName);
980
+		foreach ($objects as $model_object) {
981
+			if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
982
+				// ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
983
+				if ($model_object->ID()) {
984
+					$model->add_to_entity_map($model_object);
985
+				}
986
+			} else {
987
+				throw new EE_Error(
988
+					sprintf(
989
+						esc_html__(
990
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
991
+							'event_espresso'
992
+						),
993
+						$relationName,
994
+						gettype($model_object)
995
+					)
996
+				);
997
+			}
998
+		}
999
+		return $objects;
1000
+	}
1001
+
1002
+
1003
+	/**
1004
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1005
+	 * matching the given query conditions.
1006
+	 *
1007
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1008
+	 * @param int   $limit              How many objects to return.
1009
+	 * @param array $query_params       Any additional conditions on the query.
1010
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1011
+	 *                                  you can indicate just the columns you want returned
1012
+	 * @return array|EE_Base_Class[]
1013
+	 * @throws ReflectionException
1014
+	 * @throws InvalidArgumentException
1015
+	 * @throws InvalidInterfaceException
1016
+	 * @throws InvalidDataTypeException
1017
+	 * @throws EE_Error
1018
+	 */
1019
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1020
+	{
1021
+		$model = $this->get_model();
1022
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1023
+			? $model->get_primary_key_field()->get_name()
1024
+			: $field_to_order_by;
1025
+		$current_value = ! empty($field) ? $this->get($field) : null;
1026
+		if (empty($field) || empty($current_value)) {
1027
+			return array();
1028
+		}
1029
+		return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1030
+	}
1031
+
1032
+
1033
+	/**
1034
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1035
+	 * matching the given query conditions.
1036
+	 *
1037
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1038
+	 * @param int   $limit              How many objects to return.
1039
+	 * @param array $query_params       Any additional conditions on the query.
1040
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1041
+	 *                                  you can indicate just the columns you want returned
1042
+	 * @return array|EE_Base_Class[]
1043
+	 * @throws ReflectionException
1044
+	 * @throws InvalidArgumentException
1045
+	 * @throws InvalidInterfaceException
1046
+	 * @throws InvalidDataTypeException
1047
+	 * @throws EE_Error
1048
+	 */
1049
+	public function previous_x(
1050
+		$field_to_order_by = null,
1051
+		$limit = 1,
1052
+		$query_params = array(),
1053
+		$columns_to_select = null
1054
+	) {
1055
+		$model = $this->get_model();
1056
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1057
+			? $model->get_primary_key_field()->get_name()
1058
+			: $field_to_order_by;
1059
+		$current_value = ! empty($field) ? $this->get($field) : null;
1060
+		if (empty($field) || empty($current_value)) {
1061
+			return array();
1062
+		}
1063
+		return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1064
+	}
1065
+
1066
+
1067
+	/**
1068
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1069
+	 * matching the given query conditions.
1070
+	 *
1071
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1072
+	 * @param array $query_params       Any additional conditions on the query.
1073
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1074
+	 *                                  you can indicate just the columns you want returned
1075
+	 * @return array|EE_Base_Class
1076
+	 * @throws ReflectionException
1077
+	 * @throws InvalidArgumentException
1078
+	 * @throws InvalidInterfaceException
1079
+	 * @throws InvalidDataTypeException
1080
+	 * @throws EE_Error
1081
+	 */
1082
+	public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1083
+	{
1084
+		$model = $this->get_model();
1085
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1086
+			? $model->get_primary_key_field()->get_name()
1087
+			: $field_to_order_by;
1088
+		$current_value = ! empty($field) ? $this->get($field) : null;
1089
+		if (empty($field) || empty($current_value)) {
1090
+			return array();
1091
+		}
1092
+		return $model->next($current_value, $field, $query_params, $columns_to_select);
1093
+	}
1094
+
1095
+
1096
+	/**
1097
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1098
+	 * matching the given query conditions.
1099
+	 *
1100
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1101
+	 * @param array $query_params       Any additional conditions on the query.
1102
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1103
+	 *                                  you can indicate just the column you want returned
1104
+	 * @return array|EE_Base_Class
1105
+	 * @throws ReflectionException
1106
+	 * @throws InvalidArgumentException
1107
+	 * @throws InvalidInterfaceException
1108
+	 * @throws InvalidDataTypeException
1109
+	 * @throws EE_Error
1110
+	 */
1111
+	public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1112
+	{
1113
+		$model = $this->get_model();
1114
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1115
+			? $model->get_primary_key_field()->get_name()
1116
+			: $field_to_order_by;
1117
+		$current_value = ! empty($field) ? $this->get($field) : null;
1118
+		if (empty($field) || empty($current_value)) {
1119
+			return array();
1120
+		}
1121
+		return $model->previous($current_value, $field, $query_params, $columns_to_select);
1122
+	}
1123
+
1124
+
1125
+	/**
1126
+	 * Overrides parent because parent expects old models.
1127
+	 * This also doesn't do any validation, and won't work for serialized arrays
1128
+	 *
1129
+	 * @param string $field_name
1130
+	 * @param mixed  $field_value_from_db
1131
+	 * @throws ReflectionException
1132
+	 * @throws InvalidArgumentException
1133
+	 * @throws InvalidInterfaceException
1134
+	 * @throws InvalidDataTypeException
1135
+	 * @throws EE_Error
1136
+	 */
1137
+	public function set_from_db($field_name, $field_value_from_db)
1138
+	{
1139
+		$field_obj = $this->get_model()->field_settings_for($field_name);
1140
+		if ($field_obj instanceof EE_Model_Field_Base) {
1141
+			// you would think the DB has no NULLs for non-null label fields right? wrong!
1142
+			// eg, a CPT model object could have an entry in the posts table, but no
1143
+			// entry in the meta table. Meaning that all its columns in the meta table
1144
+			// are null! yikes! so when we find one like that, use defaults for its meta columns
1145
+			if ($field_value_from_db === null) {
1146
+				if ($field_obj->is_nullable()) {
1147
+					// if the field allows nulls, then let it be null
1148
+					$field_value = null;
1149
+				} else {
1150
+					$field_value = $field_obj->get_default_value();
1151
+				}
1152
+			} else {
1153
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1154
+			}
1155
+			$this->_fields[ $field_name ] = $field_value;
1156
+			$this->_clear_cached_property($field_name);
1157
+		}
1158
+	}
1159
+
1160
+
1161
+	/**
1162
+	 * verifies that the specified field is of the correct type
1163
+	 *
1164
+	 * @param string $field_name
1165
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1166
+	 *                                (in cases where the same property may be used for different outputs
1167
+	 *                                - i.e. datetime, money etc.)
1168
+	 * @return mixed
1169
+	 * @throws ReflectionException
1170
+	 * @throws InvalidArgumentException
1171
+	 * @throws InvalidInterfaceException
1172
+	 * @throws InvalidDataTypeException
1173
+	 * @throws EE_Error
1174
+	 */
1175
+	public function get($field_name, $extra_cache_ref = null)
1176
+	{
1177
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1178
+	}
1179
+
1180
+
1181
+	/**
1182
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1183
+	 *
1184
+	 * @param  string $field_name A valid fieldname
1185
+	 * @return mixed              Whatever the raw value stored on the property is.
1186
+	 * @throws ReflectionException
1187
+	 * @throws InvalidArgumentException
1188
+	 * @throws InvalidInterfaceException
1189
+	 * @throws InvalidDataTypeException
1190
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1191
+	 */
1192
+	public function get_raw($field_name)
1193
+	{
1194
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1195
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1196
+			? $this->_fields[ $field_name ]->format('U')
1197
+			: $this->_fields[ $field_name ];
1198
+	}
1199
+
1200
+
1201
+	/**
1202
+	 * This is used to return the internal DateTime object used for a field that is a
1203
+	 * EE_Datetime_Field.
1204
+	 *
1205
+	 * @param string $field_name               The field name retrieving the DateTime object.
1206
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1207
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1208
+	 *                                         EE_Datetime_Field and but the field value is null, then
1209
+	 *                                         just null is returned (because that indicates that likely
1210
+	 *                                         this field is nullable).
1211
+	 * @throws InvalidArgumentException
1212
+	 * @throws InvalidDataTypeException
1213
+	 * @throws InvalidInterfaceException
1214
+	 * @throws ReflectionException
1215
+	 */
1216
+	public function get_DateTime_object($field_name)
1217
+	{
1218
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1219
+		if (! $field_settings instanceof EE_Datetime_Field) {
1220
+			EE_Error::add_error(
1221
+				sprintf(
1222
+					esc_html__(
1223
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1224
+						'event_espresso'
1225
+					),
1226
+					$field_name
1227
+				),
1228
+				__FILE__,
1229
+				__FUNCTION__,
1230
+				__LINE__
1231
+			);
1232
+			return false;
1233
+		}
1234
+		return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1235
+			? clone $this->_fields[ $field_name ]
1236
+			: null;
1237
+	}
1238
+
1239
+
1240
+	/**
1241
+	 * To be used in template to immediately echo out the value, and format it for output.
1242
+	 * Eg, should call stripslashes and whatnot before echoing
1243
+	 *
1244
+	 * @param string $field_name      the name of the field as it appears in the DB
1245
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1246
+	 *                                (in cases where the same property may be used for different outputs
1247
+	 *                                - i.e. datetime, money etc.)
1248
+	 * @return void
1249
+	 * @throws ReflectionException
1250
+	 * @throws InvalidArgumentException
1251
+	 * @throws InvalidInterfaceException
1252
+	 * @throws InvalidDataTypeException
1253
+	 * @throws EE_Error
1254
+	 */
1255
+	public function e($field_name, $extra_cache_ref = null)
1256
+	{
1257
+		echo wp_kses($this->get_pretty($field_name, $extra_cache_ref), AllowedTags::getWithFormTags());
1258
+	}
1259
+
1260
+
1261
+	/**
1262
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1263
+	 * can be easily used as the value of form input.
1264
+	 *
1265
+	 * @param string $field_name
1266
+	 * @return void
1267
+	 * @throws ReflectionException
1268
+	 * @throws InvalidArgumentException
1269
+	 * @throws InvalidInterfaceException
1270
+	 * @throws InvalidDataTypeException
1271
+	 * @throws EE_Error
1272
+	 */
1273
+	public function f($field_name)
1274
+	{
1275
+		$this->e($field_name, 'form_input');
1276
+	}
1277
+
1278
+
1279
+	/**
1280
+	 * Same as `f()` but just returns the value instead of echoing it
1281
+	 *
1282
+	 * @param string $field_name
1283
+	 * @return string
1284
+	 * @throws ReflectionException
1285
+	 * @throws InvalidArgumentException
1286
+	 * @throws InvalidInterfaceException
1287
+	 * @throws InvalidDataTypeException
1288
+	 * @throws EE_Error
1289
+	 */
1290
+	public function get_f($field_name)
1291
+	{
1292
+		return (string) $this->get_pretty($field_name, 'form_input');
1293
+	}
1294
+
1295
+
1296
+	/**
1297
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1298
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1299
+	 * to see what options are available.
1300
+	 *
1301
+	 * @param string $field_name
1302
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1303
+	 *                                (in cases where the same property may be used for different outputs
1304
+	 *                                - i.e. datetime, money etc.)
1305
+	 * @return mixed
1306
+	 * @throws ReflectionException
1307
+	 * @throws InvalidArgumentException
1308
+	 * @throws InvalidInterfaceException
1309
+	 * @throws InvalidDataTypeException
1310
+	 * @throws EE_Error
1311
+	 */
1312
+	public function get_pretty($field_name, $extra_cache_ref = null)
1313
+	{
1314
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1315
+	}
1316
+
1317
+
1318
+	/**
1319
+	 * This simply returns the datetime for the given field name
1320
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1321
+	 * (and the equivalent e_date, e_time, e_datetime).
1322
+	 *
1323
+	 * @access   protected
1324
+	 * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1325
+	 * @param string   $dt_frmt      valid datetime format used for date
1326
+	 *                               (if '' then we just use the default on the field,
1327
+	 *                               if NULL we use the last-used format)
1328
+	 * @param string   $tm_frmt      Same as above except this is for time format
1329
+	 * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1330
+	 * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1331
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1332
+	 *                               if field is not a valid dtt field, or void if echoing
1333
+	 * @throws ReflectionException
1334
+	 * @throws InvalidArgumentException
1335
+	 * @throws InvalidInterfaceException
1336
+	 * @throws InvalidDataTypeException
1337
+	 * @throws EE_Error
1338
+	 */
1339
+	protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1340
+	{
1341
+		// clear cached property
1342
+		$this->_clear_cached_property($field_name);
1343
+		// reset format properties because they are used in get()
1344
+		$this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1345
+		$this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1346
+		if ($echo) {
1347
+			$this->e($field_name, $date_or_time);
1348
+			return '';
1349
+		}
1350
+		return $this->get($field_name, $date_or_time);
1351
+	}
1352
+
1353
+
1354
+	/**
1355
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1356
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1357
+	 * other echoes the pretty value for dtt)
1358
+	 *
1359
+	 * @param  string $field_name name of model object datetime field holding the value
1360
+	 * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1361
+	 * @return string            datetime value formatted
1362
+	 * @throws ReflectionException
1363
+	 * @throws InvalidArgumentException
1364
+	 * @throws InvalidInterfaceException
1365
+	 * @throws InvalidDataTypeException
1366
+	 * @throws EE_Error
1367
+	 */
1368
+	public function get_date($field_name, $format = '')
1369
+	{
1370
+		return $this->_get_datetime($field_name, $format, null, 'D');
1371
+	}
1372
+
1373
+
1374
+	/**
1375
+	 * @param        $field_name
1376
+	 * @param string $format
1377
+	 * @throws ReflectionException
1378
+	 * @throws InvalidArgumentException
1379
+	 * @throws InvalidInterfaceException
1380
+	 * @throws InvalidDataTypeException
1381
+	 * @throws EE_Error
1382
+	 */
1383
+	public function e_date($field_name, $format = '')
1384
+	{
1385
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1386
+	}
1387
+
1388
+
1389
+	/**
1390
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1391
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1392
+	 * other echoes the pretty value for dtt)
1393
+	 *
1394
+	 * @param  string $field_name name of model object datetime field holding the value
1395
+	 * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1396
+	 * @return string             datetime value formatted
1397
+	 * @throws ReflectionException
1398
+	 * @throws InvalidArgumentException
1399
+	 * @throws InvalidInterfaceException
1400
+	 * @throws InvalidDataTypeException
1401
+	 * @throws EE_Error
1402
+	 */
1403
+	public function get_time($field_name, $format = '')
1404
+	{
1405
+		return $this->_get_datetime($field_name, null, $format, 'T');
1406
+	}
1407
+
1408
+
1409
+	/**
1410
+	 * @param        $field_name
1411
+	 * @param string $format
1412
+	 * @throws ReflectionException
1413
+	 * @throws InvalidArgumentException
1414
+	 * @throws InvalidInterfaceException
1415
+	 * @throws InvalidDataTypeException
1416
+	 * @throws EE_Error
1417
+	 */
1418
+	public function e_time($field_name, $format = '')
1419
+	{
1420
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1421
+	}
1422
+
1423
+
1424
+	/**
1425
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1426
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1427
+	 * other echoes the pretty value for dtt)
1428
+	 *
1429
+	 * @param  string $field_name name of model object datetime field holding the value
1430
+	 * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1431
+	 * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1432
+	 * @return string             datetime value formatted
1433
+	 * @throws ReflectionException
1434
+	 * @throws InvalidArgumentException
1435
+	 * @throws InvalidInterfaceException
1436
+	 * @throws InvalidDataTypeException
1437
+	 * @throws EE_Error
1438
+	 */
1439
+	public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1440
+	{
1441
+		return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1442
+	}
1443
+
1444
+
1445
+	/**
1446
+	 * @param string $field_name
1447
+	 * @param string $dt_frmt
1448
+	 * @param string $tm_frmt
1449
+	 * @throws ReflectionException
1450
+	 * @throws InvalidArgumentException
1451
+	 * @throws InvalidInterfaceException
1452
+	 * @throws InvalidDataTypeException
1453
+	 * @throws EE_Error
1454
+	 */
1455
+	public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1456
+	{
1457
+		$this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1458
+	}
1459
+
1460
+
1461
+	/**
1462
+	 * Get the i8ln value for a date using the WordPress @see date_i18n function.
1463
+	 *
1464
+	 * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1465
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1466
+	 *                           on the object will be used.
1467
+	 * @return string Date and time string in set locale or false if no field exists for the given
1468
+	 * @throws ReflectionException
1469
+	 * @throws InvalidArgumentException
1470
+	 * @throws InvalidInterfaceException
1471
+	 * @throws InvalidDataTypeException
1472
+	 * @throws EE_Error
1473
+	 *                           field name.
1474
+	 */
1475
+	public function get_i18n_datetime($field_name, $format = '')
1476
+	{
1477
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1478
+		return date_i18n(
1479
+			$format,
1480
+			EEH_DTT_Helper::get_timestamp_with_offset(
1481
+				$this->get_raw($field_name),
1482
+				$this->_timezone
1483
+			)
1484
+		);
1485
+	}
1486
+
1487
+
1488
+	/**
1489
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
1490
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1491
+	 * thrown.
1492
+	 *
1493
+	 * @param  string $field_name The field name being checked
1494
+	 * @throws ReflectionException
1495
+	 * @throws InvalidArgumentException
1496
+	 * @throws InvalidInterfaceException
1497
+	 * @throws InvalidDataTypeException
1498
+	 * @throws EE_Error
1499
+	 * @return EE_Datetime_Field
1500
+	 */
1501
+	protected function _get_dtt_field_settings($field_name)
1502
+	{
1503
+		$field = $this->get_model()->field_settings_for($field_name);
1504
+		// check if field is dtt
1505
+		if ($field instanceof EE_Datetime_Field) {
1506
+			return $field;
1507
+		}
1508
+		throw new EE_Error(
1509
+			sprintf(
1510
+				esc_html__(
1511
+					'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1512
+					'event_espresso'
1513
+				),
1514
+				$field_name,
1515
+				self::_get_model_classname(get_class($this))
1516
+			)
1517
+		);
1518
+	}
1519
+
1520
+
1521
+
1522
+
1523
+	/**
1524
+	 * NOTE ABOUT BELOW:
1525
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1526
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1527
+	 * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1528
+	 * method and make sure you send the entire datetime value for setting.
1529
+	 */
1530
+	/**
1531
+	 * sets the time on a datetime property
1532
+	 *
1533
+	 * @access protected
1534
+	 * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1535
+	 * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1536
+	 * @throws ReflectionException
1537
+	 * @throws InvalidArgumentException
1538
+	 * @throws InvalidInterfaceException
1539
+	 * @throws InvalidDataTypeException
1540
+	 * @throws EE_Error
1541
+	 */
1542
+	protected function _set_time_for($time, $fieldname)
1543
+	{
1544
+		$this->_set_date_time('T', $time, $fieldname);
1545
+	}
1546
+
1547
+
1548
+	/**
1549
+	 * sets the date on a datetime property
1550
+	 *
1551
+	 * @access protected
1552
+	 * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1553
+	 * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1554
+	 * @throws ReflectionException
1555
+	 * @throws InvalidArgumentException
1556
+	 * @throws InvalidInterfaceException
1557
+	 * @throws InvalidDataTypeException
1558
+	 * @throws EE_Error
1559
+	 */
1560
+	protected function _set_date_for($date, $fieldname)
1561
+	{
1562
+		$this->_set_date_time('D', $date, $fieldname);
1563
+	}
1564
+
1565
+
1566
+	/**
1567
+	 * This takes care of setting a date or time independently on a given model object property. This method also
1568
+	 * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1569
+	 *
1570
+	 * @access protected
1571
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1572
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1573
+	 * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1574
+	 *                                        EE_Datetime_Field property)
1575
+	 * @throws ReflectionException
1576
+	 * @throws InvalidArgumentException
1577
+	 * @throws InvalidInterfaceException
1578
+	 * @throws InvalidDataTypeException
1579
+	 * @throws EE_Error
1580
+	 */
1581
+	protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1582
+	{
1583
+		$field = $this->_get_dtt_field_settings($fieldname);
1584
+		$field->set_timezone($this->_timezone);
1585
+		$field->set_date_format($this->_dt_frmt);
1586
+		$field->set_time_format($this->_tm_frmt);
1587
+		switch ($what) {
1588
+			case 'T':
1589
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1590
+					$datetime_value,
1591
+					$this->_fields[ $fieldname ]
1592
+				);
1593
+				$this->_has_changes = true;
1594
+				break;
1595
+			case 'D':
1596
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1597
+					$datetime_value,
1598
+					$this->_fields[ $fieldname ]
1599
+				);
1600
+				$this->_has_changes = true;
1601
+				break;
1602
+			case 'B':
1603
+				$this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1604
+				$this->_has_changes = true;
1605
+				break;
1606
+		}
1607
+		$this->_clear_cached_property($fieldname);
1608
+	}
1609
+
1610
+
1611
+	/**
1612
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1613
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1614
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1615
+	 * that could lead to some unexpected results!
1616
+	 *
1617
+	 * @access public
1618
+	 * @param string $field_name               This is the name of the field on the object that contains the date/time
1619
+	 *                                         value being returned.
1620
+	 * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1621
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1622
+	 * @param string $prepend                  You can include something to prepend on the timestamp
1623
+	 * @param string $append                   You can include something to append on the timestamp
1624
+	 * @throws ReflectionException
1625
+	 * @throws InvalidArgumentException
1626
+	 * @throws InvalidInterfaceException
1627
+	 * @throws InvalidDataTypeException
1628
+	 * @throws EE_Error
1629
+	 * @return string timestamp
1630
+	 */
1631
+	public function display_in_my_timezone(
1632
+		$field_name,
1633
+		$callback = 'get_datetime',
1634
+		$args = null,
1635
+		$prepend = '',
1636
+		$append = ''
1637
+	) {
1638
+		$timezone = EEH_DTT_Helper::get_timezone();
1639
+		if ($timezone === $this->_timezone) {
1640
+			return '';
1641
+		}
1642
+		$original_timezone = $this->_timezone;
1643
+		$this->set_timezone($timezone);
1644
+		$fn = (array) $field_name;
1645
+		$args = array_merge($fn, (array) $args);
1646
+		if (! method_exists($this, $callback)) {
1647
+			throw new EE_Error(
1648
+				sprintf(
1649
+					esc_html__(
1650
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1651
+						'event_espresso'
1652
+					),
1653
+					$callback
1654
+				)
1655
+			);
1656
+		}
1657
+		$args = (array) $args;
1658
+		$return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1659
+		$this->set_timezone($original_timezone);
1660
+		return $return;
1661
+	}
1662
+
1663
+
1664
+	/**
1665
+	 * Deletes this model object.
1666
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1667
+	 * override
1668
+	 * `EE_Base_Class::_delete` NOT this class.
1669
+	 *
1670
+	 * @return boolean | int
1671
+	 * @throws ReflectionException
1672
+	 * @throws InvalidArgumentException
1673
+	 * @throws InvalidInterfaceException
1674
+	 * @throws InvalidDataTypeException
1675
+	 * @throws EE_Error
1676
+	 */
1677
+	public function delete()
1678
+	{
1679
+		/**
1680
+		 * Called just before the `EE_Base_Class::_delete` method call.
1681
+		 * Note:
1682
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1683
+		 * should be aware that `_delete` may not always result in a permanent delete.
1684
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
1685
+		 * soft deletes (trash) the object and does not permanently delete it.
1686
+		 *
1687
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1688
+		 */
1689
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
1690
+		$result = $this->_delete();
1691
+		/**
1692
+		 * Called just after the `EE_Base_Class::_delete` method call.
1693
+		 * Note:
1694
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1695
+		 * should be aware that `_delete` may not always result in a permanent delete.
1696
+		 * For example `EE_Soft_Base_Class::_delete`
1697
+		 * soft deletes (trash) the object and does not permanently delete it.
1698
+		 *
1699
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1700
+		 * @param boolean       $result
1701
+		 */
1702
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1703
+		return $result;
1704
+	}
1705
+
1706
+
1707
+	/**
1708
+	 * Calls the specific delete method for the instantiated class.
1709
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1710
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1711
+	 * `EE_Base_Class::delete`
1712
+	 *
1713
+	 * @return bool|int
1714
+	 * @throws ReflectionException
1715
+	 * @throws InvalidArgumentException
1716
+	 * @throws InvalidInterfaceException
1717
+	 * @throws InvalidDataTypeException
1718
+	 * @throws EE_Error
1719
+	 */
1720
+	protected function _delete()
1721
+	{
1722
+		return $this->delete_permanently();
1723
+	}
1724
+
1725
+
1726
+	/**
1727
+	 * Deletes this model object permanently from db
1728
+	 * (but keep in mind related models may block the delete and return an error)
1729
+	 *
1730
+	 * @return bool | int
1731
+	 * @throws ReflectionException
1732
+	 * @throws InvalidArgumentException
1733
+	 * @throws InvalidInterfaceException
1734
+	 * @throws InvalidDataTypeException
1735
+	 * @throws EE_Error
1736
+	 */
1737
+	public function delete_permanently()
1738
+	{
1739
+		/**
1740
+		 * Called just before HARD deleting a model object
1741
+		 *
1742
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1743
+		 */
1744
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1745
+		$model = $this->get_model();
1746
+		$result = $model->delete_permanently_by_ID($this->ID());
1747
+		$this->refresh_cache_of_related_objects();
1748
+		/**
1749
+		 * Called just after HARD deleting a model object
1750
+		 *
1751
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1752
+		 * @param boolean       $result
1753
+		 */
1754
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1755
+		return $result;
1756
+	}
1757
+
1758
+
1759
+	/**
1760
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1761
+	 * related model objects
1762
+	 *
1763
+	 * @throws ReflectionException
1764
+	 * @throws InvalidArgumentException
1765
+	 * @throws InvalidInterfaceException
1766
+	 * @throws InvalidDataTypeException
1767
+	 * @throws EE_Error
1768
+	 */
1769
+	public function refresh_cache_of_related_objects()
1770
+	{
1771
+		$model = $this->get_model();
1772
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1773
+			if (! empty($this->_model_relations[ $relation_name ])) {
1774
+				$related_objects = $this->_model_relations[ $relation_name ];
1775
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
1776
+					// this relation only stores a single model object, not an array
1777
+					// but let's make it consistent
1778
+					$related_objects = array($related_objects);
1779
+				}
1780
+				foreach ($related_objects as $related_object) {
1781
+					// only refresh their cache if they're in memory
1782
+					if ($related_object instanceof EE_Base_Class) {
1783
+						$related_object->clear_cache(
1784
+							$model->get_this_model_name(),
1785
+							$this
1786
+						);
1787
+					}
1788
+				}
1789
+			}
1790
+		}
1791
+	}
1792
+
1793
+
1794
+	/**
1795
+	 *        Saves this object to the database. An array may be supplied to set some values on this
1796
+	 * object just before saving.
1797
+	 *
1798
+	 * @access public
1799
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
1800
+	 *                                 if provided during the save() method (often client code will change the fields'
1801
+	 *                                 values before calling save)
1802
+	 * @return bool|int|string         1 on a successful update
1803
+	 *                                 the ID of the new entry on insert
1804
+	 *                                 0 on failure or if the model object isn't allowed to persist
1805
+	 *                                 (as determined by EE_Base_Class::allow_persist())
1806
+	 * @throws InvalidInterfaceException
1807
+	 * @throws InvalidDataTypeException
1808
+	 * @throws EE_Error
1809
+	 * @throws InvalidArgumentException
1810
+	 * @throws ReflectionException
1811
+	 * @throws ReflectionException
1812
+	 * @throws ReflectionException
1813
+	 */
1814
+	public function save($set_cols_n_values = array())
1815
+	{
1816
+		$model = $this->get_model();
1817
+		/**
1818
+		 * Filters the fields we're about to save on the model object
1819
+		 *
1820
+		 * @param array         $set_cols_n_values
1821
+		 * @param EE_Base_Class $model_object
1822
+		 */
1823
+		$set_cols_n_values = (array) apply_filters(
1824
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
1825
+			$set_cols_n_values,
1826
+			$this
1827
+		);
1828
+		// set attributes as provided in $set_cols_n_values
1829
+		foreach ($set_cols_n_values as $column => $value) {
1830
+			$this->set($column, $value);
1831
+		}
1832
+		// no changes ? then don't do anything
1833
+		if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1834
+			return 0;
1835
+		}
1836
+		/**
1837
+		 * Saving a model object.
1838
+		 * Before we perform a save, this action is fired.
1839
+		 *
1840
+		 * @param EE_Base_Class $model_object the model object about to be saved.
1841
+		 */
1842
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
1843
+		if (! $this->allow_persist()) {
1844
+			return 0;
1845
+		}
1846
+		// now get current attribute values
1847
+		$save_cols_n_values = $this->_fields;
1848
+		// if the object already has an ID, update it. Otherwise, insert it
1849
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
1850
+		// They have been
1851
+		$old_assumption_concerning_value_preparation = $model
1852
+			->get_assumption_concerning_values_already_prepared_by_model_object();
1853
+		$model->assume_values_already_prepared_by_model_object(true);
1854
+		// does this model have an autoincrement PK?
1855
+		if ($model->has_primary_key_field()) {
1856
+			if ($model->get_primary_key_field()->is_auto_increment()) {
1857
+				// ok check if it's set, if so: update; if not, insert
1858
+				if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1859
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1860
+				} else {
1861
+					unset($save_cols_n_values[ $model->primary_key_name() ]);
1862
+					$results = $model->insert($save_cols_n_values);
1863
+					if ($results) {
1864
+						// if successful, set the primary key
1865
+						// but don't use the normal SET method, because it will check if
1866
+						// an item with the same ID exists in the mapper & db, then
1867
+						// will find it in the db (because we just added it) and THAT object
1868
+						// will get added to the mapper before we can add this one!
1869
+						// but if we just avoid using the SET method, all that headache can be avoided
1870
+						$pk_field_name = $model->primary_key_name();
1871
+						$this->_fields[ $pk_field_name ] = $results;
1872
+						$this->_clear_cached_property($pk_field_name);
1873
+						$model->add_to_entity_map($this);
1874
+						$this->_update_cached_related_model_objs_fks();
1875
+					}
1876
+				}
1877
+			} else {// PK is NOT auto-increment
1878
+				// so check if one like it already exists in the db
1879
+				if ($model->exists_by_ID($this->ID())) {
1880
+					if (WP_DEBUG && ! $this->in_entity_map()) {
1881
+						throw new EE_Error(
1882
+							sprintf(
1883
+								esc_html__(
1884
+									'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1885
+									'event_espresso'
1886
+								),
1887
+								get_class($this),
1888
+								get_class($model) . '::instance()->add_to_entity_map()',
1889
+								get_class($model) . '::instance()->get_one_by_ID()',
1890
+								'<br />'
1891
+							)
1892
+						);
1893
+					}
1894
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1895
+				} else {
1896
+					$results = $model->insert($save_cols_n_values);
1897
+					$this->_update_cached_related_model_objs_fks();
1898
+				}
1899
+			}
1900
+		} else {// there is NO primary key
1901
+			$already_in_db = false;
1902
+			foreach ($model->unique_indexes() as $index) {
1903
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1904
+				if ($model->exists(array($uniqueness_where_params))) {
1905
+					$already_in_db = true;
1906
+				}
1907
+			}
1908
+			if ($already_in_db) {
1909
+				$combined_pk_fields_n_values = array_intersect_key(
1910
+					$save_cols_n_values,
1911
+					$model->get_combined_primary_key_fields()
1912
+				);
1913
+				$results = $model->update(
1914
+					$save_cols_n_values,
1915
+					$combined_pk_fields_n_values
1916
+				);
1917
+			} else {
1918
+				$results = $model->insert($save_cols_n_values);
1919
+			}
1920
+		}
1921
+		// restore the old assumption about values being prepared by the model object
1922
+		$model->assume_values_already_prepared_by_model_object(
1923
+			$old_assumption_concerning_value_preparation
1924
+		);
1925
+		/**
1926
+		 * After saving the model object this action is called
1927
+		 *
1928
+		 * @param EE_Base_Class $model_object which was just saved
1929
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1930
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1931
+		 */
1932
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1933
+		$this->_has_changes = false;
1934
+		return $results;
1935
+	}
1936
+
1937
+
1938
+	/**
1939
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1940
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1941
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1942
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1943
+	 * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1944
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1945
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1946
+	 *
1947
+	 * @return void
1948
+	 * @throws ReflectionException
1949
+	 * @throws InvalidArgumentException
1950
+	 * @throws InvalidInterfaceException
1951
+	 * @throws InvalidDataTypeException
1952
+	 * @throws EE_Error
1953
+	 */
1954
+	protected function _update_cached_related_model_objs_fks()
1955
+	{
1956
+		$model = $this->get_model();
1957
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1958
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1959
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1960
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1961
+						$model->get_this_model_name()
1962
+					);
1963
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1964
+					if ($related_model_obj_in_cache->ID()) {
1965
+						$related_model_obj_in_cache->save();
1966
+					}
1967
+				}
1968
+			}
1969
+		}
1970
+	}
1971
+
1972
+
1973
+	/**
1974
+	 * Saves this model object and its NEW cached relations to the database.
1975
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1976
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
1977
+	 * because otherwise, there's a potential for infinite looping of saving
1978
+	 * Saves the cached related model objects, and ensures the relation between them
1979
+	 * and this object and properly setup
1980
+	 *
1981
+	 * @return int ID of new model object on save; 0 on failure+
1982
+	 * @throws ReflectionException
1983
+	 * @throws InvalidArgumentException
1984
+	 * @throws InvalidInterfaceException
1985
+	 * @throws InvalidDataTypeException
1986
+	 * @throws EE_Error
1987
+	 */
1988
+	public function save_new_cached_related_model_objs()
1989
+	{
1990
+		// make sure this has been saved
1991
+		if (! $this->ID()) {
1992
+			$id = $this->save();
1993
+		} else {
1994
+			$id = $this->ID();
1995
+		}
1996
+		// now save all the NEW cached model objects  (ie they don't exist in the DB)
1997
+		foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1998
+			if ($this->_model_relations[ $relationName ]) {
1999
+				// is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2000
+				// or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2001
+				/* @var $related_model_obj EE_Base_Class */
2002
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
2003
+					// add a relation to that relation type (which saves the appropriate thing in the process)
2004
+					// but ONLY if it DOES NOT exist in the DB
2005
+					$related_model_obj = $this->_model_relations[ $relationName ];
2006
+					// if( ! $related_model_obj->ID()){
2007
+					$this->_add_relation_to($related_model_obj, $relationName);
2008
+					$related_model_obj->save_new_cached_related_model_objs();
2009
+					// }
2010
+				} else {
2011
+					foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2012
+						// add a relation to that relation type (which saves the appropriate thing in the process)
2013
+						// but ONLY if it DOES NOT exist in the DB
2014
+						// if( ! $related_model_obj->ID()){
2015
+						$this->_add_relation_to($related_model_obj, $relationName);
2016
+						$related_model_obj->save_new_cached_related_model_objs();
2017
+						// }
2018
+					}
2019
+				}
2020
+			}
2021
+		}
2022
+		return $id;
2023
+	}
2024
+
2025
+
2026
+	/**
2027
+	 * for getting a model while instantiated.
2028
+	 *
2029
+	 * @return EEM_Base | EEM_CPT_Base
2030
+	 * @throws ReflectionException
2031
+	 * @throws InvalidArgumentException
2032
+	 * @throws InvalidInterfaceException
2033
+	 * @throws InvalidDataTypeException
2034
+	 * @throws EE_Error
2035
+	 */
2036
+	public function get_model()
2037
+	{
2038
+		if (! $this->_model) {
2039
+			$modelName = self::_get_model_classname(get_class($this));
2040
+			$this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2041
+		} else {
2042
+			$this->_model->set_timezone($this->_timezone);
2043
+		}
2044
+		return $this->_model;
2045
+	}
2046
+
2047
+
2048
+	/**
2049
+	 * @param $props_n_values
2050
+	 * @param $classname
2051
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2052
+	 * @throws ReflectionException
2053
+	 * @throws InvalidArgumentException
2054
+	 * @throws InvalidInterfaceException
2055
+	 * @throws InvalidDataTypeException
2056
+	 * @throws EE_Error
2057
+	 */
2058
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2059
+	{
2060
+		// TODO: will not work for Term_Relationships because they have no PK!
2061
+		$primary_id_ref = self::_get_primary_key_name($classname);
2062
+		if (
2063
+			array_key_exists($primary_id_ref, $props_n_values)
2064
+			&& ! empty($props_n_values[ $primary_id_ref ])
2065
+		) {
2066
+			$id = $props_n_values[ $primary_id_ref ];
2067
+			return self::_get_model($classname)->get_from_entity_map($id);
2068
+		}
2069
+		return false;
2070
+	}
2071
+
2072
+
2073
+	/**
2074
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2075
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2076
+	 * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2077
+	 * we return false.
2078
+	 *
2079
+	 * @param  array  $props_n_values   incoming array of properties and their values
2080
+	 * @param  string $classname        the classname of the child class
2081
+	 * @param null    $timezone
2082
+	 * @param array   $date_formats     incoming date_formats in an array where the first value is the
2083
+	 *                                  date_format and the second value is the time format
2084
+	 * @return mixed (EE_Base_Class|bool)
2085
+	 * @throws InvalidArgumentException
2086
+	 * @throws InvalidInterfaceException
2087
+	 * @throws InvalidDataTypeException
2088
+	 * @throws EE_Error
2089
+	 * @throws ReflectionException
2090
+	 * @throws ReflectionException
2091
+	 * @throws ReflectionException
2092
+	 */
2093
+	protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2094
+	{
2095
+		$existing = null;
2096
+		$model = self::_get_model($classname, $timezone);
2097
+		if ($model->has_primary_key_field()) {
2098
+			$primary_id_ref = self::_get_primary_key_name($classname);
2099
+			if (
2100
+				array_key_exists($primary_id_ref, $props_n_values)
2101
+				&& ! empty($props_n_values[ $primary_id_ref ])
2102
+			) {
2103
+				$existing = $model->get_one_by_ID(
2104
+					$props_n_values[ $primary_id_ref ]
2105
+				);
2106
+			}
2107
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2108
+			// no primary key on this model, but there's still a matching item in the DB
2109
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2110
+				self::_get_model($classname, $timezone)
2111
+					->get_index_primary_key_string($props_n_values)
2112
+			);
2113
+		}
2114
+		if ($existing) {
2115
+			// set date formats if present before setting values
2116
+			if (! empty($date_formats) && is_array($date_formats)) {
2117
+				$existing->set_date_format($date_formats[0]);
2118
+				$existing->set_time_format($date_formats[1]);
2119
+			} else {
2120
+				// set default formats for date and time
2121
+				$existing->set_date_format(get_option('date_format'));
2122
+				$existing->set_time_format(get_option('time_format'));
2123
+			}
2124
+			foreach ($props_n_values as $property => $field_value) {
2125
+				$existing->set($property, $field_value);
2126
+			}
2127
+			return $existing;
2128
+		}
2129
+		return false;
2130
+	}
2131
+
2132
+
2133
+	/**
2134
+	 * Gets the EEM_*_Model for this class
2135
+	 *
2136
+	 * @access public now, as this is more convenient
2137
+	 * @param      $classname
2138
+	 * @param null $timezone
2139
+	 * @throws ReflectionException
2140
+	 * @throws InvalidArgumentException
2141
+	 * @throws InvalidInterfaceException
2142
+	 * @throws InvalidDataTypeException
2143
+	 * @throws EE_Error
2144
+	 * @return EEM_Base
2145
+	 */
2146
+	protected static function _get_model($classname, $timezone = null)
2147
+	{
2148
+		// find model for this class
2149
+		if (! $classname) {
2150
+			throw new EE_Error(
2151
+				sprintf(
2152
+					esc_html__(
2153
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2154
+						'event_espresso'
2155
+					),
2156
+					$classname
2157
+				)
2158
+			);
2159
+		}
2160
+		$modelName = self::_get_model_classname($classname);
2161
+		return self::_get_model_instance_with_name($modelName, $timezone);
2162
+	}
2163
+
2164
+
2165
+	/**
2166
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2167
+	 *
2168
+	 * @param string $model_classname
2169
+	 * @param null   $timezone
2170
+	 * @return EEM_Base
2171
+	 * @throws ReflectionException
2172
+	 * @throws InvalidArgumentException
2173
+	 * @throws InvalidInterfaceException
2174
+	 * @throws InvalidDataTypeException
2175
+	 * @throws EE_Error
2176
+	 */
2177
+	protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2178
+	{
2179
+		$model_classname = str_replace('EEM_', '', $model_classname);
2180
+		$model = EE_Registry::instance()->load_model($model_classname);
2181
+		$model->set_timezone($timezone);
2182
+		return $model;
2183
+	}
2184
+
2185
+
2186
+	/**
2187
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
2188
+	 * Also works if a model class's classname is provided (eg EE_Registration).
2189
+	 *
2190
+	 * @param null $model_name
2191
+	 * @return string like EEM_Attendee
2192
+	 */
2193
+	private static function _get_model_classname($model_name = null)
2194
+	{
2195
+		if (strpos($model_name, 'EE_') === 0) {
2196
+			$model_classname = str_replace('EE_', 'EEM_', $model_name);
2197
+		} else {
2198
+			$model_classname = 'EEM_' . $model_name;
2199
+		}
2200
+		return $model_classname;
2201
+	}
2202
+
2203
+
2204
+	/**
2205
+	 * returns the name of the primary key attribute
2206
+	 *
2207
+	 * @param null $classname
2208
+	 * @throws ReflectionException
2209
+	 * @throws InvalidArgumentException
2210
+	 * @throws InvalidInterfaceException
2211
+	 * @throws InvalidDataTypeException
2212
+	 * @throws EE_Error
2213
+	 * @return string
2214
+	 */
2215
+	protected static function _get_primary_key_name($classname = null)
2216
+	{
2217
+		if (! $classname) {
2218
+			throw new EE_Error(
2219
+				sprintf(
2220
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2221
+					$classname
2222
+				)
2223
+			);
2224
+		}
2225
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
2226
+	}
2227
+
2228
+
2229
+	/**
2230
+	 * Gets the value of the primary key.
2231
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
2232
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2233
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2234
+	 *
2235
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2236
+	 * @throws ReflectionException
2237
+	 * @throws InvalidArgumentException
2238
+	 * @throws InvalidInterfaceException
2239
+	 * @throws InvalidDataTypeException
2240
+	 * @throws EE_Error
2241
+	 */
2242
+	public function ID()
2243
+	{
2244
+		$model = $this->get_model();
2245
+		// now that we know the name of the variable, use a variable variable to get its value and return its
2246
+		if ($model->has_primary_key_field()) {
2247
+			return $this->_fields[ $model->primary_key_name() ];
2248
+		}
2249
+		return $model->get_index_primary_key_string($this->_fields);
2250
+	}
2251
+
2252
+
2253
+	/**
2254
+	 * @param EE_Base_Class|int|string $otherModelObjectOrID
2255
+	 * @param string                   $relationName
2256
+	 * @return bool
2257
+	 * @throws EE_Error
2258
+	 * @throws ReflectionException
2259
+	 * @since   $VID:$
2260
+	 */
2261
+	public function hasRelation($otherModelObjectOrID, string $relationName): bool
2262
+	{
2263
+		$other_model = self::_get_model_instance_with_name(
2264
+			self::_get_model_classname($relationName),
2265
+			$this->_timezone
2266
+		);
2267
+		$primary_key = $other_model->primary_key_name();
2268
+		/** @var EE_Base_Class $otherModelObject */
2269
+		$otherModelObject = $other_model->ensure_is_obj($otherModelObjectOrID, $relationName);
2270
+		return $this->count_related($relationName, [[$primary_key => $otherModelObject->ID()]]) > 0;
2271
+	}
2272
+
2273
+
2274
+	/**
2275
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2276
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2277
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2278
+	 *
2279
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2280
+	 * @param string $relationName                     eg 'Events','Question',etc.
2281
+	 *                                                 an attendee to a group, you also want to specify which role they
2282
+	 *                                                 will have in that group. So you would use this parameter to
2283
+	 *                                                 specify array('role-column-name'=>'role-id')
2284
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2285
+	 *                                                 allow you to further constrict the relation to being added.
2286
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2287
+	 *                                                 column on the JOIN table and currently only the HABTM models
2288
+	 *                                                 accept these additional conditions.  Also remember that if an
2289
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2290
+	 *                                                 NEW row is created in the join table.
2291
+	 * @param null   $cache_id
2292
+	 * @throws ReflectionException
2293
+	 * @throws InvalidArgumentException
2294
+	 * @throws InvalidInterfaceException
2295
+	 * @throws InvalidDataTypeException
2296
+	 * @throws EE_Error
2297
+	 * @return EE_Base_Class the object the relation was added to
2298
+	 */
2299
+	public function _add_relation_to(
2300
+		$otherObjectModelObjectOrID,
2301
+		$relationName,
2302
+		$extra_join_model_fields_n_values = array(),
2303
+		$cache_id = null
2304
+	) {
2305
+		$model = $this->get_model();
2306
+		// if this thing exists in the DB, save the relation to the DB
2307
+		if ($this->ID()) {
2308
+			$otherObject = $model->add_relationship_to(
2309
+				$this,
2310
+				$otherObjectModelObjectOrID,
2311
+				$relationName,
2312
+				$extra_join_model_fields_n_values
2313
+			);
2314
+			// clear cache so future get_many_related and get_first_related() return new results.
2315
+			$this->clear_cache($relationName, $otherObject, true);
2316
+			if ($otherObject instanceof EE_Base_Class) {
2317
+				$otherObject->clear_cache($model->get_this_model_name(), $this);
2318
+			}
2319
+		} else {
2320
+			// this thing doesn't exist in the DB,  so just cache it
2321
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2322
+				throw new EE_Error(
2323
+					sprintf(
2324
+						esc_html__(
2325
+							'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2326
+							'event_espresso'
2327
+						),
2328
+						$otherObjectModelObjectOrID,
2329
+						get_class($this)
2330
+					)
2331
+				);
2332
+			}
2333
+			$otherObject = $otherObjectModelObjectOrID;
2334
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2335
+		}
2336
+		if ($otherObject instanceof EE_Base_Class) {
2337
+			// fix the reciprocal relation too
2338
+			if ($otherObject->ID()) {
2339
+				// its saved so assumed relations exist in the DB, so we can just
2340
+				// clear the cache so future queries use the updated info in the DB
2341
+				$otherObject->clear_cache(
2342
+					$model->get_this_model_name(),
2343
+					null,
2344
+					true
2345
+				);
2346
+			} else {
2347
+				// it's not saved, so it caches relations like this
2348
+				$otherObject->cache($model->get_this_model_name(), $this);
2349
+			}
2350
+		}
2351
+		return $otherObject;
2352
+	}
2353
+
2354
+
2355
+	/**
2356
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2357
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2358
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2359
+	 * from the cache
2360
+	 *
2361
+	 * @param mixed  $otherObjectModelObjectOrID
2362
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2363
+	 *                to the DB yet
2364
+	 * @param string $relationName
2365
+	 * @param array  $where_query
2366
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2367
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2368
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2369
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2370
+	 *                deleted.
2371
+	 * @return EE_Base_Class the relation was removed from
2372
+	 * @throws ReflectionException
2373
+	 * @throws InvalidArgumentException
2374
+	 * @throws InvalidInterfaceException
2375
+	 * @throws InvalidDataTypeException
2376
+	 * @throws EE_Error
2377
+	 */
2378
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2379
+	{
2380
+		if ($this->ID()) {
2381
+			// if this exists in the DB, save the relation change to the DB too
2382
+			$otherObject = $this->get_model()->remove_relationship_to(
2383
+				$this,
2384
+				$otherObjectModelObjectOrID,
2385
+				$relationName,
2386
+				$where_query
2387
+			);
2388
+			$this->clear_cache(
2389
+				$relationName,
2390
+				$otherObject
2391
+			);
2392
+		} else {
2393
+			// this doesn't exist in the DB, just remove it from the cache
2394
+			$otherObject = $this->clear_cache(
2395
+				$relationName,
2396
+				$otherObjectModelObjectOrID
2397
+			);
2398
+		}
2399
+		if ($otherObject instanceof EE_Base_Class) {
2400
+			$otherObject->clear_cache(
2401
+				$this->get_model()->get_this_model_name(),
2402
+				$this
2403
+			);
2404
+		}
2405
+		return $otherObject;
2406
+	}
2407
+
2408
+
2409
+	/**
2410
+	 * Removes ALL the related things for the $relationName.
2411
+	 *
2412
+	 * @param string $relationName
2413
+	 * @param array  $where_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2414
+	 * @return EE_Base_Class
2415
+	 * @throws ReflectionException
2416
+	 * @throws InvalidArgumentException
2417
+	 * @throws InvalidInterfaceException
2418
+	 * @throws InvalidDataTypeException
2419
+	 * @throws EE_Error
2420
+	 */
2421
+	public function _remove_relations($relationName, $where_query_params = array())
2422
+	{
2423
+		if ($this->ID()) {
2424
+			// if this exists in the DB, save the relation change to the DB too
2425
+			$otherObjects = $this->get_model()->remove_relations(
2426
+				$this,
2427
+				$relationName,
2428
+				$where_query_params
2429
+			);
2430
+			$this->clear_cache(
2431
+				$relationName,
2432
+				null,
2433
+				true
2434
+			);
2435
+		} else {
2436
+			// this doesn't exist in the DB, just remove it from the cache
2437
+			$otherObjects = $this->clear_cache(
2438
+				$relationName,
2439
+				null,
2440
+				true
2441
+			);
2442
+		}
2443
+		if (is_array($otherObjects)) {
2444
+			foreach ($otherObjects as $otherObject) {
2445
+				$otherObject->clear_cache(
2446
+					$this->get_model()->get_this_model_name(),
2447
+					$this
2448
+				);
2449
+			}
2450
+		}
2451
+		return $otherObjects;
2452
+	}
2453
+
2454
+
2455
+	/**
2456
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
2457
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2458
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2459
+	 * because we want to get even deleted items etc.
2460
+	 *
2461
+	 * @param string $relationName key in the model's _model_relations array
2462
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2463
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2464
+	 *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2465
+	 *                             results if you want IDs
2466
+	 * @throws ReflectionException
2467
+	 * @throws InvalidArgumentException
2468
+	 * @throws InvalidInterfaceException
2469
+	 * @throws InvalidDataTypeException
2470
+	 * @throws EE_Error
2471
+	 */
2472
+	public function get_many_related($relationName, $query_params = array())
2473
+	{
2474
+		if ($this->ID()) {
2475
+			// this exists in the DB, so get the related things from either the cache or the DB
2476
+			// if there are query parameters, forget about caching the related model objects.
2477
+			if ($query_params) {
2478
+				$related_model_objects = $this->get_model()->get_all_related(
2479
+					$this,
2480
+					$relationName,
2481
+					$query_params
2482
+				);
2483
+			} else {
2484
+				// did we already cache the result of this query?
2485
+				$cached_results = $this->get_all_from_cache($relationName);
2486
+				if (! $cached_results) {
2487
+					$related_model_objects = $this->get_model()->get_all_related(
2488
+						$this,
2489
+						$relationName,
2490
+						$query_params
2491
+					);
2492
+					// if no query parameters were passed, then we got all the related model objects
2493
+					// for that relation. We can cache them then.
2494
+					foreach ($related_model_objects as $related_model_object) {
2495
+						$this->cache($relationName, $related_model_object);
2496
+					}
2497
+				} else {
2498
+					$related_model_objects = $cached_results;
2499
+				}
2500
+			}
2501
+		} else {
2502
+			// this doesn't exist in the DB, so just get the related things from the cache
2503
+			$related_model_objects = $this->get_all_from_cache($relationName);
2504
+		}
2505
+		return $related_model_objects;
2506
+	}
2507
+
2508
+
2509
+	/**
2510
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2511
+	 * unless otherwise specified in the $query_params
2512
+	 *
2513
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2514
+	 * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2515
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2516
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2517
+	 *                               that by the setting $distinct to TRUE;
2518
+	 * @return int
2519
+	 * @throws ReflectionException
2520
+	 * @throws InvalidArgumentException
2521
+	 * @throws InvalidInterfaceException
2522
+	 * @throws InvalidDataTypeException
2523
+	 * @throws EE_Error
2524
+	 */
2525
+	public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2526
+	{
2527
+		return $this->get_model()->count_related(
2528
+			$this,
2529
+			$relation_name,
2530
+			$query_params,
2531
+			$field_to_count,
2532
+			$distinct
2533
+		);
2534
+	}
2535
+
2536
+
2537
+	/**
2538
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2539
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2540
+	 *
2541
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2542
+	 * @param array  $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2543
+	 * @param string $field_to_sum  name of field to count by.
2544
+	 *                              By default, uses primary key
2545
+	 *                              (which doesn't make much sense, so you should probably change it)
2546
+	 * @return int
2547
+	 * @throws ReflectionException
2548
+	 * @throws InvalidArgumentException
2549
+	 * @throws InvalidInterfaceException
2550
+	 * @throws InvalidDataTypeException
2551
+	 * @throws EE_Error
2552
+	 */
2553
+	public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2554
+	{
2555
+		return $this->get_model()->sum_related(
2556
+			$this,
2557
+			$relation_name,
2558
+			$query_params,
2559
+			$field_to_sum
2560
+		);
2561
+	}
2562
+
2563
+
2564
+	/**
2565
+	 * Gets the first (ie, one) related model object of the specified type.
2566
+	 *
2567
+	 * @param string $relationName key in the model's _model_relations array
2568
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2569
+	 * @return EE_Base_Class (not an array, a single object)
2570
+	 * @throws ReflectionException
2571
+	 * @throws InvalidArgumentException
2572
+	 * @throws InvalidInterfaceException
2573
+	 * @throws InvalidDataTypeException
2574
+	 * @throws EE_Error
2575
+	 */
2576
+	public function get_first_related($relationName, $query_params = array())
2577
+	{
2578
+		$model = $this->get_model();
2579
+		if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2580
+			// if they've provided some query parameters, don't bother trying to cache the result
2581
+			// also make sure we're not caching the result of get_first_related
2582
+			// on a relation which should have an array of objects (because the cache might have an array of objects)
2583
+			if (
2584
+				$query_params
2585
+				|| ! $model->related_settings_for($relationName)
2586
+					 instanceof
2587
+					 EE_Belongs_To_Relation
2588
+			) {
2589
+				$related_model_object = $model->get_first_related(
2590
+					$this,
2591
+					$relationName,
2592
+					$query_params
2593
+				);
2594
+			} else {
2595
+				// first, check if we've already cached the result of this query
2596
+				$cached_result = $this->get_one_from_cache($relationName);
2597
+				if (! $cached_result) {
2598
+					$related_model_object = $model->get_first_related(
2599
+						$this,
2600
+						$relationName,
2601
+						$query_params
2602
+					);
2603
+					$this->cache($relationName, $related_model_object);
2604
+				} else {
2605
+					$related_model_object = $cached_result;
2606
+				}
2607
+			}
2608
+		} else {
2609
+			$related_model_object = null;
2610
+			// this doesn't exist in the Db,
2611
+			// but maybe the relation is of type belongs to, and so the related thing might
2612
+			if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2613
+				$related_model_object = $model->get_first_related(
2614
+					$this,
2615
+					$relationName,
2616
+					$query_params
2617
+				);
2618
+			}
2619
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2620
+			// just get what's cached on this object
2621
+			if (! $related_model_object) {
2622
+				$related_model_object = $this->get_one_from_cache($relationName);
2623
+			}
2624
+		}
2625
+		return $related_model_object;
2626
+	}
2627
+
2628
+
2629
+	/**
2630
+	 * Does a delete on all related objects of type $relationName and removes
2631
+	 * the current model object's relation to them. If they can't be deleted (because
2632
+	 * of blocking related model objects) does nothing. If the related model objects are
2633
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2634
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2635
+	 *
2636
+	 * @param string $relationName
2637
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2638
+	 * @return int how many deleted
2639
+	 * @throws ReflectionException
2640
+	 * @throws InvalidArgumentException
2641
+	 * @throws InvalidInterfaceException
2642
+	 * @throws InvalidDataTypeException
2643
+	 * @throws EE_Error
2644
+	 */
2645
+	public function delete_related($relationName, $query_params = array())
2646
+	{
2647
+		if ($this->ID()) {
2648
+			$count = $this->get_model()->delete_related(
2649
+				$this,
2650
+				$relationName,
2651
+				$query_params
2652
+			);
2653
+		} else {
2654
+			$count = count($this->get_all_from_cache($relationName));
2655
+			$this->clear_cache($relationName, null, true);
2656
+		}
2657
+		return $count;
2658
+	}
2659
+
2660
+
2661
+	/**
2662
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2663
+	 * the current model object's relation to them. If they can't be deleted (because
2664
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2665
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2666
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2667
+	 *
2668
+	 * @param string $relationName
2669
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2670
+	 * @return int how many deleted (including those soft deleted)
2671
+	 * @throws ReflectionException
2672
+	 * @throws InvalidArgumentException
2673
+	 * @throws InvalidInterfaceException
2674
+	 * @throws InvalidDataTypeException
2675
+	 * @throws EE_Error
2676
+	 */
2677
+	public function delete_related_permanently($relationName, $query_params = array())
2678
+	{
2679
+		if ($this->ID()) {
2680
+			$count = $this->get_model()->delete_related_permanently(
2681
+				$this,
2682
+				$relationName,
2683
+				$query_params
2684
+			);
2685
+		} else {
2686
+			$count = count($this->get_all_from_cache($relationName));
2687
+		}
2688
+		$this->clear_cache($relationName, null, true);
2689
+		return $count;
2690
+	}
2691
+
2692
+
2693
+	/**
2694
+	 * is_set
2695
+	 * Just a simple utility function children can use for checking if property exists
2696
+	 *
2697
+	 * @access  public
2698
+	 * @param  string $field_name property to check
2699
+	 * @return bool                              TRUE if existing,FALSE if not.
2700
+	 */
2701
+	public function is_set($field_name)
2702
+	{
2703
+		return isset($this->_fields[ $field_name ]);
2704
+	}
2705
+
2706
+
2707
+	/**
2708
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2709
+	 * EE_Error exception if they don't
2710
+	 *
2711
+	 * @param  mixed (string|array) $properties properties to check
2712
+	 * @throws EE_Error
2713
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
2714
+	 */
2715
+	protected function _property_exists($properties)
2716
+	{
2717
+		foreach ((array) $properties as $property_name) {
2718
+			// first make sure this property exists
2719
+			if (! $this->_fields[ $property_name ]) {
2720
+				throw new EE_Error(
2721
+					sprintf(
2722
+						esc_html__(
2723
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2724
+							'event_espresso'
2725
+						),
2726
+						$property_name
2727
+					)
2728
+				);
2729
+			}
2730
+		}
2731
+		return true;
2732
+	}
2733
+
2734
+
2735
+	/**
2736
+	 * This simply returns an array of model fields for this object
2737
+	 *
2738
+	 * @return array
2739
+	 * @throws ReflectionException
2740
+	 * @throws InvalidArgumentException
2741
+	 * @throws InvalidInterfaceException
2742
+	 * @throws InvalidDataTypeException
2743
+	 * @throws EE_Error
2744
+	 */
2745
+	public function model_field_array()
2746
+	{
2747
+		$fields = $this->get_model()->field_settings(false);
2748
+		$properties = array();
2749
+		// remove prepended underscore
2750
+		foreach ($fields as $field_name => $settings) {
2751
+			$properties[ $field_name ] = $this->get($field_name);
2752
+		}
2753
+		return $properties;
2754
+	}
2755
+
2756
+
2757
+	/**
2758
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2759
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2760
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2761
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2762
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2763
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2764
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2765
+	 * and accepts 2 arguments: the object on which the function was called,
2766
+	 * and an array of the original arguments passed to the function.
2767
+	 * Whatever their callback function returns will be returned by this function.
2768
+	 * Example: in functions.php (or in a plugin):
2769
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2770
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2771
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2772
+	 *          return $previousReturnValue.$returnString;
2773
+	 *      }
2774
+	 * require('EE_Answer.class.php');
2775
+	 * echo EE_Answer::new_instance(['REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'])
2776
+	 *      ->my_callback('monkeys',100);
2777
+	 * // will output "you called my_callback! and passed args:monkeys,100"
2778
+	 *
2779
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2780
+	 * @param array  $args       array of original arguments passed to the function
2781
+	 * @throws EE_Error
2782
+	 * @return mixed whatever the plugin which calls add_filter decides
2783
+	 */
2784
+	public function __call($methodName, $args)
2785
+	{
2786
+		$className = get_class($this);
2787
+		$tagName = "FHEE__{$className}__{$methodName}";
2788
+		if (! has_filter($tagName)) {
2789
+			throw new EE_Error(
2790
+				sprintf(
2791
+					esc_html__(
2792
+						"Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2793
+						'event_espresso'
2794
+					),
2795
+					$methodName,
2796
+					$className,
2797
+					$tagName
2798
+				)
2799
+			);
2800
+		}
2801
+		return apply_filters($tagName, null, $this, $args);
2802
+	}
2803
+
2804
+
2805
+	/**
2806
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2807
+	 * A $previous_value can be specified in case there are many meta rows with the same key
2808
+	 *
2809
+	 * @param string $meta_key
2810
+	 * @param mixed  $meta_value
2811
+	 * @param mixed  $previous_value
2812
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2813
+	 *                  NOTE: if the values haven't changed, returns 0
2814
+	 * @throws InvalidArgumentException
2815
+	 * @throws InvalidInterfaceException
2816
+	 * @throws InvalidDataTypeException
2817
+	 * @throws EE_Error
2818
+	 * @throws ReflectionException
2819
+	 */
2820
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2821
+	{
2822
+		$query_params = array(
2823
+			array(
2824
+				'EXM_key'  => $meta_key,
2825
+				'OBJ_ID'   => $this->ID(),
2826
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2827
+			),
2828
+		);
2829
+		if ($previous_value !== null) {
2830
+			$query_params[0]['EXM_value'] = $meta_value;
2831
+		}
2832
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2833
+		if (! $existing_rows_like_that) {
2834
+			return $this->add_extra_meta($meta_key, $meta_value);
2835
+		}
2836
+		foreach ($existing_rows_like_that as $existing_row) {
2837
+			$existing_row->save(array('EXM_value' => $meta_value));
2838
+		}
2839
+		return count($existing_rows_like_that);
2840
+	}
2841
+
2842
+
2843
+	/**
2844
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2845
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
2846
+	 * extra meta row was entered, false if not
2847
+	 *
2848
+	 * @param string  $meta_key
2849
+	 * @param mixed   $meta_value
2850
+	 * @param boolean $unique
2851
+	 * @return boolean
2852
+	 * @throws InvalidArgumentException
2853
+	 * @throws InvalidInterfaceException
2854
+	 * @throws InvalidDataTypeException
2855
+	 * @throws EE_Error
2856
+	 * @throws ReflectionException
2857
+	 * @throws ReflectionException
2858
+	 */
2859
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
2860
+	{
2861
+		if ($unique) {
2862
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2863
+				array(
2864
+					array(
2865
+						'EXM_key'  => $meta_key,
2866
+						'OBJ_ID'   => $this->ID(),
2867
+						'EXM_type' => $this->get_model()->get_this_model_name(),
2868
+					),
2869
+				)
2870
+			);
2871
+			if ($existing_extra_meta) {
2872
+				return false;
2873
+			}
2874
+		}
2875
+		$new_extra_meta = EE_Extra_Meta::new_instance(
2876
+			array(
2877
+				'EXM_key'   => $meta_key,
2878
+				'EXM_value' => $meta_value,
2879
+				'OBJ_ID'    => $this->ID(),
2880
+				'EXM_type'  => $this->get_model()->get_this_model_name(),
2881
+			)
2882
+		);
2883
+		$new_extra_meta->save();
2884
+		return true;
2885
+	}
2886
+
2887
+
2888
+	/**
2889
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2890
+	 * is specified, only deletes extra meta records with that value.
2891
+	 *
2892
+	 * @param string $meta_key
2893
+	 * @param mixed  $meta_value
2894
+	 * @return int number of extra meta rows deleted
2895
+	 * @throws InvalidArgumentException
2896
+	 * @throws InvalidInterfaceException
2897
+	 * @throws InvalidDataTypeException
2898
+	 * @throws EE_Error
2899
+	 * @throws ReflectionException
2900
+	 */
2901
+	public function delete_extra_meta($meta_key, $meta_value = null)
2902
+	{
2903
+		$query_params = array(
2904
+			array(
2905
+				'EXM_key'  => $meta_key,
2906
+				'OBJ_ID'   => $this->ID(),
2907
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2908
+			),
2909
+		);
2910
+		if ($meta_value !== null) {
2911
+			$query_params[0]['EXM_value'] = $meta_value;
2912
+		}
2913
+		return EEM_Extra_Meta::instance()->delete($query_params);
2914
+	}
2915
+
2916
+
2917
+	/**
2918
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2919
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2920
+	 * You can specify $default is case you haven't found the extra meta
2921
+	 *
2922
+	 * @param string  $meta_key
2923
+	 * @param boolean $single
2924
+	 * @param mixed   $default if we don't find anything, what should we return?
2925
+	 * @return mixed single value if $single; array if ! $single
2926
+	 * @throws ReflectionException
2927
+	 * @throws InvalidArgumentException
2928
+	 * @throws InvalidInterfaceException
2929
+	 * @throws InvalidDataTypeException
2930
+	 * @throws EE_Error
2931
+	 */
2932
+	public function get_extra_meta($meta_key, $single = false, $default = null)
2933
+	{
2934
+		if ($single) {
2935
+			$result = $this->get_first_related(
2936
+				'Extra_Meta',
2937
+				array(array('EXM_key' => $meta_key))
2938
+			);
2939
+			if ($result instanceof EE_Extra_Meta) {
2940
+				return $result->value();
2941
+			}
2942
+		} else {
2943
+			$results = $this->get_many_related(
2944
+				'Extra_Meta',
2945
+				array(array('EXM_key' => $meta_key))
2946
+			);
2947
+			if ($results) {
2948
+				$values = array();
2949
+				foreach ($results as $result) {
2950
+					if ($result instanceof EE_Extra_Meta) {
2951
+						$values[ $result->ID() ] = $result->value();
2952
+					}
2953
+				}
2954
+				return $values;
2955
+			}
2956
+		}
2957
+		// if nothing discovered yet return default.
2958
+		return apply_filters(
2959
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
2960
+			$default,
2961
+			$meta_key,
2962
+			$single,
2963
+			$this
2964
+		);
2965
+	}
2966
+
2967
+
2968
+	/**
2969
+	 * Returns a simple array of all the extra meta associated with this model object.
2970
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2971
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2972
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2973
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2974
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2975
+	 * finally the extra meta's value as each sub-value. (eg
2976
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2977
+	 *
2978
+	 * @param boolean $one_of_each_key
2979
+	 * @return array
2980
+	 * @throws ReflectionException
2981
+	 * @throws InvalidArgumentException
2982
+	 * @throws InvalidInterfaceException
2983
+	 * @throws InvalidDataTypeException
2984
+	 * @throws EE_Error
2985
+	 */
2986
+	public function all_extra_meta_array($one_of_each_key = true)
2987
+	{
2988
+		$return_array = array();
2989
+		if ($one_of_each_key) {
2990
+			$extra_meta_objs = $this->get_many_related(
2991
+				'Extra_Meta',
2992
+				array('group_by' => 'EXM_key')
2993
+			);
2994
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2995
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2996
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2997
+				}
2998
+			}
2999
+		} else {
3000
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
3001
+			foreach ($extra_meta_objs as $extra_meta_obj) {
3002
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
3003
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
3004
+						$return_array[ $extra_meta_obj->key() ] = array();
3005
+					}
3006
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
3007
+				}
3008
+			}
3009
+		}
3010
+		return $return_array;
3011
+	}
3012
+
3013
+
3014
+	/**
3015
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
3016
+	 *
3017
+	 * @return string
3018
+	 * @throws ReflectionException
3019
+	 * @throws InvalidArgumentException
3020
+	 * @throws InvalidInterfaceException
3021
+	 * @throws InvalidDataTypeException
3022
+	 * @throws EE_Error
3023
+	 */
3024
+	public function name()
3025
+	{
3026
+		// find a field that's not a text field
3027
+		$field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
3028
+		if ($field_we_can_use) {
3029
+			return $this->get($field_we_can_use->get_name());
3030
+		}
3031
+		$first_few_properties = $this->model_field_array();
3032
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
3033
+		$name_parts = array();
3034
+		foreach ($first_few_properties as $name => $value) {
3035
+			$name_parts[] = "$name:$value";
3036
+		}
3037
+		return implode(',', $name_parts);
3038
+	}
3039
+
3040
+
3041
+	/**
3042
+	 * in_entity_map
3043
+	 * Checks if this model object has been proven to already be in the entity map
3044
+	 *
3045
+	 * @return boolean
3046
+	 * @throws ReflectionException
3047
+	 * @throws InvalidArgumentException
3048
+	 * @throws InvalidInterfaceException
3049
+	 * @throws InvalidDataTypeException
3050
+	 * @throws EE_Error
3051
+	 */
3052
+	public function in_entity_map()
3053
+	{
3054
+		// well, if we looked, did we find it in the entity map?
3055
+		return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3056
+	}
3057
+
3058
+
3059
+	/**
3060
+	 * refresh_from_db
3061
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
3062
+	 *
3063
+	 * @throws ReflectionException
3064
+	 * @throws InvalidArgumentException
3065
+	 * @throws InvalidInterfaceException
3066
+	 * @throws InvalidDataTypeException
3067
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3068
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3069
+	 */
3070
+	public function refresh_from_db()
3071
+	{
3072
+		if ($this->ID() && $this->in_entity_map()) {
3073
+			$this->get_model()->refresh_entity_map_from_db($this->ID());
3074
+		} else {
3075
+			// if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3076
+			// if it has an ID but it's not in the map, and you're asking me to refresh it
3077
+			// that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3078
+			// absolutely nothing in it for this ID
3079
+			if (WP_DEBUG) {
3080
+				throw new EE_Error(
3081
+					sprintf(
3082
+						esc_html__(
3083
+							'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3084
+							'event_espresso'
3085
+						),
3086
+						$this->ID(),
3087
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3088
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3089
+					)
3090
+				);
3091
+			}
3092
+		}
3093
+	}
3094
+
3095
+
3096
+	/**
3097
+	 * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3098
+	 *
3099
+	 * @since 4.9.80.p
3100
+	 * @param EE_Model_Field_Base[] $fields
3101
+	 * @param string $new_value_sql
3102
+	 *      example: 'column_name=123',
3103
+	 *      or 'column_name=column_name+1',
3104
+	 *      or 'column_name= CASE
3105
+	 *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3106
+	 *          THEN `column_name` + 5
3107
+	 *          ELSE `column_name`
3108
+	 *      END'
3109
+	 *      Also updates $field on this model object with the latest value from the database.
3110
+	 * @return bool
3111
+	 * @throws EE_Error
3112
+	 * @throws InvalidArgumentException
3113
+	 * @throws InvalidDataTypeException
3114
+	 * @throws InvalidInterfaceException
3115
+	 * @throws ReflectionException
3116
+	 */
3117
+	protected function updateFieldsInDB($fields, $new_value_sql)
3118
+	{
3119
+		// First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3120
+		// if it wasn't even there to start off.
3121
+		if (! $this->ID()) {
3122
+			$this->save();
3123
+		}
3124
+		global $wpdb;
3125
+		if (empty($fields)) {
3126
+			throw new InvalidArgumentException(
3127
+				esc_html__(
3128
+					'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3129
+					'event_espresso'
3130
+				)
3131
+			);
3132
+		}
3133
+		$first_field = reset($fields);
3134
+		$table_alias = $first_field->get_table_alias();
3135
+		foreach ($fields as $field) {
3136
+			if ($table_alias !== $field->get_table_alias()) {
3137
+				throw new InvalidArgumentException(
3138
+					sprintf(
3139
+						esc_html__(
3140
+							// @codingStandardsIgnoreStart
3141
+							'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
3142
+							// @codingStandardsIgnoreEnd
3143
+							'event_espresso'
3144
+						),
3145
+						$table_alias,
3146
+						$field->get_table_alias()
3147
+					)
3148
+				);
3149
+			}
3150
+		}
3151
+		// Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3152
+		$table_obj = $this->get_model()->get_table_obj_by_alias($table_alias);
3153
+		$table_pk_value = $this->ID();
3154
+		$table_name = $table_obj->get_table_name();
3155
+		if ($table_obj instanceof EE_Secondary_Table) {
3156
+			$table_pk_field_name = $table_obj->get_fk_on_table();
3157
+		} else {
3158
+			$table_pk_field_name = $table_obj->get_pk_column();
3159
+		}
3160
+
3161
+		$query =
3162
+			"UPDATE `{$table_name}`
3163 3163
             SET "
3164
-            . $new_value_sql
3165
-            . $wpdb->prepare(
3166
-                "
3164
+			. $new_value_sql
3165
+			. $wpdb->prepare(
3166
+				"
3167 3167
             WHERE `{$table_pk_field_name}` = %d;",
3168
-                $table_pk_value
3169
-            );
3170
-        $result = $wpdb->query($query);
3171
-        foreach ($fields as $field) {
3172
-            // If it was successful, we'd like to know the new value.
3173
-            // If it failed, we'd also like to know the new value.
3174
-            $new_value = $this->get_model()->get_var(
3175
-                $this->get_model()->alter_query_params_to_restrict_by_ID(
3176
-                    $this->get_model()->get_index_primary_key_string(
3177
-                        $this->model_field_array()
3178
-                    ),
3179
-                    array(
3180
-                        'default_where_conditions' => 'minimum',
3181
-                    )
3182
-                ),
3183
-                $field->get_name()
3184
-            );
3185
-            $this->set_from_db(
3186
-                $field->get_name(),
3187
-                $new_value
3188
-            );
3189
-        }
3190
-        return (bool) $result;
3191
-    }
3192
-
3193
-
3194
-    /**
3195
-     * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3196
-     * Does not allow negative values, however.
3197
-     *
3198
-     * @since 4.9.80.p
3199
-     * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3200
-     *                                   (positive or negative). One important gotcha: all these values must be
3201
-     *                                   on the same table (eg don't pass in one field for the posts table and
3202
-     *                                   another for the event meta table.)
3203
-     * @return bool
3204
-     * @throws EE_Error
3205
-     * @throws InvalidArgumentException
3206
-     * @throws InvalidDataTypeException
3207
-     * @throws InvalidInterfaceException
3208
-     * @throws ReflectionException
3209
-     */
3210
-    public function adjustNumericFieldsInDb(array $fields_n_quantities)
3211
-    {
3212
-        global $wpdb;
3213
-        if (empty($fields_n_quantities)) {
3214
-            // No fields to update? Well sure, we updated them to that value just fine.
3215
-            return true;
3216
-        }
3217
-        $fields = [];
3218
-        $set_sql_statements = [];
3219
-        foreach ($fields_n_quantities as $field_name => $quantity) {
3220
-            $field = $this->get_model()->field_settings_for($field_name, true);
3221
-            $fields[] = $field;
3222
-            $column_name = $field->get_table_column();
3223
-
3224
-            $abs_qty = absint($quantity);
3225
-            if ($quantity > 0) {
3226
-                // don't let the value be negative as often these fields are unsigned
3227
-                $set_sql_statements[] = $wpdb->prepare(
3228
-                    "`{$column_name}` = `{$column_name}` + %d",
3229
-                    $abs_qty
3230
-                );
3231
-            } else {
3232
-                $set_sql_statements[] = $wpdb->prepare(
3233
-                    "`{$column_name}` = CASE
3168
+				$table_pk_value
3169
+			);
3170
+		$result = $wpdb->query($query);
3171
+		foreach ($fields as $field) {
3172
+			// If it was successful, we'd like to know the new value.
3173
+			// If it failed, we'd also like to know the new value.
3174
+			$new_value = $this->get_model()->get_var(
3175
+				$this->get_model()->alter_query_params_to_restrict_by_ID(
3176
+					$this->get_model()->get_index_primary_key_string(
3177
+						$this->model_field_array()
3178
+					),
3179
+					array(
3180
+						'default_where_conditions' => 'minimum',
3181
+					)
3182
+				),
3183
+				$field->get_name()
3184
+			);
3185
+			$this->set_from_db(
3186
+				$field->get_name(),
3187
+				$new_value
3188
+			);
3189
+		}
3190
+		return (bool) $result;
3191
+	}
3192
+
3193
+
3194
+	/**
3195
+	 * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3196
+	 * Does not allow negative values, however.
3197
+	 *
3198
+	 * @since 4.9.80.p
3199
+	 * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3200
+	 *                                   (positive or negative). One important gotcha: all these values must be
3201
+	 *                                   on the same table (eg don't pass in one field for the posts table and
3202
+	 *                                   another for the event meta table.)
3203
+	 * @return bool
3204
+	 * @throws EE_Error
3205
+	 * @throws InvalidArgumentException
3206
+	 * @throws InvalidDataTypeException
3207
+	 * @throws InvalidInterfaceException
3208
+	 * @throws ReflectionException
3209
+	 */
3210
+	public function adjustNumericFieldsInDb(array $fields_n_quantities)
3211
+	{
3212
+		global $wpdb;
3213
+		if (empty($fields_n_quantities)) {
3214
+			// No fields to update? Well sure, we updated them to that value just fine.
3215
+			return true;
3216
+		}
3217
+		$fields = [];
3218
+		$set_sql_statements = [];
3219
+		foreach ($fields_n_quantities as $field_name => $quantity) {
3220
+			$field = $this->get_model()->field_settings_for($field_name, true);
3221
+			$fields[] = $field;
3222
+			$column_name = $field->get_table_column();
3223
+
3224
+			$abs_qty = absint($quantity);
3225
+			if ($quantity > 0) {
3226
+				// don't let the value be negative as often these fields are unsigned
3227
+				$set_sql_statements[] = $wpdb->prepare(
3228
+					"`{$column_name}` = `{$column_name}` + %d",
3229
+					$abs_qty
3230
+				);
3231
+			} else {
3232
+				$set_sql_statements[] = $wpdb->prepare(
3233
+					"`{$column_name}` = CASE
3234 3234
                        WHEN (`{$column_name}` >= %d)
3235 3235
                        THEN `{$column_name}` - %d
3236 3236
                        ELSE 0
3237 3237
                     END",
3238
-                    $abs_qty,
3239
-                    $abs_qty
3240
-                );
3241
-            }
3242
-        }
3243
-        return $this->updateFieldsInDB(
3244
-            $fields,
3245
-            implode(', ', $set_sql_statements)
3246
-        );
3247
-    }
3248
-
3249
-
3250
-    /**
3251
-     * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3252
-     * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3253
-     * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3254
-     * Returns true if the value was successfully bumped, and updates the value on this model object.
3255
-     * Otherwise returns false.
3256
-     *
3257
-     * @since 4.9.80.p
3258
-     * @param string $field_name_to_bump
3259
-     * @param string $field_name_affecting_total
3260
-     * @param string $limit_field_name
3261
-     * @param int    $quantity
3262
-     * @return bool
3263
-     * @throws EE_Error
3264
-     * @throws InvalidArgumentException
3265
-     * @throws InvalidDataTypeException
3266
-     * @throws InvalidInterfaceException
3267
-     * @throws ReflectionException
3268
-     */
3269
-    public function incrementFieldConditionallyInDb($field_name_to_bump, $field_name_affecting_total, $limit_field_name, $quantity)
3270
-    {
3271
-        global $wpdb;
3272
-        $field = $this->get_model()->field_settings_for($field_name_to_bump, true);
3273
-        $column_name = $field->get_table_column();
3274
-
3275
-        $field_affecting_total = $this->get_model()->field_settings_for($field_name_affecting_total, true);
3276
-        $column_affecting_total = $field_affecting_total->get_table_column();
3277
-
3278
-        $limiting_field = $this->get_model()->field_settings_for($limit_field_name, true);
3279
-        $limiting_column = $limiting_field->get_table_column();
3280
-        return $this->updateFieldsInDB(
3281
-            [$field],
3282
-            $wpdb->prepare(
3283
-                "`{$column_name}` =
3238
+					$abs_qty,
3239
+					$abs_qty
3240
+				);
3241
+			}
3242
+		}
3243
+		return $this->updateFieldsInDB(
3244
+			$fields,
3245
+			implode(', ', $set_sql_statements)
3246
+		);
3247
+	}
3248
+
3249
+
3250
+	/**
3251
+	 * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3252
+	 * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3253
+	 * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3254
+	 * Returns true if the value was successfully bumped, and updates the value on this model object.
3255
+	 * Otherwise returns false.
3256
+	 *
3257
+	 * @since 4.9.80.p
3258
+	 * @param string $field_name_to_bump
3259
+	 * @param string $field_name_affecting_total
3260
+	 * @param string $limit_field_name
3261
+	 * @param int    $quantity
3262
+	 * @return bool
3263
+	 * @throws EE_Error
3264
+	 * @throws InvalidArgumentException
3265
+	 * @throws InvalidDataTypeException
3266
+	 * @throws InvalidInterfaceException
3267
+	 * @throws ReflectionException
3268
+	 */
3269
+	public function incrementFieldConditionallyInDb($field_name_to_bump, $field_name_affecting_total, $limit_field_name, $quantity)
3270
+	{
3271
+		global $wpdb;
3272
+		$field = $this->get_model()->field_settings_for($field_name_to_bump, true);
3273
+		$column_name = $field->get_table_column();
3274
+
3275
+		$field_affecting_total = $this->get_model()->field_settings_for($field_name_affecting_total, true);
3276
+		$column_affecting_total = $field_affecting_total->get_table_column();
3277
+
3278
+		$limiting_field = $this->get_model()->field_settings_for($limit_field_name, true);
3279
+		$limiting_column = $limiting_field->get_table_column();
3280
+		return $this->updateFieldsInDB(
3281
+			[$field],
3282
+			$wpdb->prepare(
3283
+				"`{$column_name}` =
3284 3284
             CASE
3285 3285
                WHEN ((`{$column_name}` + `{$column_affecting_total}` + %d) <= `{$limiting_column}`) OR `{$limiting_column}` = %d
3286 3286
                THEN `{$column_name}` + %d
3287 3287
                ELSE `{$column_name}`
3288 3288
             END",
3289
-                $quantity,
3290
-                EE_INF_IN_DB,
3291
-                $quantity
3292
-            )
3293
-        );
3294
-    }
3295
-
3296
-
3297
-    /**
3298
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3299
-     * (probably a bad assumption they have made, oh well)
3300
-     *
3301
-     * @return string
3302
-     */
3303
-    public function __toString()
3304
-    {
3305
-        try {
3306
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3307
-        } catch (Exception $e) {
3308
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3309
-            return '';
3310
-        }
3311
-    }
3312
-
3313
-
3314
-    /**
3315
-     * Clear related model objects if they're already in the DB, because otherwise when we
3316
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3317
-     * This means if we have made changes to those related model objects, and want to unserialize
3318
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3319
-     * Instead, those related model objects should be directly serialized and stored.
3320
-     * Eg, the following won't work:
3321
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3322
-     * $att = $reg->attendee();
3323
-     * $att->set( 'ATT_fname', 'Dirk' );
3324
-     * update_option( 'my_option', serialize( $reg ) );
3325
-     * //END REQUEST
3326
-     * //START NEXT REQUEST
3327
-     * $reg = get_option( 'my_option' );
3328
-     * $reg->attendee()->save();
3329
-     * And would need to be replace with:
3330
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3331
-     * $att = $reg->attendee();
3332
-     * $att->set( 'ATT_fname', 'Dirk' );
3333
-     * update_option( 'my_option', serialize( $reg ) );
3334
-     * //END REQUEST
3335
-     * //START NEXT REQUEST
3336
-     * $att = get_option( 'my_option' );
3337
-     * $att->save();
3338
-     *
3339
-     * @return array
3340
-     * @throws ReflectionException
3341
-     * @throws InvalidArgumentException
3342
-     * @throws InvalidInterfaceException
3343
-     * @throws InvalidDataTypeException
3344
-     * @throws EE_Error
3345
-     */
3346
-    public function __sleep()
3347
-    {
3348
-        $model = $this->get_model();
3349
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3350
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3351
-                $classname = 'EE_' . $model->get_this_model_name();
3352
-                if (
3353
-                    $this->get_one_from_cache($relation_name) instanceof $classname
3354
-                    && $this->get_one_from_cache($relation_name)->ID()
3355
-                ) {
3356
-                    $this->clear_cache(
3357
-                        $relation_name,
3358
-                        $this->get_one_from_cache($relation_name)->ID()
3359
-                    );
3360
-                }
3361
-            }
3362
-        }
3363
-        $this->_props_n_values_provided_in_constructor = array();
3364
-        $properties_to_serialize = get_object_vars($this);
3365
-        // don't serialize the model. It's big and that risks recursion
3366
-        unset($properties_to_serialize['_model']);
3367
-        return array_keys($properties_to_serialize);
3368
-    }
3369
-
3370
-
3371
-    /**
3372
-     * restore _props_n_values_provided_in_constructor
3373
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3374
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3375
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3376
-     */
3377
-    public function __wakeup()
3378
-    {
3379
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3380
-    }
3381
-
3382
-
3383
-    /**
3384
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3385
-     * distinct with the clone host instance are also cloned.
3386
-     */
3387
-    public function __clone()
3388
-    {
3389
-        // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3390
-        foreach ($this->_fields as $field => $value) {
3391
-            if ($value instanceof DateTime) {
3392
-                $this->_fields[ $field ] = clone $value;
3393
-            }
3394
-        }
3395
-    }
3289
+				$quantity,
3290
+				EE_INF_IN_DB,
3291
+				$quantity
3292
+			)
3293
+		);
3294
+	}
3295
+
3296
+
3297
+	/**
3298
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3299
+	 * (probably a bad assumption they have made, oh well)
3300
+	 *
3301
+	 * @return string
3302
+	 */
3303
+	public function __toString()
3304
+	{
3305
+		try {
3306
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3307
+		} catch (Exception $e) {
3308
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3309
+			return '';
3310
+		}
3311
+	}
3312
+
3313
+
3314
+	/**
3315
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3316
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3317
+	 * This means if we have made changes to those related model objects, and want to unserialize
3318
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3319
+	 * Instead, those related model objects should be directly serialized and stored.
3320
+	 * Eg, the following won't work:
3321
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3322
+	 * $att = $reg->attendee();
3323
+	 * $att->set( 'ATT_fname', 'Dirk' );
3324
+	 * update_option( 'my_option', serialize( $reg ) );
3325
+	 * //END REQUEST
3326
+	 * //START NEXT REQUEST
3327
+	 * $reg = get_option( 'my_option' );
3328
+	 * $reg->attendee()->save();
3329
+	 * And would need to be replace with:
3330
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3331
+	 * $att = $reg->attendee();
3332
+	 * $att->set( 'ATT_fname', 'Dirk' );
3333
+	 * update_option( 'my_option', serialize( $reg ) );
3334
+	 * //END REQUEST
3335
+	 * //START NEXT REQUEST
3336
+	 * $att = get_option( 'my_option' );
3337
+	 * $att->save();
3338
+	 *
3339
+	 * @return array
3340
+	 * @throws ReflectionException
3341
+	 * @throws InvalidArgumentException
3342
+	 * @throws InvalidInterfaceException
3343
+	 * @throws InvalidDataTypeException
3344
+	 * @throws EE_Error
3345
+	 */
3346
+	public function __sleep()
3347
+	{
3348
+		$model = $this->get_model();
3349
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3350
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3351
+				$classname = 'EE_' . $model->get_this_model_name();
3352
+				if (
3353
+					$this->get_one_from_cache($relation_name) instanceof $classname
3354
+					&& $this->get_one_from_cache($relation_name)->ID()
3355
+				) {
3356
+					$this->clear_cache(
3357
+						$relation_name,
3358
+						$this->get_one_from_cache($relation_name)->ID()
3359
+					);
3360
+				}
3361
+			}
3362
+		}
3363
+		$this->_props_n_values_provided_in_constructor = array();
3364
+		$properties_to_serialize = get_object_vars($this);
3365
+		// don't serialize the model. It's big and that risks recursion
3366
+		unset($properties_to_serialize['_model']);
3367
+		return array_keys($properties_to_serialize);
3368
+	}
3369
+
3370
+
3371
+	/**
3372
+	 * restore _props_n_values_provided_in_constructor
3373
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3374
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3375
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3376
+	 */
3377
+	public function __wakeup()
3378
+	{
3379
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3380
+	}
3381
+
3382
+
3383
+	/**
3384
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3385
+	 * distinct with the clone host instance are also cloned.
3386
+	 */
3387
+	public function __clone()
3388
+	{
3389
+		// handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3390
+		foreach ($this->_fields as $field => $value) {
3391
+			if ($value instanceof DateTime) {
3392
+				$this->_fields[ $field ] = clone $value;
3393
+			}
3394
+		}
3395
+	}
3396 3396
 }
Please login to merge, or discard this patch.
core/EE_Error.core.php 2 patches
Indentation   +1129 added lines, -1129 removed lines patch added patch discarded remove patch
@@ -12,8 +12,8 @@  discard block
 block discarded – undo
12 12
 // if you're a dev and want to receive all errors via email
13 13
 // add this to your wp-config.php: define( 'EE_ERROR_EMAILS', TRUE );
14 14
 if (defined('WP_DEBUG') && WP_DEBUG === true && defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS === true) {
15
-    set_error_handler(array('EE_Error', 'error_handler'));
16
-    register_shutdown_function(array('EE_Error', 'fatal_error_handler'));
15
+	set_error_handler(array('EE_Error', 'error_handler'));
16
+	register_shutdown_function(array('EE_Error', 'fatal_error_handler'));
17 17
 }
18 18
 
19 19
 
@@ -26,266 +26,266 @@  discard block
 block discarded – undo
26 26
  */
27 27
 class EE_Error extends Exception
28 28
 {
29
-    const OPTIONS_KEY_NOTICES = 'ee_notices';
30
-
31
-
32
-    /**
33
-     * name of the file to log exceptions to
34
-     *
35
-     * @var string
36
-     */
37
-    private static $_exception_log_file = 'espresso_error_log.txt';
38
-
39
-    /**
40
-     *    stores details for all exception
41
-     *
42
-     * @var array
43
-     */
44
-    private static $_all_exceptions = array();
45
-
46
-    /**
47
-     *    tracks number of errors
48
-     *
49
-     * @var int
50
-     */
51
-    private static $_error_count = 0;
52
-
53
-    /**
54
-     * @var array $_espresso_notices
55
-     */
56
-    private static $_espresso_notices = array('success' => false, 'errors' => false, 'attention' => false);
57
-
58
-
59
-    /**
60
-     * @override default exception handling
61
-     * @param string         $message
62
-     * @param int            $code
63
-     * @param Exception|null $previous
64
-     */
65
-    public function __construct($message, $code = 0, Exception $previous = null)
66
-    {
67
-        if (version_compare(PHP_VERSION, '5.3.0', '<')) {
68
-            parent::__construct($message, $code);
69
-        } else {
70
-            parent::__construct($message, $code, $previous);
71
-        }
72
-    }
73
-
74
-
75
-    /**
76
-     *    error_handler
77
-     *
78
-     * @param $code
79
-     * @param $message
80
-     * @param $file
81
-     * @param $line
82
-     * @return void
83
-     */
84
-    public static function error_handler($code, $message, $file, $line)
85
-    {
86
-        $type = EE_Error::error_type($code);
87
-        $site = site_url();
88
-        switch ($site) {
89
-            case 'http://ee4.eventespresso.com/':
90
-            case 'http://ee4decaf.eventespresso.com/':
91
-            case 'http://ee4hf.eventespresso.com/':
92
-            case 'http://ee4a.eventespresso.com/':
93
-            case 'http://ee4ad.eventespresso.com/':
94
-            case 'http://ee4b.eventespresso.com/':
95
-            case 'http://ee4bd.eventespresso.com/':
96
-            case 'http://ee4d.eventespresso.com/':
97
-            case 'http://ee4dd.eventespresso.com/':
98
-                $to = '[email protected]';
99
-                break;
100
-            default:
101
-                $to = get_option('admin_email');
102
-        }
103
-        $subject = $type . ' ' . $message . ' in ' . EVENT_ESPRESSO_VERSION . ' on ' . site_url();
104
-        $msg = EE_Error::_format_error($type, $message, $file, $line);
105
-        if (function_exists('wp_mail')) {
106
-            add_filter('wp_mail_content_type', array('EE_Error', 'set_content_type'));
107
-            wp_mail($to, $subject, $msg);
108
-        }
109
-        echo '<div id="message" class="espresso-notices error"><p>';
110
-        echo wp_kses($type . ': ' . $message . '<br />' . $file . ' line ' . $line, AllowedTags::getWithFormTags());
111
-        echo '<br /></p></div>';
112
-    }
113
-
114
-
115
-    /**
116
-     * error_type
117
-     * http://www.php.net/manual/en/errorfunc.constants.php#109430
118
-     *
119
-     * @param $code
120
-     * @return string
121
-     */
122
-    public static function error_type($code)
123
-    {
124
-        switch ($code) {
125
-            case E_ERROR: // 1 //
126
-                return 'E_ERROR';
127
-            case E_WARNING: // 2 //
128
-                return 'E_WARNING';
129
-            case E_PARSE: // 4 //
130
-                return 'E_PARSE';
131
-            case E_NOTICE: // 8 //
132
-                return 'E_NOTICE';
133
-            case E_CORE_ERROR: // 16 //
134
-                return 'E_CORE_ERROR';
135
-            case E_CORE_WARNING: // 32 //
136
-                return 'E_CORE_WARNING';
137
-            case E_COMPILE_ERROR: // 64 //
138
-                return 'E_COMPILE_ERROR';
139
-            case E_COMPILE_WARNING: // 128 //
140
-                return 'E_COMPILE_WARNING';
141
-            case E_USER_ERROR: // 256 //
142
-                return 'E_USER_ERROR';
143
-            case E_USER_WARNING: // 512 //
144
-                return 'E_USER_WARNING';
145
-            case E_USER_NOTICE: // 1024 //
146
-                return 'E_USER_NOTICE';
147
-            case E_STRICT: // 2048 //
148
-                return 'E_STRICT';
149
-            case E_RECOVERABLE_ERROR: // 4096 //
150
-                return 'E_RECOVERABLE_ERROR';
151
-            case E_DEPRECATED: // 8192 //
152
-                return 'E_DEPRECATED';
153
-            case E_USER_DEPRECATED: // 16384 //
154
-                return 'E_USER_DEPRECATED';
155
-            case E_ALL: // 16384 //
156
-                return 'E_ALL';
157
-        }
158
-        return '';
159
-    }
160
-
161
-
162
-    /**
163
-     *    fatal_error_handler
164
-     *
165
-     * @return void
166
-     */
167
-    public static function fatal_error_handler()
168
-    {
169
-        $last_error = error_get_last();
170
-        if ($last_error['type'] === E_ERROR) {
171
-            EE_Error::error_handler(E_ERROR, $last_error['message'], $last_error['file'], $last_error['line']);
172
-        }
173
-    }
174
-
175
-
176
-    /**
177
-     * _format_error
178
-     *
179
-     * @param $code
180
-     * @param $message
181
-     * @param $file
182
-     * @param $line
183
-     * @return string
184
-     */
185
-    private static function _format_error($code, $message, $file, $line)
186
-    {
187
-        $html = "<table cellpadding='5'><thead bgcolor='#f8f8f8'><th>Item</th><th align='left'>Details</th></thead><tbody>";
188
-        $html .= "<tr valign='top'><td><b>Code</b></td><td>$code</td></tr>";
189
-        $html .= "<tr valign='top'><td><b>Error</b></td><td>$message</td></tr>";
190
-        $html .= "<tr valign='top'><td><b>File</b></td><td>$file</td></tr>";
191
-        $html .= "<tr valign='top'><td><b>Line</b></td><td>$line</td></tr>";
192
-        $html .= '</tbody></table>';
193
-        return $html;
194
-    }
195
-
196
-
197
-    /**
198
-     * set_content_type
199
-     *
200
-     * @param $content_type
201
-     * @return string
202
-     */
203
-    public static function set_content_type($content_type)
204
-    {
205
-        return 'text/html';
206
-    }
207
-
208
-
209
-    /**
210
-     * @return void
211
-     * @throws EE_Error
212
-     * @throws ReflectionException
213
-     */
214
-    public function get_error()
215
-    {
216
-        if (apply_filters('FHEE__EE_Error__get_error__show_normal_exceptions', false)) {
217
-            throw $this;
218
-        }
219
-        // get separate user and developer messages if they exist
220
-        $msg = explode('||', $this->getMessage());
221
-        $user_msg = $msg[0];
222
-        $dev_msg = isset($msg[1]) ? $msg[1] : $msg[0];
223
-        $msg = WP_DEBUG ? $dev_msg : $user_msg;
224
-        // add details to _all_exceptions array
225
-        $x_time = time();
226
-        self::$_all_exceptions[ $x_time ]['name'] = get_class($this);
227
-        self::$_all_exceptions[ $x_time ]['file'] = $this->getFile();
228
-        self::$_all_exceptions[ $x_time ]['line'] = $this->getLine();
229
-        self::$_all_exceptions[ $x_time ]['msg'] = $msg;
230
-        self::$_all_exceptions[ $x_time ]['code'] = $this->getCode();
231
-        self::$_all_exceptions[ $x_time ]['trace'] = $this->getTrace();
232
-        self::$_all_exceptions[ $x_time ]['string'] = $this->getTraceAsString();
233
-        self::$_error_count++;
234
-        // add_action( 'shutdown', array( $this, 'display_errors' ));
235
-        $this->display_errors();
236
-    }
237
-
238
-
239
-    /**
240
-     * @param bool   $check_stored
241
-     * @param string $type_to_check
242
-     * @return bool
243
-     * @throws InvalidInterfaceException
244
-     * @throws InvalidArgumentException
245
-     * @throws InvalidDataTypeException
246
-     */
247
-    public static function has_error($check_stored = false, $type_to_check = 'errors')
248
-    {
249
-        $has_error = isset(self::$_espresso_notices[ $type_to_check ])
250
-                     && ! empty(self::$_espresso_notices[ $type_to_check ])
251
-            ? true
252
-            : false;
253
-        if ($check_stored && ! $has_error) {
254
-            $notices = EE_Error::getStoredNotices();
255
-            foreach ($notices as $type => $notice) {
256
-                if ($type === $type_to_check && $notice) {
257
-                    return true;
258
-                }
259
-            }
260
-        }
261
-        return $has_error;
262
-    }
263
-
264
-
265
-    /**
266
-     * @echo string
267
-     * @throws ReflectionException
268
-     */
269
-    public function display_errors()
270
-    {
271
-        $trace_details = '';
272
-        $output = '
29
+	const OPTIONS_KEY_NOTICES = 'ee_notices';
30
+
31
+
32
+	/**
33
+	 * name of the file to log exceptions to
34
+	 *
35
+	 * @var string
36
+	 */
37
+	private static $_exception_log_file = 'espresso_error_log.txt';
38
+
39
+	/**
40
+	 *    stores details for all exception
41
+	 *
42
+	 * @var array
43
+	 */
44
+	private static $_all_exceptions = array();
45
+
46
+	/**
47
+	 *    tracks number of errors
48
+	 *
49
+	 * @var int
50
+	 */
51
+	private static $_error_count = 0;
52
+
53
+	/**
54
+	 * @var array $_espresso_notices
55
+	 */
56
+	private static $_espresso_notices = array('success' => false, 'errors' => false, 'attention' => false);
57
+
58
+
59
+	/**
60
+	 * @override default exception handling
61
+	 * @param string         $message
62
+	 * @param int            $code
63
+	 * @param Exception|null $previous
64
+	 */
65
+	public function __construct($message, $code = 0, Exception $previous = null)
66
+	{
67
+		if (version_compare(PHP_VERSION, '5.3.0', '<')) {
68
+			parent::__construct($message, $code);
69
+		} else {
70
+			parent::__construct($message, $code, $previous);
71
+		}
72
+	}
73
+
74
+
75
+	/**
76
+	 *    error_handler
77
+	 *
78
+	 * @param $code
79
+	 * @param $message
80
+	 * @param $file
81
+	 * @param $line
82
+	 * @return void
83
+	 */
84
+	public static function error_handler($code, $message, $file, $line)
85
+	{
86
+		$type = EE_Error::error_type($code);
87
+		$site = site_url();
88
+		switch ($site) {
89
+			case 'http://ee4.eventespresso.com/':
90
+			case 'http://ee4decaf.eventespresso.com/':
91
+			case 'http://ee4hf.eventespresso.com/':
92
+			case 'http://ee4a.eventespresso.com/':
93
+			case 'http://ee4ad.eventespresso.com/':
94
+			case 'http://ee4b.eventespresso.com/':
95
+			case 'http://ee4bd.eventespresso.com/':
96
+			case 'http://ee4d.eventespresso.com/':
97
+			case 'http://ee4dd.eventespresso.com/':
98
+				$to = '[email protected]';
99
+				break;
100
+			default:
101
+				$to = get_option('admin_email');
102
+		}
103
+		$subject = $type . ' ' . $message . ' in ' . EVENT_ESPRESSO_VERSION . ' on ' . site_url();
104
+		$msg = EE_Error::_format_error($type, $message, $file, $line);
105
+		if (function_exists('wp_mail')) {
106
+			add_filter('wp_mail_content_type', array('EE_Error', 'set_content_type'));
107
+			wp_mail($to, $subject, $msg);
108
+		}
109
+		echo '<div id="message" class="espresso-notices error"><p>';
110
+		echo wp_kses($type . ': ' . $message . '<br />' . $file . ' line ' . $line, AllowedTags::getWithFormTags());
111
+		echo '<br /></p></div>';
112
+	}
113
+
114
+
115
+	/**
116
+	 * error_type
117
+	 * http://www.php.net/manual/en/errorfunc.constants.php#109430
118
+	 *
119
+	 * @param $code
120
+	 * @return string
121
+	 */
122
+	public static function error_type($code)
123
+	{
124
+		switch ($code) {
125
+			case E_ERROR: // 1 //
126
+				return 'E_ERROR';
127
+			case E_WARNING: // 2 //
128
+				return 'E_WARNING';
129
+			case E_PARSE: // 4 //
130
+				return 'E_PARSE';
131
+			case E_NOTICE: // 8 //
132
+				return 'E_NOTICE';
133
+			case E_CORE_ERROR: // 16 //
134
+				return 'E_CORE_ERROR';
135
+			case E_CORE_WARNING: // 32 //
136
+				return 'E_CORE_WARNING';
137
+			case E_COMPILE_ERROR: // 64 //
138
+				return 'E_COMPILE_ERROR';
139
+			case E_COMPILE_WARNING: // 128 //
140
+				return 'E_COMPILE_WARNING';
141
+			case E_USER_ERROR: // 256 //
142
+				return 'E_USER_ERROR';
143
+			case E_USER_WARNING: // 512 //
144
+				return 'E_USER_WARNING';
145
+			case E_USER_NOTICE: // 1024 //
146
+				return 'E_USER_NOTICE';
147
+			case E_STRICT: // 2048 //
148
+				return 'E_STRICT';
149
+			case E_RECOVERABLE_ERROR: // 4096 //
150
+				return 'E_RECOVERABLE_ERROR';
151
+			case E_DEPRECATED: // 8192 //
152
+				return 'E_DEPRECATED';
153
+			case E_USER_DEPRECATED: // 16384 //
154
+				return 'E_USER_DEPRECATED';
155
+			case E_ALL: // 16384 //
156
+				return 'E_ALL';
157
+		}
158
+		return '';
159
+	}
160
+
161
+
162
+	/**
163
+	 *    fatal_error_handler
164
+	 *
165
+	 * @return void
166
+	 */
167
+	public static function fatal_error_handler()
168
+	{
169
+		$last_error = error_get_last();
170
+		if ($last_error['type'] === E_ERROR) {
171
+			EE_Error::error_handler(E_ERROR, $last_error['message'], $last_error['file'], $last_error['line']);
172
+		}
173
+	}
174
+
175
+
176
+	/**
177
+	 * _format_error
178
+	 *
179
+	 * @param $code
180
+	 * @param $message
181
+	 * @param $file
182
+	 * @param $line
183
+	 * @return string
184
+	 */
185
+	private static function _format_error($code, $message, $file, $line)
186
+	{
187
+		$html = "<table cellpadding='5'><thead bgcolor='#f8f8f8'><th>Item</th><th align='left'>Details</th></thead><tbody>";
188
+		$html .= "<tr valign='top'><td><b>Code</b></td><td>$code</td></tr>";
189
+		$html .= "<tr valign='top'><td><b>Error</b></td><td>$message</td></tr>";
190
+		$html .= "<tr valign='top'><td><b>File</b></td><td>$file</td></tr>";
191
+		$html .= "<tr valign='top'><td><b>Line</b></td><td>$line</td></tr>";
192
+		$html .= '</tbody></table>';
193
+		return $html;
194
+	}
195
+
196
+
197
+	/**
198
+	 * set_content_type
199
+	 *
200
+	 * @param $content_type
201
+	 * @return string
202
+	 */
203
+	public static function set_content_type($content_type)
204
+	{
205
+		return 'text/html';
206
+	}
207
+
208
+
209
+	/**
210
+	 * @return void
211
+	 * @throws EE_Error
212
+	 * @throws ReflectionException
213
+	 */
214
+	public function get_error()
215
+	{
216
+		if (apply_filters('FHEE__EE_Error__get_error__show_normal_exceptions', false)) {
217
+			throw $this;
218
+		}
219
+		// get separate user and developer messages if they exist
220
+		$msg = explode('||', $this->getMessage());
221
+		$user_msg = $msg[0];
222
+		$dev_msg = isset($msg[1]) ? $msg[1] : $msg[0];
223
+		$msg = WP_DEBUG ? $dev_msg : $user_msg;
224
+		// add details to _all_exceptions array
225
+		$x_time = time();
226
+		self::$_all_exceptions[ $x_time ]['name'] = get_class($this);
227
+		self::$_all_exceptions[ $x_time ]['file'] = $this->getFile();
228
+		self::$_all_exceptions[ $x_time ]['line'] = $this->getLine();
229
+		self::$_all_exceptions[ $x_time ]['msg'] = $msg;
230
+		self::$_all_exceptions[ $x_time ]['code'] = $this->getCode();
231
+		self::$_all_exceptions[ $x_time ]['trace'] = $this->getTrace();
232
+		self::$_all_exceptions[ $x_time ]['string'] = $this->getTraceAsString();
233
+		self::$_error_count++;
234
+		// add_action( 'shutdown', array( $this, 'display_errors' ));
235
+		$this->display_errors();
236
+	}
237
+
238
+
239
+	/**
240
+	 * @param bool   $check_stored
241
+	 * @param string $type_to_check
242
+	 * @return bool
243
+	 * @throws InvalidInterfaceException
244
+	 * @throws InvalidArgumentException
245
+	 * @throws InvalidDataTypeException
246
+	 */
247
+	public static function has_error($check_stored = false, $type_to_check = 'errors')
248
+	{
249
+		$has_error = isset(self::$_espresso_notices[ $type_to_check ])
250
+					 && ! empty(self::$_espresso_notices[ $type_to_check ])
251
+			? true
252
+			: false;
253
+		if ($check_stored && ! $has_error) {
254
+			$notices = EE_Error::getStoredNotices();
255
+			foreach ($notices as $type => $notice) {
256
+				if ($type === $type_to_check && $notice) {
257
+					return true;
258
+				}
259
+			}
260
+		}
261
+		return $has_error;
262
+	}
263
+
264
+
265
+	/**
266
+	 * @echo string
267
+	 * @throws ReflectionException
268
+	 */
269
+	public function display_errors()
270
+	{
271
+		$trace_details = '';
272
+		$output = '
273 273
         <div id="ee-error-message" class="error">';
274
-        if (! WP_DEBUG) {
275
-            $output .= '
274
+		if (! WP_DEBUG) {
275
+			$output .= '
276 276
 	        <p>';
277
-        }
278
-        // cycle thru errors
279
-        foreach (self::$_all_exceptions as $time => $ex) {
280
-            $error_code = '';
281
-            // process trace info
282
-            if (empty($ex['trace'])) {
283
-                $trace_details .= esc_html__(
284
-                    'Sorry, but no trace information was available for this exception.',
285
-                    'event_espresso'
286
-                );
287
-            } else {
288
-                $trace_details .= '
277
+		}
278
+		// cycle thru errors
279
+		foreach (self::$_all_exceptions as $time => $ex) {
280
+			$error_code = '';
281
+			// process trace info
282
+			if (empty($ex['trace'])) {
283
+				$trace_details .= esc_html__(
284
+					'Sorry, but no trace information was available for this exception.',
285
+					'event_espresso'
286
+				);
287
+			} else {
288
+				$trace_details .= '
289 289
 			<div id="ee-trace-details">
290 290
 			<table width="100%" border="0" cellpadding="5" cellspacing="0">
291 291
 				<tr>
@@ -295,43 +295,43 @@  discard block
 block discarded – undo
295 295
 					<th scope="col" align="left">Class</th>
296 296
 					<th scope="col" align="left">Method( arguments )</th>
297 297
 				</tr>';
298
-                $last_on_stack = count($ex['trace']) - 1;
299
-                // reverse array so that stack is in proper chronological order
300
-                $sorted_trace = array_reverse($ex['trace']);
301
-                foreach ($sorted_trace as $nmbr => $trace) {
302
-                    $file = isset($trace['file']) ? $trace['file'] : '';
303
-                    $class = isset($trace['class']) ? $trace['class'] : '';
304
-                    $type = isset($trace['type']) ? $trace['type'] : '';
305
-                    $function = isset($trace['function']) ? $trace['function'] : '';
306
-                    $args = isset($trace['args']) ? $this->_convert_args_to_string($trace['args']) : '';
307
-                    $line = isset($trace['line']) ? $trace['line'] : '';
308
-                    $zebra = ($nmbr % 2) ? ' odd' : '';
309
-                    if (empty($file) && ! empty($class)) {
310
-                        $a = new ReflectionClass($class);
311
-                        $file = $a->getFileName();
312
-                        if (empty($line) && ! empty($function)) {
313
-                            try {
314
-                                // if $function is a closure, this throws an exception
315
-                                $b = new ReflectionMethod($class, $function);
316
-                                $line = $b->getStartLine();
317
-                            } catch (Exception $closure_exception) {
318
-                                $line = 'unknown';
319
-                            }
320
-                        }
321
-                    }
322
-                    if ($nmbr === $last_on_stack) {
323
-                        $file = $ex['file'] !== '' ? $ex['file'] : $file;
324
-                        $line = $ex['line'] !== '' ? $ex['line'] : $line;
325
-                        $error_code = self::generate_error_code($file, $trace['function'], $line);
326
-                    }
327
-                    $nmbr_dsply = ! empty($nmbr) ? $nmbr : '&nbsp;';
328
-                    $line_dsply = ! empty($line) ? $line : '&nbsp;';
329
-                    $file_dsply = ! empty($file) ? $file : '&nbsp;';
330
-                    $class_dsply = ! empty($class) ? $class : '&nbsp;';
331
-                    $type_dsply = ! empty($type) ? $type : '&nbsp;';
332
-                    $function_dsply = ! empty($function) ? $function : '&nbsp;';
333
-                    $args_dsply = ! empty($args) ? '( ' . $args . ' )' : '';
334
-                    $trace_details .= '
298
+				$last_on_stack = count($ex['trace']) - 1;
299
+				// reverse array so that stack is in proper chronological order
300
+				$sorted_trace = array_reverse($ex['trace']);
301
+				foreach ($sorted_trace as $nmbr => $trace) {
302
+					$file = isset($trace['file']) ? $trace['file'] : '';
303
+					$class = isset($trace['class']) ? $trace['class'] : '';
304
+					$type = isset($trace['type']) ? $trace['type'] : '';
305
+					$function = isset($trace['function']) ? $trace['function'] : '';
306
+					$args = isset($trace['args']) ? $this->_convert_args_to_string($trace['args']) : '';
307
+					$line = isset($trace['line']) ? $trace['line'] : '';
308
+					$zebra = ($nmbr % 2) ? ' odd' : '';
309
+					if (empty($file) && ! empty($class)) {
310
+						$a = new ReflectionClass($class);
311
+						$file = $a->getFileName();
312
+						if (empty($line) && ! empty($function)) {
313
+							try {
314
+								// if $function is a closure, this throws an exception
315
+								$b = new ReflectionMethod($class, $function);
316
+								$line = $b->getStartLine();
317
+							} catch (Exception $closure_exception) {
318
+								$line = 'unknown';
319
+							}
320
+						}
321
+					}
322
+					if ($nmbr === $last_on_stack) {
323
+						$file = $ex['file'] !== '' ? $ex['file'] : $file;
324
+						$line = $ex['line'] !== '' ? $ex['line'] : $line;
325
+						$error_code = self::generate_error_code($file, $trace['function'], $line);
326
+					}
327
+					$nmbr_dsply = ! empty($nmbr) ? $nmbr : '&nbsp;';
328
+					$line_dsply = ! empty($line) ? $line : '&nbsp;';
329
+					$file_dsply = ! empty($file) ? $file : '&nbsp;';
330
+					$class_dsply = ! empty($class) ? $class : '&nbsp;';
331
+					$type_dsply = ! empty($type) ? $type : '&nbsp;';
332
+					$function_dsply = ! empty($function) ? $function : '&nbsp;';
333
+					$args_dsply = ! empty($args) ? '( ' . $args . ' )' : '';
334
+					$trace_details .= '
335 335
 					<tr>
336 336
 						<td align="right" class="' . $zebra . '">' . $nmbr_dsply . '</td>
337 337
 						<td align="right" class="' . $zebra . '">' . $line_dsply . '</td>
@@ -339,628 +339,628 @@  discard block
 block discarded – undo
339 339
 						<td align="left" class="' . $zebra . '">' . $class_dsply . '</td>
340 340
 						<td align="left" class="' . $zebra . '">' . $type_dsply . $function_dsply . $args_dsply . '</td>
341 341
 					</tr>';
342
-                }
343
-                $trace_details .= '
342
+				}
343
+				$trace_details .= '
344 344
 			 </table>
345 345
 			</div>';
346
-            }
347
-            $ex['code'] = $ex['code'] ? $ex['code'] : $error_code;
348
-            // add generic non-identifying messages for non-privileged users
349
-            if (! WP_DEBUG) {
350
-                $output .= '<span class="ee-error-user-msg-spn">'
351
-                           . trim($ex['msg'])
352
-                           . '</span> &nbsp; <sup>'
353
-                           . $ex['code']
354
-                           . '</sup><br />';
355
-            } else {
356
-                // or helpful developer messages if debugging is on
357
-                $output .= '
346
+			}
347
+			$ex['code'] = $ex['code'] ? $ex['code'] : $error_code;
348
+			// add generic non-identifying messages for non-privileged users
349
+			if (! WP_DEBUG) {
350
+				$output .= '<span class="ee-error-user-msg-spn">'
351
+						   . trim($ex['msg'])
352
+						   . '</span> &nbsp; <sup>'
353
+						   . $ex['code']
354
+						   . '</sup><br />';
355
+			} else {
356
+				// or helpful developer messages if debugging is on
357
+				$output .= '
358 358
 		<div class="ee-error-dev-msg-dv">
359 359
 			<p class="ee-error-dev-msg-pg">
360 360
 				<strong class="ee-error-dev-msg-str">An '
361
-                           . $ex['name']
362
-                           . ' exception was thrown!</strong>  &nbsp; <span>code: '
363
-                           . $ex['code']
364
-                           . '</span><br />
361
+						   . $ex['name']
362
+						   . ' exception was thrown!</strong>  &nbsp; <span>code: '
363
+						   . $ex['code']
364
+						   . '</span><br />
365 365
 				<span class="big-text">"'
366
-                           . trim($ex['msg'])
367
-                           . '"</span><br/>
366
+						   . trim($ex['msg'])
367
+						   . '"</span><br/>
368 368
 				<a id="display-ee-error-trace-'
369
-                           . self::$_error_count
370
-                           . $time
371
-                           . '" class="display-ee-error-trace-lnk small-text" rel="ee-error-trace-'
372
-                           . self::$_error_count
373
-                           . $time
374
-                           . '">
369
+						   . self::$_error_count
370
+						   . $time
371
+						   . '" class="display-ee-error-trace-lnk small-text" rel="ee-error-trace-'
372
+						   . self::$_error_count
373
+						   . $time
374
+						   . '">
375 375
 					'
376
-                           . esc_html__('click to view backtrace and class/method details', 'event_espresso')
377
-                           . '
376
+						   . esc_html__('click to view backtrace and class/method details', 'event_espresso')
377
+						   . '
378 378
 				</a><br />
379 379
 				<span class="small-text lt-grey-text">'
380
-                           . $ex['file']
381
-                           . ' &nbsp; ( line no: '
382
-                           . $ex['line']
383
-                           . ' )</span>
380
+						   . $ex['file']
381
+						   . ' &nbsp; ( line no: '
382
+						   . $ex['line']
383
+						   . ' )</span>
384 384
 			</p>
385 385
 			<div id="ee-error-trace-'
386
-                           . self::$_error_count
387
-                           . $time
388
-                           . '-dv" class="ee-error-trace-dv" style="display: none;">
386
+						   . self::$_error_count
387
+						   . $time
388
+						   . '-dv" class="ee-error-trace-dv" style="display: none;">
389 389
 				'
390
-                           . $trace_details;
391
-                if (! empty($class)) {
392
-                    $output .= '
390
+						   . $trace_details;
391
+				if (! empty($class)) {
392
+					$output .= '
393 393
 				<div style="padding:3px; margin:0 0 1em; border:1px solid #666; background:#fff; border-radius:3px;">
394 394
 					<div style="padding:1em 2em; border:1px solid #666; background:#f9f9f9;">
395 395
 						<h3>Class Details</h3>';
396
-                    $a = new ReflectionClass($class);
397
-                    $output .= '
396
+					$a = new ReflectionClass($class);
397
+					$output .= '
398 398
 						<pre>' . $a . '</pre>
399 399
 					</div>
400 400
 				</div>';
401
-                }
402
-                $output .= '
401
+				}
402
+				$output .= '
403 403
 			</div>
404 404
 		</div>
405 405
 		<br />';
406
-            }
407
-            $this->write_to_error_log($time, $ex);
408
-        }
409
-        // remove last linebreak
410
-        $output = substr($output, 0, -6);
411
-        if (! WP_DEBUG) {
412
-            $output .= '
406
+			}
407
+			$this->write_to_error_log($time, $ex);
408
+		}
409
+		// remove last linebreak
410
+		$output = substr($output, 0, -6);
411
+		if (! WP_DEBUG) {
412
+			$output .= '
413 413
 	        </p>';
414
-        }
415
-        $output .= '
414
+		}
415
+		$output .= '
416 416
         </div>';
417
-        $output .= self::_print_scripts(true);
418
-        if (defined('DOING_AJAX')) {
419
-            echo wp_json_encode(array('error' => $output));
420
-            exit();
421
-        }
422
-        echo wp_kses($output, AllowedTags::getWithFormTags());
423
-        die();
424
-    }
425
-
426
-
427
-    /**
428
-     *    generate string from exception trace args
429
-     *
430
-     * @param array $arguments
431
-     * @param bool  $array
432
-     * @return string
433
-     */
434
-    private function _convert_args_to_string($arguments = array(), $array = false)
435
-    {
436
-        $arg_string = '';
437
-        if (! empty($arguments)) {
438
-            $args = array();
439
-            foreach ($arguments as $arg) {
440
-                if (! empty($arg)) {
441
-                    if (is_string($arg)) {
442
-                        $args[] = " '" . $arg . "'";
443
-                    } elseif (is_array($arg)) {
444
-                        $args[] = 'ARRAY(' . $this->_convert_args_to_string($arg, true);
445
-                    } elseif ($arg === null) {
446
-                        $args[] = ' NULL';
447
-                    } elseif (is_bool($arg)) {
448
-                        $args[] = ($arg) ? ' TRUE' : ' FALSE';
449
-                    } elseif (is_object($arg)) {
450
-                        $args[] = ' OBJECT ' . get_class($arg);
451
-                    } elseif (is_resource($arg)) {
452
-                        $args[] = get_resource_type($arg);
453
-                    } else {
454
-                        $args[] = $arg;
455
-                    }
456
-                }
457
-            }
458
-            $arg_string = implode(', ', $args);
459
-        }
460
-        if ($array) {
461
-            $arg_string .= ' )';
462
-        }
463
-        return $arg_string;
464
-    }
465
-
466
-
467
-    /**
468
-     *    add error message
469
-     *
470
-     * @param        string $msg  the message to display to users or developers - adding a double pipe || (OR) creates
471
-     *                            separate messages for user || dev
472
-     * @param        string $file the file that the error occurred in - just use __FILE__
473
-     * @param        string $func the function/method that the error occurred in - just use __FUNCTION__
474
-     * @param        string $line the line number where the error occurred - just use __LINE__
475
-     * @return        void
476
-     */
477
-    public static function add_error($msg = null, $file = null, $func = null, $line = null)
478
-    {
479
-        self::_add_notice('errors', $msg, $file, $func, $line);
480
-        self::$_error_count++;
481
-    }
482
-
483
-
484
-    /**
485
-     * If WP_DEBUG is active, throws an exception. If WP_DEBUG is off, just
486
-     * adds an error
487
-     *
488
-     * @param string $msg
489
-     * @param string $file
490
-     * @param string $func
491
-     * @param string $line
492
-     * @throws EE_Error
493
-     */
494
-    public static function throw_exception_if_debugging($msg = null, $file = null, $func = null, $line = null)
495
-    {
496
-        if (WP_DEBUG) {
497
-            throw new EE_Error($msg);
498
-        }
499
-        EE_Error::add_error($msg, $file, $func, $line);
500
-    }
501
-
502
-
503
-    /**
504
-     *    add success message
505
-     *
506
-     * @param        string $msg  the message to display to users or developers - adding a double pipe || (OR) creates
507
-     *                            separate messages for user || dev
508
-     * @param        string $file the file that the error occurred in - just use __FILE__
509
-     * @param        string $func the function/method that the error occurred in - just use __FUNCTION__
510
-     * @param        string $line the line number where the error occurred - just use __LINE__
511
-     * @return        void
512
-     */
513
-    public static function add_success($msg = null, $file = null, $func = null, $line = null)
514
-    {
515
-        self::_add_notice('success', $msg, $file, $func, $line);
516
-    }
517
-
518
-
519
-    /**
520
-     *    add attention message
521
-     *
522
-     * @param        string $msg  the message to display to users or developers - adding a double pipe || (OR) creates
523
-     *                            separate messages for user || dev
524
-     * @param        string $file the file that the error occurred in - just use __FILE__
525
-     * @param        string $func the function/method that the error occurred in - just use __FUNCTION__
526
-     * @param        string $line the line number where the error occurred - just use __LINE__
527
-     * @return        void
528
-     */
529
-    public static function add_attention($msg = null, $file = null, $func = null, $line = null)
530
-    {
531
-        self::_add_notice('attention', $msg, $file, $func, $line);
532
-    }
533
-
534
-
535
-    /**
536
-     * @param string $type whether the message is for a success or error notification
537
-     * @param string $msg  the message to display to users or developers
538
-     *                     - adding a double pipe || (OR) creates separate messages for user || dev
539
-     * @param string $file the file that the error occurred in - just use __FILE__
540
-     * @param string $func the function/method that the error occurred in - just use __FUNCTION__
541
-     * @param string $line the line number where the error occurred - just use __LINE__
542
-     * @return void
543
-     */
544
-    private static function _add_notice($type = 'success', $msg = '', $file = '', $func = '', $line = '')
545
-    {
546
-        if (empty($msg)) {
547
-            EE_Error::doing_it_wrong(
548
-                'EE_Error::add_' . $type . '()',
549
-                sprintf(
550
-                    esc_html__(
551
-                        'Notifications are not much use without a message! Please add a message to the EE_Error::add_%s() call made in %s on line %d',
552
-                        'event_espresso'
553
-                    ),
554
-                    $type,
555
-                    $file,
556
-                    $line
557
-                ),
558
-                EVENT_ESPRESSO_VERSION
559
-            );
560
-        }
561
-        if ($type === 'errors' && (empty($file) || empty($func) || empty($line))) {
562
-            EE_Error::doing_it_wrong(
563
-                'EE_Error::add_error()',
564
-                esc_html__(
565
-                    'You need to provide the file name, function name, and line number that the error occurred on in order to better assist with debugging.',
566
-                    'event_espresso'
567
-                ),
568
-                EVENT_ESPRESSO_VERSION
569
-            );
570
-        }
571
-        // get separate user and developer messages if they exist
572
-        $msg = explode('||', $msg);
573
-        $user_msg = $msg[0];
574
-        $dev_msg = isset($msg[1]) ? $msg[1] : $msg[0];
575
-        /**
576
-         * Do an action so other code can be triggered when a notice is created
577
-         *
578
-         * @param string $type     can be 'errors', 'attention', or 'success'
579
-         * @param string $user_msg message displayed to user when WP_DEBUG is off
580
-         * @param string $user_msg message displayed to user when WP_DEBUG is on
581
-         * @param string $file     file where error was generated
582
-         * @param string $func     function where error was generated
583
-         * @param string $line     line where error was generated
584
-         */
585
-        do_action('AHEE__EE_Error___add_notice', $type, $user_msg, $dev_msg, $file, $func, $line);
586
-        $msg = WP_DEBUG ? $dev_msg : $user_msg;
587
-        // add notice if message exists
588
-        if (! empty($msg)) {
589
-            // get error code
590
-            $notice_code = EE_Error::generate_error_code($file, $func, $line);
591
-            if (WP_DEBUG && $type === 'errors') {
592
-                $msg .= '<br/><span class="tiny-text">' . $notice_code . '</span>';
593
-            }
594
-            // add notice. Index by code if it's not blank
595
-            if ($notice_code) {
596
-                self::$_espresso_notices[ $type ][ $notice_code ] = $msg;
597
-            } else {
598
-                self::$_espresso_notices[ $type ][] = $msg;
599
-            }
600
-            add_action('wp_footer', array('EE_Error', 'enqueue_error_scripts'), 1);
601
-        }
602
-    }
603
-
604
-
605
-    /**
606
-     * in some case it may be necessary to overwrite the existing success messages
607
-     *
608
-     * @return        void
609
-     */
610
-    public static function overwrite_success()
611
-    {
612
-        self::$_espresso_notices['success'] = false;
613
-    }
614
-
615
-
616
-    /**
617
-     * in some case it may be necessary to overwrite the existing attention messages
618
-     *
619
-     * @return void
620
-     */
621
-    public static function overwrite_attention()
622
-    {
623
-        self::$_espresso_notices['attention'] = false;
624
-    }
625
-
626
-
627
-    /**
628
-     * in some case it may be necessary to overwrite the existing error messages
629
-     *
630
-     * @return void
631
-     */
632
-    public static function overwrite_errors()
633
-    {
634
-        self::$_espresso_notices['errors'] = false;
635
-    }
636
-
637
-
638
-    /**
639
-     * @return void
640
-     */
641
-    public static function reset_notices()
642
-    {
643
-        self::$_espresso_notices['success'] = false;
644
-        self::$_espresso_notices['attention'] = false;
645
-        self::$_espresso_notices['errors'] = false;
646
-    }
647
-
648
-
649
-    /**
650
-     * @return int
651
-     */
652
-    public static function has_notices()
653
-    {
654
-        $has_notices = 0;
655
-        // check for success messages
656
-        $has_notices = self::$_espresso_notices['success'] && ! empty(self::$_espresso_notices['success'])
657
-            ? 3
658
-            : $has_notices;
659
-        // check for attention messages
660
-        $has_notices = self::$_espresso_notices['attention'] && ! empty(self::$_espresso_notices['attention'])
661
-            ? 2
662
-            : $has_notices;
663
-        // check for error messages
664
-        $has_notices = self::$_espresso_notices['errors'] && ! empty(self::$_espresso_notices['errors'])
665
-            ? 1
666
-            : $has_notices;
667
-        return $has_notices;
668
-    }
669
-
670
-
671
-    /**
672
-     * This simply returns non formatted error notices as they were sent into the EE_Error object.
673
-     *
674
-     * @since 4.9.0
675
-     * @return array
676
-     */
677
-    public static function get_vanilla_notices()
678
-    {
679
-        return array(
680
-            'success'   => isset(self::$_espresso_notices['success'])
681
-                ? self::$_espresso_notices['success']
682
-                : array(),
683
-            'attention' => isset(self::$_espresso_notices['attention'])
684
-                ? self::$_espresso_notices['attention']
685
-                : array(),
686
-            'errors'    => isset(self::$_espresso_notices['errors'])
687
-                ? self::$_espresso_notices['errors']
688
-                : array(),
689
-        );
690
-    }
691
-
692
-
693
-    /**
694
-     * @return array
695
-     * @throws InvalidArgumentException
696
-     * @throws InvalidDataTypeException
697
-     * @throws InvalidInterfaceException
698
-     */
699
-    public static function getStoredNotices()
700
-    {
701
-        if ($user_id = get_current_user_id()) {
702
-            // get notices for logged in user
703
-            $notices = get_user_option(EE_Error::OPTIONS_KEY_NOTICES, $user_id);
704
-            return is_array($notices) ? $notices : array();
705
-        }
706
-        if (EE_Session::isLoadedAndActive()) {
707
-            // get notices for user currently engaged in a session
708
-            $session_data = EE_Session::instance()->get_session_data(EE_Error::OPTIONS_KEY_NOTICES);
709
-            return is_array($session_data) ? $session_data : array();
710
-        }
711
-        // get global notices and hope they apply to the current site visitor
712
-        $notices = get_option(EE_Error::OPTIONS_KEY_NOTICES, array());
713
-        return is_array($notices) ? $notices : array();
714
-    }
715
-
716
-
717
-    /**
718
-     * @param array $notices
719
-     * @return bool
720
-     * @throws InvalidArgumentException
721
-     * @throws InvalidDataTypeException
722
-     * @throws InvalidInterfaceException
723
-     */
724
-    public static function storeNotices(array $notices)
725
-    {
726
-        if ($user_id = get_current_user_id()) {
727
-            // store notices for logged in user
728
-            return (bool) update_user_option(
729
-                $user_id,
730
-                EE_Error::OPTIONS_KEY_NOTICES,
731
-                $notices
732
-            );
733
-        }
734
-        if (EE_Session::isLoadedAndActive()) {
735
-            // store notices for user currently engaged in a session
736
-            return EE_Session::instance()->set_session_data(
737
-                array(EE_Error::OPTIONS_KEY_NOTICES => $notices)
738
-            );
739
-        }
740
-        // store global notices and hope they apply to the same site visitor on the next request
741
-        return update_option(EE_Error::OPTIONS_KEY_NOTICES, $notices);
742
-    }
743
-
744
-
745
-    /**
746
-     * @return bool|TRUE
747
-     * @throws InvalidArgumentException
748
-     * @throws InvalidDataTypeException
749
-     * @throws InvalidInterfaceException
750
-     */
751
-    public static function clearNotices()
752
-    {
753
-        if ($user_id = get_current_user_id()) {
754
-            // clear notices for logged in user
755
-            return (bool) update_user_option(
756
-                $user_id,
757
-                EE_Error::OPTIONS_KEY_NOTICES,
758
-                array()
759
-            );
760
-        }
761
-        if (EE_Session::isLoadedAndActive()) {
762
-            // clear notices for user currently engaged in a session
763
-            return EE_Session::instance()->reset_data(EE_Error::OPTIONS_KEY_NOTICES);
764
-        }
765
-        // clear global notices and hope none belonged to some for some other site visitor
766
-        return update_option(EE_Error::OPTIONS_KEY_NOTICES, array());
767
-    }
768
-
769
-
770
-    /**
771
-     * saves notices to the db for retrieval on next request
772
-     *
773
-     * @return void
774
-     * @throws InvalidArgumentException
775
-     * @throws InvalidDataTypeException
776
-     * @throws InvalidInterfaceException
777
-     */
778
-    public static function stashNoticesBeforeRedirect()
779
-    {
780
-        EE_Error::get_notices(false, true);
781
-    }
782
-
783
-
784
-    /**
785
-     * compile all error or success messages into one string
786
-     *
787
-     * @see EE_Error::get_raw_notices if you want the raw notices without any preparations made to them
788
-     * @param bool $format_output            whether or not to format the messages for display in the WP admin
789
-     * @param bool $save_to_transient        whether or not to save notices to the db for retrieval on next request
790
-     *                                          - ONLY do this just before redirecting
791
-     * @param bool $remove_empty             whether or not to unset empty messages
792
-     * @return array|string
793
-     * @throws InvalidArgumentException
794
-     * @throws InvalidDataTypeException
795
-     * @throws InvalidInterfaceException
796
-     */
797
-    public static function get_notices($format_output = true, $save_to_transient = false, $remove_empty = true)
798
-    {
799
-        $success_messages = '';
800
-        $attention_messages = '';
801
-        $error_messages = '';
802
-        /** @var RequestInterface $request */
803
-        $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
804
-        // either save notices to the db
805
-        if ($save_to_transient || $request->requestParamIsSet('activate-selected')) {
806
-            self::$_espresso_notices = array_merge(
807
-                EE_Error::getStoredNotices(),
808
-                self::$_espresso_notices
809
-            );
810
-            EE_Error::storeNotices(self::$_espresso_notices);
811
-            return array();
812
-        }
813
-        $print_scripts = EE_Error::combineExistingAndNewNotices();
814
-        // check for success messages
815
-        if (self::$_espresso_notices['success'] && ! empty(self::$_espresso_notices['success'])) {
816
-            // combine messages
817
-            $success_messages .= implode('<br />', self::$_espresso_notices['success']);
818
-            $print_scripts = true;
819
-        }
820
-        // check for attention messages
821
-        if (self::$_espresso_notices['attention'] && ! empty(self::$_espresso_notices['attention'])) {
822
-            // combine messages
823
-            $attention_messages .= implode('<br />', self::$_espresso_notices['attention']);
824
-            $print_scripts = true;
825
-        }
826
-        // check for error messages
827
-        if (self::$_espresso_notices['errors'] && ! empty(self::$_espresso_notices['errors'])) {
828
-            $error_messages .= count(self::$_espresso_notices['errors']) > 1
829
-                ? esc_html__('The following errors have occurred:', 'event_espresso')
830
-                : esc_html__('An error has occurred:', 'event_espresso');
831
-            // combine messages
832
-            $error_messages .= '<br />' . implode('<br />', self::$_espresso_notices['errors']);
833
-            $print_scripts = true;
834
-        }
835
-        if ($format_output) {
836
-            $notices = EE_Error::formatNoticesOutput(
837
-                $success_messages,
838
-                $attention_messages,
839
-                $error_messages
840
-            );
841
-        } else {
842
-            $notices = array(
843
-                'success'   => $success_messages,
844
-                'attention' => $attention_messages,
845
-                'errors'    => $error_messages,
846
-            );
847
-            if ($remove_empty) {
848
-                // remove empty notices
849
-                foreach ($notices as $type => $notice) {
850
-                    if (empty($notice)) {
851
-                        unset($notices[ $type ]);
852
-                    }
853
-                }
854
-            }
855
-        }
856
-        if ($print_scripts) {
857
-            self::_print_scripts();
858
-        }
859
-        return $notices;
860
-    }
861
-
862
-
863
-    /**
864
-     * @return bool
865
-     * @throws InvalidArgumentException
866
-     * @throws InvalidDataTypeException
867
-     * @throws InvalidInterfaceException
868
-     */
869
-    private static function combineExistingAndNewNotices()
870
-    {
871
-        $print_scripts = false;
872
-        // grab any notices that have been previously saved
873
-        $notices = EE_Error::getStoredNotices();
874
-        if (! empty($notices)) {
875
-            foreach ($notices as $type => $notice) {
876
-                if (is_array($notice) && ! empty($notice)) {
877
-                    // make sure that existing notice type is an array
878
-                    self::$_espresso_notices[ $type ] = is_array(self::$_espresso_notices[ $type ])
879
-                                                        && ! empty(self::$_espresso_notices[ $type ])
880
-                        ? self::$_espresso_notices[ $type ]
881
-                        : array();
882
-                    // add newly created notices to existing ones
883
-                    self::$_espresso_notices[ $type ] += $notice;
884
-                    $print_scripts = true;
885
-                }
886
-            }
887
-            // now clear any stored notices
888
-            EE_Error::clearNotices();
889
-        }
890
-        return $print_scripts;
891
-    }
892
-
893
-
894
-    /**
895
-     * @param string $success_messages
896
-     * @param string $attention_messages
897
-     * @param string $error_messages
898
-     * @return string
899
-     */
900
-    private static function formatNoticesOutput($success_messages, $attention_messages, $error_messages)
901
-    {
902
-        $notices = '<div id="espresso-notices">';
903
-        $close = is_admin()
904
-            ? ''
905
-            : '<a class="close-espresso-notice hide-if-no-js"><span class="dashicons dashicons-no"/></a>';
906
-        if ($success_messages !== '') {
907
-            $css_id = is_admin() ? 'ee-success-message' : 'espresso-notices-success';
908
-            $css_class = is_admin() ? 'updated fade' : 'success fade-away';
909
-            // showMessage( $success_messages );
910
-            $notices .= '<div id="' . $css_id . '" '
911
-                        . 'class="espresso-notices ' . $css_class . '" '
912
-                        . 'style="display:none;">'
913
-                        . '<p>' . $success_messages . '</p>'
914
-                        . $close
915
-                        . '</div>';
916
-        }
917
-        if ($attention_messages !== '') {
918
-            $css_id = is_admin() ? 'ee-attention-message' : 'espresso-notices-attention';
919
-            $css_class = is_admin() ? 'updated ee-notices-attention' : 'attention fade-away';
920
-            // showMessage( $error_messages, TRUE );
921
-            $notices .= '<div id="' . $css_id . '" '
922
-                        . 'class="espresso-notices ' . $css_class . '" '
923
-                        . 'style="display:none;">'
924
-                        . '<p>' . $attention_messages . '</p>'
925
-                        . $close
926
-                        . '</div>';
927
-        }
928
-        if ($error_messages !== '') {
929
-            $css_id = is_admin() ? 'ee-error-message' : 'espresso-notices-error';
930
-            $css_class = is_admin() ? 'error' : 'error fade-away';
931
-            // showMessage( $error_messages, TRUE );
932
-            $notices .= '<div id="' . $css_id . '" '
933
-                        . 'class="espresso-notices ' . $css_class . '" '
934
-                        . 'style="display:none;">'
935
-                        . '<p>' . $error_messages . '</p>'
936
-                        . $close
937
-                        . '</div>';
938
-        }
939
-        $notices .= '</div>';
940
-        return $notices;
941
-    }
942
-
943
-
944
-    /**
945
-     * _print_scripts
946
-     *
947
-     * @param    bool $force_print
948
-     * @return    string
949
-     */
950
-    private static function _print_scripts($force_print = false)
951
-    {
952
-        if (! $force_print && (did_action('admin_enqueue_scripts') || did_action('wp_enqueue_scripts'))) {
953
-            if (wp_script_is('ee_error_js', 'registered')) {
954
-                wp_enqueue_style('espresso_default');
955
-                wp_enqueue_style('espresso_custom_css');
956
-                wp_enqueue_script('ee_error_js');
957
-            }
958
-            if (wp_script_is('ee_error_js', 'enqueued')) {
959
-                wp_localize_script('ee_error_js', 'ee_settings', array('wp_debug' => WP_DEBUG));
960
-                return '';
961
-            }
962
-        } else {
963
-            return '
417
+		$output .= self::_print_scripts(true);
418
+		if (defined('DOING_AJAX')) {
419
+			echo wp_json_encode(array('error' => $output));
420
+			exit();
421
+		}
422
+		echo wp_kses($output, AllowedTags::getWithFormTags());
423
+		die();
424
+	}
425
+
426
+
427
+	/**
428
+	 *    generate string from exception trace args
429
+	 *
430
+	 * @param array $arguments
431
+	 * @param bool  $array
432
+	 * @return string
433
+	 */
434
+	private function _convert_args_to_string($arguments = array(), $array = false)
435
+	{
436
+		$arg_string = '';
437
+		if (! empty($arguments)) {
438
+			$args = array();
439
+			foreach ($arguments as $arg) {
440
+				if (! empty($arg)) {
441
+					if (is_string($arg)) {
442
+						$args[] = " '" . $arg . "'";
443
+					} elseif (is_array($arg)) {
444
+						$args[] = 'ARRAY(' . $this->_convert_args_to_string($arg, true);
445
+					} elseif ($arg === null) {
446
+						$args[] = ' NULL';
447
+					} elseif (is_bool($arg)) {
448
+						$args[] = ($arg) ? ' TRUE' : ' FALSE';
449
+					} elseif (is_object($arg)) {
450
+						$args[] = ' OBJECT ' . get_class($arg);
451
+					} elseif (is_resource($arg)) {
452
+						$args[] = get_resource_type($arg);
453
+					} else {
454
+						$args[] = $arg;
455
+					}
456
+				}
457
+			}
458
+			$arg_string = implode(', ', $args);
459
+		}
460
+		if ($array) {
461
+			$arg_string .= ' )';
462
+		}
463
+		return $arg_string;
464
+	}
465
+
466
+
467
+	/**
468
+	 *    add error message
469
+	 *
470
+	 * @param        string $msg  the message to display to users or developers - adding a double pipe || (OR) creates
471
+	 *                            separate messages for user || dev
472
+	 * @param        string $file the file that the error occurred in - just use __FILE__
473
+	 * @param        string $func the function/method that the error occurred in - just use __FUNCTION__
474
+	 * @param        string $line the line number where the error occurred - just use __LINE__
475
+	 * @return        void
476
+	 */
477
+	public static function add_error($msg = null, $file = null, $func = null, $line = null)
478
+	{
479
+		self::_add_notice('errors', $msg, $file, $func, $line);
480
+		self::$_error_count++;
481
+	}
482
+
483
+
484
+	/**
485
+	 * If WP_DEBUG is active, throws an exception. If WP_DEBUG is off, just
486
+	 * adds an error
487
+	 *
488
+	 * @param string $msg
489
+	 * @param string $file
490
+	 * @param string $func
491
+	 * @param string $line
492
+	 * @throws EE_Error
493
+	 */
494
+	public static function throw_exception_if_debugging($msg = null, $file = null, $func = null, $line = null)
495
+	{
496
+		if (WP_DEBUG) {
497
+			throw new EE_Error($msg);
498
+		}
499
+		EE_Error::add_error($msg, $file, $func, $line);
500
+	}
501
+
502
+
503
+	/**
504
+	 *    add success message
505
+	 *
506
+	 * @param        string $msg  the message to display to users or developers - adding a double pipe || (OR) creates
507
+	 *                            separate messages for user || dev
508
+	 * @param        string $file the file that the error occurred in - just use __FILE__
509
+	 * @param        string $func the function/method that the error occurred in - just use __FUNCTION__
510
+	 * @param        string $line the line number where the error occurred - just use __LINE__
511
+	 * @return        void
512
+	 */
513
+	public static function add_success($msg = null, $file = null, $func = null, $line = null)
514
+	{
515
+		self::_add_notice('success', $msg, $file, $func, $line);
516
+	}
517
+
518
+
519
+	/**
520
+	 *    add attention message
521
+	 *
522
+	 * @param        string $msg  the message to display to users or developers - adding a double pipe || (OR) creates
523
+	 *                            separate messages for user || dev
524
+	 * @param        string $file the file that the error occurred in - just use __FILE__
525
+	 * @param        string $func the function/method that the error occurred in - just use __FUNCTION__
526
+	 * @param        string $line the line number where the error occurred - just use __LINE__
527
+	 * @return        void
528
+	 */
529
+	public static function add_attention($msg = null, $file = null, $func = null, $line = null)
530
+	{
531
+		self::_add_notice('attention', $msg, $file, $func, $line);
532
+	}
533
+
534
+
535
+	/**
536
+	 * @param string $type whether the message is for a success or error notification
537
+	 * @param string $msg  the message to display to users or developers
538
+	 *                     - adding a double pipe || (OR) creates separate messages for user || dev
539
+	 * @param string $file the file that the error occurred in - just use __FILE__
540
+	 * @param string $func the function/method that the error occurred in - just use __FUNCTION__
541
+	 * @param string $line the line number where the error occurred - just use __LINE__
542
+	 * @return void
543
+	 */
544
+	private static function _add_notice($type = 'success', $msg = '', $file = '', $func = '', $line = '')
545
+	{
546
+		if (empty($msg)) {
547
+			EE_Error::doing_it_wrong(
548
+				'EE_Error::add_' . $type . '()',
549
+				sprintf(
550
+					esc_html__(
551
+						'Notifications are not much use without a message! Please add a message to the EE_Error::add_%s() call made in %s on line %d',
552
+						'event_espresso'
553
+					),
554
+					$type,
555
+					$file,
556
+					$line
557
+				),
558
+				EVENT_ESPRESSO_VERSION
559
+			);
560
+		}
561
+		if ($type === 'errors' && (empty($file) || empty($func) || empty($line))) {
562
+			EE_Error::doing_it_wrong(
563
+				'EE_Error::add_error()',
564
+				esc_html__(
565
+					'You need to provide the file name, function name, and line number that the error occurred on in order to better assist with debugging.',
566
+					'event_espresso'
567
+				),
568
+				EVENT_ESPRESSO_VERSION
569
+			);
570
+		}
571
+		// get separate user and developer messages if they exist
572
+		$msg = explode('||', $msg);
573
+		$user_msg = $msg[0];
574
+		$dev_msg = isset($msg[1]) ? $msg[1] : $msg[0];
575
+		/**
576
+		 * Do an action so other code can be triggered when a notice is created
577
+		 *
578
+		 * @param string $type     can be 'errors', 'attention', or 'success'
579
+		 * @param string $user_msg message displayed to user when WP_DEBUG is off
580
+		 * @param string $user_msg message displayed to user when WP_DEBUG is on
581
+		 * @param string $file     file where error was generated
582
+		 * @param string $func     function where error was generated
583
+		 * @param string $line     line where error was generated
584
+		 */
585
+		do_action('AHEE__EE_Error___add_notice', $type, $user_msg, $dev_msg, $file, $func, $line);
586
+		$msg = WP_DEBUG ? $dev_msg : $user_msg;
587
+		// add notice if message exists
588
+		if (! empty($msg)) {
589
+			// get error code
590
+			$notice_code = EE_Error::generate_error_code($file, $func, $line);
591
+			if (WP_DEBUG && $type === 'errors') {
592
+				$msg .= '<br/><span class="tiny-text">' . $notice_code . '</span>';
593
+			}
594
+			// add notice. Index by code if it's not blank
595
+			if ($notice_code) {
596
+				self::$_espresso_notices[ $type ][ $notice_code ] = $msg;
597
+			} else {
598
+				self::$_espresso_notices[ $type ][] = $msg;
599
+			}
600
+			add_action('wp_footer', array('EE_Error', 'enqueue_error_scripts'), 1);
601
+		}
602
+	}
603
+
604
+
605
+	/**
606
+	 * in some case it may be necessary to overwrite the existing success messages
607
+	 *
608
+	 * @return        void
609
+	 */
610
+	public static function overwrite_success()
611
+	{
612
+		self::$_espresso_notices['success'] = false;
613
+	}
614
+
615
+
616
+	/**
617
+	 * in some case it may be necessary to overwrite the existing attention messages
618
+	 *
619
+	 * @return void
620
+	 */
621
+	public static function overwrite_attention()
622
+	{
623
+		self::$_espresso_notices['attention'] = false;
624
+	}
625
+
626
+
627
+	/**
628
+	 * in some case it may be necessary to overwrite the existing error messages
629
+	 *
630
+	 * @return void
631
+	 */
632
+	public static function overwrite_errors()
633
+	{
634
+		self::$_espresso_notices['errors'] = false;
635
+	}
636
+
637
+
638
+	/**
639
+	 * @return void
640
+	 */
641
+	public static function reset_notices()
642
+	{
643
+		self::$_espresso_notices['success'] = false;
644
+		self::$_espresso_notices['attention'] = false;
645
+		self::$_espresso_notices['errors'] = false;
646
+	}
647
+
648
+
649
+	/**
650
+	 * @return int
651
+	 */
652
+	public static function has_notices()
653
+	{
654
+		$has_notices = 0;
655
+		// check for success messages
656
+		$has_notices = self::$_espresso_notices['success'] && ! empty(self::$_espresso_notices['success'])
657
+			? 3
658
+			: $has_notices;
659
+		// check for attention messages
660
+		$has_notices = self::$_espresso_notices['attention'] && ! empty(self::$_espresso_notices['attention'])
661
+			? 2
662
+			: $has_notices;
663
+		// check for error messages
664
+		$has_notices = self::$_espresso_notices['errors'] && ! empty(self::$_espresso_notices['errors'])
665
+			? 1
666
+			: $has_notices;
667
+		return $has_notices;
668
+	}
669
+
670
+
671
+	/**
672
+	 * This simply returns non formatted error notices as they were sent into the EE_Error object.
673
+	 *
674
+	 * @since 4.9.0
675
+	 * @return array
676
+	 */
677
+	public static function get_vanilla_notices()
678
+	{
679
+		return array(
680
+			'success'   => isset(self::$_espresso_notices['success'])
681
+				? self::$_espresso_notices['success']
682
+				: array(),
683
+			'attention' => isset(self::$_espresso_notices['attention'])
684
+				? self::$_espresso_notices['attention']
685
+				: array(),
686
+			'errors'    => isset(self::$_espresso_notices['errors'])
687
+				? self::$_espresso_notices['errors']
688
+				: array(),
689
+		);
690
+	}
691
+
692
+
693
+	/**
694
+	 * @return array
695
+	 * @throws InvalidArgumentException
696
+	 * @throws InvalidDataTypeException
697
+	 * @throws InvalidInterfaceException
698
+	 */
699
+	public static function getStoredNotices()
700
+	{
701
+		if ($user_id = get_current_user_id()) {
702
+			// get notices for logged in user
703
+			$notices = get_user_option(EE_Error::OPTIONS_KEY_NOTICES, $user_id);
704
+			return is_array($notices) ? $notices : array();
705
+		}
706
+		if (EE_Session::isLoadedAndActive()) {
707
+			// get notices for user currently engaged in a session
708
+			$session_data = EE_Session::instance()->get_session_data(EE_Error::OPTIONS_KEY_NOTICES);
709
+			return is_array($session_data) ? $session_data : array();
710
+		}
711
+		// get global notices and hope they apply to the current site visitor
712
+		$notices = get_option(EE_Error::OPTIONS_KEY_NOTICES, array());
713
+		return is_array($notices) ? $notices : array();
714
+	}
715
+
716
+
717
+	/**
718
+	 * @param array $notices
719
+	 * @return bool
720
+	 * @throws InvalidArgumentException
721
+	 * @throws InvalidDataTypeException
722
+	 * @throws InvalidInterfaceException
723
+	 */
724
+	public static function storeNotices(array $notices)
725
+	{
726
+		if ($user_id = get_current_user_id()) {
727
+			// store notices for logged in user
728
+			return (bool) update_user_option(
729
+				$user_id,
730
+				EE_Error::OPTIONS_KEY_NOTICES,
731
+				$notices
732
+			);
733
+		}
734
+		if (EE_Session::isLoadedAndActive()) {
735
+			// store notices for user currently engaged in a session
736
+			return EE_Session::instance()->set_session_data(
737
+				array(EE_Error::OPTIONS_KEY_NOTICES => $notices)
738
+			);
739
+		}
740
+		// store global notices and hope they apply to the same site visitor on the next request
741
+		return update_option(EE_Error::OPTIONS_KEY_NOTICES, $notices);
742
+	}
743
+
744
+
745
+	/**
746
+	 * @return bool|TRUE
747
+	 * @throws InvalidArgumentException
748
+	 * @throws InvalidDataTypeException
749
+	 * @throws InvalidInterfaceException
750
+	 */
751
+	public static function clearNotices()
752
+	{
753
+		if ($user_id = get_current_user_id()) {
754
+			// clear notices for logged in user
755
+			return (bool) update_user_option(
756
+				$user_id,
757
+				EE_Error::OPTIONS_KEY_NOTICES,
758
+				array()
759
+			);
760
+		}
761
+		if (EE_Session::isLoadedAndActive()) {
762
+			// clear notices for user currently engaged in a session
763
+			return EE_Session::instance()->reset_data(EE_Error::OPTIONS_KEY_NOTICES);
764
+		}
765
+		// clear global notices and hope none belonged to some for some other site visitor
766
+		return update_option(EE_Error::OPTIONS_KEY_NOTICES, array());
767
+	}
768
+
769
+
770
+	/**
771
+	 * saves notices to the db for retrieval on next request
772
+	 *
773
+	 * @return void
774
+	 * @throws InvalidArgumentException
775
+	 * @throws InvalidDataTypeException
776
+	 * @throws InvalidInterfaceException
777
+	 */
778
+	public static function stashNoticesBeforeRedirect()
779
+	{
780
+		EE_Error::get_notices(false, true);
781
+	}
782
+
783
+
784
+	/**
785
+	 * compile all error or success messages into one string
786
+	 *
787
+	 * @see EE_Error::get_raw_notices if you want the raw notices without any preparations made to them
788
+	 * @param bool $format_output            whether or not to format the messages for display in the WP admin
789
+	 * @param bool $save_to_transient        whether or not to save notices to the db for retrieval on next request
790
+	 *                                          - ONLY do this just before redirecting
791
+	 * @param bool $remove_empty             whether or not to unset empty messages
792
+	 * @return array|string
793
+	 * @throws InvalidArgumentException
794
+	 * @throws InvalidDataTypeException
795
+	 * @throws InvalidInterfaceException
796
+	 */
797
+	public static function get_notices($format_output = true, $save_to_transient = false, $remove_empty = true)
798
+	{
799
+		$success_messages = '';
800
+		$attention_messages = '';
801
+		$error_messages = '';
802
+		/** @var RequestInterface $request */
803
+		$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
804
+		// either save notices to the db
805
+		if ($save_to_transient || $request->requestParamIsSet('activate-selected')) {
806
+			self::$_espresso_notices = array_merge(
807
+				EE_Error::getStoredNotices(),
808
+				self::$_espresso_notices
809
+			);
810
+			EE_Error::storeNotices(self::$_espresso_notices);
811
+			return array();
812
+		}
813
+		$print_scripts = EE_Error::combineExistingAndNewNotices();
814
+		// check for success messages
815
+		if (self::$_espresso_notices['success'] && ! empty(self::$_espresso_notices['success'])) {
816
+			// combine messages
817
+			$success_messages .= implode('<br />', self::$_espresso_notices['success']);
818
+			$print_scripts = true;
819
+		}
820
+		// check for attention messages
821
+		if (self::$_espresso_notices['attention'] && ! empty(self::$_espresso_notices['attention'])) {
822
+			// combine messages
823
+			$attention_messages .= implode('<br />', self::$_espresso_notices['attention']);
824
+			$print_scripts = true;
825
+		}
826
+		// check for error messages
827
+		if (self::$_espresso_notices['errors'] && ! empty(self::$_espresso_notices['errors'])) {
828
+			$error_messages .= count(self::$_espresso_notices['errors']) > 1
829
+				? esc_html__('The following errors have occurred:', 'event_espresso')
830
+				: esc_html__('An error has occurred:', 'event_espresso');
831
+			// combine messages
832
+			$error_messages .= '<br />' . implode('<br />', self::$_espresso_notices['errors']);
833
+			$print_scripts = true;
834
+		}
835
+		if ($format_output) {
836
+			$notices = EE_Error::formatNoticesOutput(
837
+				$success_messages,
838
+				$attention_messages,
839
+				$error_messages
840
+			);
841
+		} else {
842
+			$notices = array(
843
+				'success'   => $success_messages,
844
+				'attention' => $attention_messages,
845
+				'errors'    => $error_messages,
846
+			);
847
+			if ($remove_empty) {
848
+				// remove empty notices
849
+				foreach ($notices as $type => $notice) {
850
+					if (empty($notice)) {
851
+						unset($notices[ $type ]);
852
+					}
853
+				}
854
+			}
855
+		}
856
+		if ($print_scripts) {
857
+			self::_print_scripts();
858
+		}
859
+		return $notices;
860
+	}
861
+
862
+
863
+	/**
864
+	 * @return bool
865
+	 * @throws InvalidArgumentException
866
+	 * @throws InvalidDataTypeException
867
+	 * @throws InvalidInterfaceException
868
+	 */
869
+	private static function combineExistingAndNewNotices()
870
+	{
871
+		$print_scripts = false;
872
+		// grab any notices that have been previously saved
873
+		$notices = EE_Error::getStoredNotices();
874
+		if (! empty($notices)) {
875
+			foreach ($notices as $type => $notice) {
876
+				if (is_array($notice) && ! empty($notice)) {
877
+					// make sure that existing notice type is an array
878
+					self::$_espresso_notices[ $type ] = is_array(self::$_espresso_notices[ $type ])
879
+														&& ! empty(self::$_espresso_notices[ $type ])
880
+						? self::$_espresso_notices[ $type ]
881
+						: array();
882
+					// add newly created notices to existing ones
883
+					self::$_espresso_notices[ $type ] += $notice;
884
+					$print_scripts = true;
885
+				}
886
+			}
887
+			// now clear any stored notices
888
+			EE_Error::clearNotices();
889
+		}
890
+		return $print_scripts;
891
+	}
892
+
893
+
894
+	/**
895
+	 * @param string $success_messages
896
+	 * @param string $attention_messages
897
+	 * @param string $error_messages
898
+	 * @return string
899
+	 */
900
+	private static function formatNoticesOutput($success_messages, $attention_messages, $error_messages)
901
+	{
902
+		$notices = '<div id="espresso-notices">';
903
+		$close = is_admin()
904
+			? ''
905
+			: '<a class="close-espresso-notice hide-if-no-js"><span class="dashicons dashicons-no"/></a>';
906
+		if ($success_messages !== '') {
907
+			$css_id = is_admin() ? 'ee-success-message' : 'espresso-notices-success';
908
+			$css_class = is_admin() ? 'updated fade' : 'success fade-away';
909
+			// showMessage( $success_messages );
910
+			$notices .= '<div id="' . $css_id . '" '
911
+						. 'class="espresso-notices ' . $css_class . '" '
912
+						. 'style="display:none;">'
913
+						. '<p>' . $success_messages . '</p>'
914
+						. $close
915
+						. '</div>';
916
+		}
917
+		if ($attention_messages !== '') {
918
+			$css_id = is_admin() ? 'ee-attention-message' : 'espresso-notices-attention';
919
+			$css_class = is_admin() ? 'updated ee-notices-attention' : 'attention fade-away';
920
+			// showMessage( $error_messages, TRUE );
921
+			$notices .= '<div id="' . $css_id . '" '
922
+						. 'class="espresso-notices ' . $css_class . '" '
923
+						. 'style="display:none;">'
924
+						. '<p>' . $attention_messages . '</p>'
925
+						. $close
926
+						. '</div>';
927
+		}
928
+		if ($error_messages !== '') {
929
+			$css_id = is_admin() ? 'ee-error-message' : 'espresso-notices-error';
930
+			$css_class = is_admin() ? 'error' : 'error fade-away';
931
+			// showMessage( $error_messages, TRUE );
932
+			$notices .= '<div id="' . $css_id . '" '
933
+						. 'class="espresso-notices ' . $css_class . '" '
934
+						. 'style="display:none;">'
935
+						. '<p>' . $error_messages . '</p>'
936
+						. $close
937
+						. '</div>';
938
+		}
939
+		$notices .= '</div>';
940
+		return $notices;
941
+	}
942
+
943
+
944
+	/**
945
+	 * _print_scripts
946
+	 *
947
+	 * @param    bool $force_print
948
+	 * @return    string
949
+	 */
950
+	private static function _print_scripts($force_print = false)
951
+	{
952
+		if (! $force_print && (did_action('admin_enqueue_scripts') || did_action('wp_enqueue_scripts'))) {
953
+			if (wp_script_is('ee_error_js', 'registered')) {
954
+				wp_enqueue_style('espresso_default');
955
+				wp_enqueue_style('espresso_custom_css');
956
+				wp_enqueue_script('ee_error_js');
957
+			}
958
+			if (wp_script_is('ee_error_js', 'enqueued')) {
959
+				wp_localize_script('ee_error_js', 'ee_settings', array('wp_debug' => WP_DEBUG));
960
+				return '';
961
+			}
962
+		} else {
963
+			return '
964 964
                 <script>
965 965
                 /* <![CDATA[ */
966 966
                 var ee_settings = {"wp_debug":"' . WP_DEBUG . '"};
@@ -970,221 +970,221 @@  discard block
 block discarded – undo
970 970
                 <script src="' . EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js' . '?ver=' . espresso_version() . '" type="text/javascript"></script>
971 971
                 <script src="' . EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js' . '?ver=' . espresso_version() . '" type="text/javascript"></script>
972 972
             ';
973
-        }
974
-        return '';
975
-    }
976
-
977
-
978
-    /**
979
-     * @return void
980
-     */
981
-    public static function enqueue_error_scripts()
982
-    {
983
-        self::_print_scripts();
984
-    }
985
-
986
-
987
-    /**
988
-     * create error code from filepath, function name,
989
-     * and line number where exception or error was thrown
990
-     *
991
-     * @param string $file
992
-     * @param string $func
993
-     * @param string $line
994
-     * @return string
995
-     */
996
-    public static function generate_error_code($file = '', $func = '', $line = '')
997
-    {
998
-        $file = explode('.', basename($file));
999
-        $error_code = ! empty($file[0]) ? $file[0] : '';
1000
-        $error_code .= ! empty($func) ? ' - ' . $func : '';
1001
-        $error_code .= ! empty($line) ? ' - ' . $line : '';
1002
-        return $error_code;
1003
-    }
1004
-
1005
-
1006
-    /**
1007
-     * write exception details to log file
1008
-     * Since 4.9.53.rc.006 this writes to the standard PHP log file, not EE's custom log file
1009
-     *
1010
-     * @param int   $time
1011
-     * @param array $ex
1012
-     * @param bool  $clear
1013
-     * @return void
1014
-     */
1015
-    public function write_to_error_log($time = 0, $ex = array(), $clear = false)
1016
-    {
1017
-        if (empty($ex)) {
1018
-            return;
1019
-        }
1020
-        if (! $time) {
1021
-            $time = time();
1022
-        }
1023
-        $exception_log = '----------------------------------------------------------------------------------------'
1024
-                         . PHP_EOL;
1025
-        $exception_log .= '[' . date('Y-m-d H:i:s', $time) . ']  Exception Details' . PHP_EOL;
1026
-        $exception_log .= 'Message: ' . $ex['msg'] . PHP_EOL;
1027
-        $exception_log .= 'Code: ' . $ex['code'] . PHP_EOL;
1028
-        $exception_log .= 'File: ' . $ex['file'] . PHP_EOL;
1029
-        $exception_log .= 'Line No: ' . $ex['line'] . PHP_EOL;
1030
-        $exception_log .= 'Stack trace: ' . PHP_EOL;
1031
-        $exception_log .= $ex['string'] . PHP_EOL;
1032
-        $exception_log .= '----------------------------------------------------------------------------------------'
1033
-                          . PHP_EOL;
1034
-        try {
1035
-            error_log($exception_log);
1036
-        } catch (EE_Error $e) {
1037
-            EE_Error::add_error(
1038
-                sprintf(
1039
-                    esc_html__(
1040
-                        'Event Espresso error logging could not be setup because: %s',
1041
-                        'event_espresso'
1042
-                    ),
1043
-                    $e->getMessage()
1044
-                )
1045
-            );
1046
-        }
1047
-    }
1048
-
1049
-
1050
-    /**
1051
-     * This is just a wrapper for the EEH_Debug_Tools::instance()->doing_it_wrong() method.
1052
-     * doing_it_wrong() is used in those cases where a normal PHP error won't get thrown,
1053
-     * but the code execution is done in a manner that could lead to unexpected results
1054
-     * (i.e. running to early, or too late in WP or EE loading process).
1055
-     * A good test for knowing whether to use this method is:
1056
-     * 1. Is there going to be a PHP error if something isn't setup/used correctly?
1057
-     * Yes -> use EE_Error::add_error() or throw new EE_Error()
1058
-     * 2. If this is loaded before something else, it won't break anything,
1059
-     * but just wont' do what its supposed to do? Yes -> use EE_Error::doing_it_wrong()
1060
-     *
1061
-     * @uses   constant WP_DEBUG test if wp_debug is on or not
1062
-     * @param string $function      The function that was called
1063
-     * @param string $message       A message explaining what has been done incorrectly
1064
-     * @param string $version       The version of Event Espresso where the error was added
1065
-     * @param string $applies_when  a version string for when you want the doing_it_wrong notice to begin appearing
1066
-     *                              for a deprecated function. This allows deprecation to occur during one version,
1067
-     *                              but not have any notices appear until a later version. This allows developers
1068
-     *                              extra time to update their code before notices appear.
1069
-     * @param int    $error_type
1070
-     */
1071
-    public static function doing_it_wrong(
1072
-        $function,
1073
-        $message,
1074
-        $version,
1075
-        $applies_when = '',
1076
-        $error_type = null
1077
-    ) {
1078
-        if (defined('WP_DEBUG') && WP_DEBUG) {
1079
-            EEH_Debug_Tools::instance()->doing_it_wrong($function, $message, $version, $applies_when, $error_type);
1080
-        }
1081
-    }
1082
-
1083
-
1084
-    /**
1085
-     * Like get_notices, but returns an array of all the notices of the given type.
1086
-     *
1087
-     * @return array {
1088
-     * @type array $success   all the success messages
1089
-     * @type array $errors    all the error messages
1090
-     * @type array $attention all the attention messages
1091
-     * }
1092
-     */
1093
-    public static function get_raw_notices()
1094
-    {
1095
-        return self::$_espresso_notices;
1096
-    }
1097
-
1098
-
1099
-    /**
1100
-     * @deprecated 4.9.27
1101
-     * @param string $pan_name     the name, or key of the Persistent Admin Notice to be stored
1102
-     * @param string $pan_message  the message to be stored persistently until dismissed
1103
-     * @param bool   $force_update allows one to enforce the reappearance of a persistent message.
1104
-     * @return void
1105
-     * @throws InvalidDataTypeException
1106
-     */
1107
-    public static function add_persistent_admin_notice($pan_name, $pan_message, $force_update = false)
1108
-    {
1109
-        new PersistentAdminNotice(
1110
-            $pan_name,
1111
-            $pan_message,
1112
-            $force_update
1113
-        );
1114
-        EE_Error::doing_it_wrong(
1115
-            __METHOD__,
1116
-            sprintf(
1117
-                esc_html__('Usage is deprecated. Use "%1$s" instead.', 'event_espresso'),
1118
-                '\EventEspresso\core\domain\entities\notifications\PersistentAdminNotice'
1119
-            ),
1120
-            '4.9.27'
1121
-        );
1122
-    }
1123
-
1124
-
1125
-    /**
1126
-     * @deprecated 4.9.27
1127
-     * @param string $pan_name the name, or key of the Persistent Admin Notice to be dismissed
1128
-     * @param bool   $purge
1129
-     * @param bool   $return
1130
-     * @throws DomainException
1131
-     * @throws InvalidInterfaceException
1132
-     * @throws InvalidDataTypeException
1133
-     * @throws ServiceNotFoundException
1134
-     * @throws InvalidArgumentException
1135
-     */
1136
-    public static function dismiss_persistent_admin_notice($pan_name, $purge = false, $return = false)
1137
-    {
1138
-        /** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
1139
-        $persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(
1140
-            'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
1141
-        );
1142
-        $persistent_admin_notice_manager->dismissNotice($pan_name, $purge, $return);
1143
-        EE_Error::doing_it_wrong(
1144
-            __METHOD__,
1145
-            sprintf(
1146
-                esc_html__('Usage is deprecated. Use "%1$s" instead.', 'event_espresso'),
1147
-                '\EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
1148
-            ),
1149
-            '4.9.27'
1150
-        );
1151
-    }
1152
-
1153
-
1154
-    /**
1155
-     * @deprecated 4.9.27
1156
-     * @param  string $pan_name    the name, or key of the Persistent Admin Notice to be stored
1157
-     * @param  string $pan_message the message to be stored persistently until dismissed
1158
-     * @param  string $return_url  URL to go back to after nag notice is dismissed
1159
-     */
1160
-    public static function display_persistent_admin_notices($pan_name = '', $pan_message = '', $return_url = '')
1161
-    {
1162
-        EE_Error::doing_it_wrong(
1163
-            __METHOD__,
1164
-            sprintf(
1165
-                esc_html__('Usage is deprecated. Use "%1$s" instead.', 'event_espresso'),
1166
-                '\EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
1167
-            ),
1168
-            '4.9.27'
1169
-        );
1170
-    }
1171
-
1172
-
1173
-    /**
1174
-     * @deprecated 4.9.27
1175
-     * @param string $return_url
1176
-     */
1177
-    public static function get_persistent_admin_notices($return_url = '')
1178
-    {
1179
-        EE_Error::doing_it_wrong(
1180
-            __METHOD__,
1181
-            sprintf(
1182
-                esc_html__('Usage is deprecated. Use "%1$s" instead.', 'event_espresso'),
1183
-                '\EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
1184
-            ),
1185
-            '4.9.27'
1186
-        );
1187
-    }
973
+		}
974
+		return '';
975
+	}
976
+
977
+
978
+	/**
979
+	 * @return void
980
+	 */
981
+	public static function enqueue_error_scripts()
982
+	{
983
+		self::_print_scripts();
984
+	}
985
+
986
+
987
+	/**
988
+	 * create error code from filepath, function name,
989
+	 * and line number where exception or error was thrown
990
+	 *
991
+	 * @param string $file
992
+	 * @param string $func
993
+	 * @param string $line
994
+	 * @return string
995
+	 */
996
+	public static function generate_error_code($file = '', $func = '', $line = '')
997
+	{
998
+		$file = explode('.', basename($file));
999
+		$error_code = ! empty($file[0]) ? $file[0] : '';
1000
+		$error_code .= ! empty($func) ? ' - ' . $func : '';
1001
+		$error_code .= ! empty($line) ? ' - ' . $line : '';
1002
+		return $error_code;
1003
+	}
1004
+
1005
+
1006
+	/**
1007
+	 * write exception details to log file
1008
+	 * Since 4.9.53.rc.006 this writes to the standard PHP log file, not EE's custom log file
1009
+	 *
1010
+	 * @param int   $time
1011
+	 * @param array $ex
1012
+	 * @param bool  $clear
1013
+	 * @return void
1014
+	 */
1015
+	public function write_to_error_log($time = 0, $ex = array(), $clear = false)
1016
+	{
1017
+		if (empty($ex)) {
1018
+			return;
1019
+		}
1020
+		if (! $time) {
1021
+			$time = time();
1022
+		}
1023
+		$exception_log = '----------------------------------------------------------------------------------------'
1024
+						 . PHP_EOL;
1025
+		$exception_log .= '[' . date('Y-m-d H:i:s', $time) . ']  Exception Details' . PHP_EOL;
1026
+		$exception_log .= 'Message: ' . $ex['msg'] . PHP_EOL;
1027
+		$exception_log .= 'Code: ' . $ex['code'] . PHP_EOL;
1028
+		$exception_log .= 'File: ' . $ex['file'] . PHP_EOL;
1029
+		$exception_log .= 'Line No: ' . $ex['line'] . PHP_EOL;
1030
+		$exception_log .= 'Stack trace: ' . PHP_EOL;
1031
+		$exception_log .= $ex['string'] . PHP_EOL;
1032
+		$exception_log .= '----------------------------------------------------------------------------------------'
1033
+						  . PHP_EOL;
1034
+		try {
1035
+			error_log($exception_log);
1036
+		} catch (EE_Error $e) {
1037
+			EE_Error::add_error(
1038
+				sprintf(
1039
+					esc_html__(
1040
+						'Event Espresso error logging could not be setup because: %s',
1041
+						'event_espresso'
1042
+					),
1043
+					$e->getMessage()
1044
+				)
1045
+			);
1046
+		}
1047
+	}
1048
+
1049
+
1050
+	/**
1051
+	 * This is just a wrapper for the EEH_Debug_Tools::instance()->doing_it_wrong() method.
1052
+	 * doing_it_wrong() is used in those cases where a normal PHP error won't get thrown,
1053
+	 * but the code execution is done in a manner that could lead to unexpected results
1054
+	 * (i.e. running to early, or too late in WP or EE loading process).
1055
+	 * A good test for knowing whether to use this method is:
1056
+	 * 1. Is there going to be a PHP error if something isn't setup/used correctly?
1057
+	 * Yes -> use EE_Error::add_error() or throw new EE_Error()
1058
+	 * 2. If this is loaded before something else, it won't break anything,
1059
+	 * but just wont' do what its supposed to do? Yes -> use EE_Error::doing_it_wrong()
1060
+	 *
1061
+	 * @uses   constant WP_DEBUG test if wp_debug is on or not
1062
+	 * @param string $function      The function that was called
1063
+	 * @param string $message       A message explaining what has been done incorrectly
1064
+	 * @param string $version       The version of Event Espresso where the error was added
1065
+	 * @param string $applies_when  a version string for when you want the doing_it_wrong notice to begin appearing
1066
+	 *                              for a deprecated function. This allows deprecation to occur during one version,
1067
+	 *                              but not have any notices appear until a later version. This allows developers
1068
+	 *                              extra time to update their code before notices appear.
1069
+	 * @param int    $error_type
1070
+	 */
1071
+	public static function doing_it_wrong(
1072
+		$function,
1073
+		$message,
1074
+		$version,
1075
+		$applies_when = '',
1076
+		$error_type = null
1077
+	) {
1078
+		if (defined('WP_DEBUG') && WP_DEBUG) {
1079
+			EEH_Debug_Tools::instance()->doing_it_wrong($function, $message, $version, $applies_when, $error_type);
1080
+		}
1081
+	}
1082
+
1083
+
1084
+	/**
1085
+	 * Like get_notices, but returns an array of all the notices of the given type.
1086
+	 *
1087
+	 * @return array {
1088
+	 * @type array $success   all the success messages
1089
+	 * @type array $errors    all the error messages
1090
+	 * @type array $attention all the attention messages
1091
+	 * }
1092
+	 */
1093
+	public static function get_raw_notices()
1094
+	{
1095
+		return self::$_espresso_notices;
1096
+	}
1097
+
1098
+
1099
+	/**
1100
+	 * @deprecated 4.9.27
1101
+	 * @param string $pan_name     the name, or key of the Persistent Admin Notice to be stored
1102
+	 * @param string $pan_message  the message to be stored persistently until dismissed
1103
+	 * @param bool   $force_update allows one to enforce the reappearance of a persistent message.
1104
+	 * @return void
1105
+	 * @throws InvalidDataTypeException
1106
+	 */
1107
+	public static function add_persistent_admin_notice($pan_name, $pan_message, $force_update = false)
1108
+	{
1109
+		new PersistentAdminNotice(
1110
+			$pan_name,
1111
+			$pan_message,
1112
+			$force_update
1113
+		);
1114
+		EE_Error::doing_it_wrong(
1115
+			__METHOD__,
1116
+			sprintf(
1117
+				esc_html__('Usage is deprecated. Use "%1$s" instead.', 'event_espresso'),
1118
+				'\EventEspresso\core\domain\entities\notifications\PersistentAdminNotice'
1119
+			),
1120
+			'4.9.27'
1121
+		);
1122
+	}
1123
+
1124
+
1125
+	/**
1126
+	 * @deprecated 4.9.27
1127
+	 * @param string $pan_name the name, or key of the Persistent Admin Notice to be dismissed
1128
+	 * @param bool   $purge
1129
+	 * @param bool   $return
1130
+	 * @throws DomainException
1131
+	 * @throws InvalidInterfaceException
1132
+	 * @throws InvalidDataTypeException
1133
+	 * @throws ServiceNotFoundException
1134
+	 * @throws InvalidArgumentException
1135
+	 */
1136
+	public static function dismiss_persistent_admin_notice($pan_name, $purge = false, $return = false)
1137
+	{
1138
+		/** @var PersistentAdminNoticeManager $persistent_admin_notice_manager */
1139
+		$persistent_admin_notice_manager = LoaderFactory::getLoader()->getShared(
1140
+			'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
1141
+		);
1142
+		$persistent_admin_notice_manager->dismissNotice($pan_name, $purge, $return);
1143
+		EE_Error::doing_it_wrong(
1144
+			__METHOD__,
1145
+			sprintf(
1146
+				esc_html__('Usage is deprecated. Use "%1$s" instead.', 'event_espresso'),
1147
+				'\EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
1148
+			),
1149
+			'4.9.27'
1150
+		);
1151
+	}
1152
+
1153
+
1154
+	/**
1155
+	 * @deprecated 4.9.27
1156
+	 * @param  string $pan_name    the name, or key of the Persistent Admin Notice to be stored
1157
+	 * @param  string $pan_message the message to be stored persistently until dismissed
1158
+	 * @param  string $return_url  URL to go back to after nag notice is dismissed
1159
+	 */
1160
+	public static function display_persistent_admin_notices($pan_name = '', $pan_message = '', $return_url = '')
1161
+	{
1162
+		EE_Error::doing_it_wrong(
1163
+			__METHOD__,
1164
+			sprintf(
1165
+				esc_html__('Usage is deprecated. Use "%1$s" instead.', 'event_espresso'),
1166
+				'\EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
1167
+			),
1168
+			'4.9.27'
1169
+		);
1170
+	}
1171
+
1172
+
1173
+	/**
1174
+	 * @deprecated 4.9.27
1175
+	 * @param string $return_url
1176
+	 */
1177
+	public static function get_persistent_admin_notices($return_url = '')
1178
+	{
1179
+		EE_Error::doing_it_wrong(
1180
+			__METHOD__,
1181
+			sprintf(
1182
+				esc_html__('Usage is deprecated. Use "%1$s" instead.', 'event_espresso'),
1183
+				'\EventEspresso\core\services\notifications\PersistentAdminNoticeManager'
1184
+			),
1185
+			'4.9.27'
1186
+		);
1187
+	}
1188 1188
 }
1189 1189
 
1190 1190
 // end of Class EE_Exceptions
@@ -1197,29 +1197,29 @@  discard block
 block discarded – undo
1197 1197
  */
1198 1198
 function espresso_error_enqueue_scripts()
1199 1199
 {
1200
-    // js for error handling
1201
-    if (! wp_script_is('espresso_core', 'registered')) {
1202
-        wp_register_script(
1203
-            'espresso_core',
1204
-            EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js',
1205
-            ['jquery'],
1206
-            EVENT_ESPRESSO_VERSION,
1207
-            false
1208
-        );
1209
-    }
1210
-    wp_register_script(
1211
-        'ee_error_js',
1212
-        EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js',
1213
-        array('espresso_core'),
1214
-        EVENT_ESPRESSO_VERSION
1215
-    );
1216
-    wp_localize_script('ee_error_js', 'ee_settings', ['wp_debug' => WP_DEBUG]);
1200
+	// js for error handling
1201
+	if (! wp_script_is('espresso_core', 'registered')) {
1202
+		wp_register_script(
1203
+			'espresso_core',
1204
+			EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js',
1205
+			['jquery'],
1206
+			EVENT_ESPRESSO_VERSION,
1207
+			false
1208
+		);
1209
+	}
1210
+	wp_register_script(
1211
+		'ee_error_js',
1212
+		EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js',
1213
+		array('espresso_core'),
1214
+		EVENT_ESPRESSO_VERSION
1215
+	);
1216
+	wp_localize_script('ee_error_js', 'ee_settings', ['wp_debug' => WP_DEBUG]);
1217 1217
 }
1218 1218
 
1219 1219
 if (is_admin()) {
1220
-    add_action('admin_enqueue_scripts', 'espresso_error_enqueue_scripts', 5);
1220
+	add_action('admin_enqueue_scripts', 'espresso_error_enqueue_scripts', 5);
1221 1221
 } else {
1222
-    add_action('wp_enqueue_scripts', 'espresso_error_enqueue_scripts', 5);
1222
+	add_action('wp_enqueue_scripts', 'espresso_error_enqueue_scripts', 5);
1223 1223
 }
1224 1224
 
1225 1225
 
Please login to merge, or discard this patch.
Spacing   +66 added lines, -66 removed lines patch added patch discarded remove patch
@@ -100,14 +100,14 @@  discard block
 block discarded – undo
100 100
             default:
101 101
                 $to = get_option('admin_email');
102 102
         }
103
-        $subject = $type . ' ' . $message . ' in ' . EVENT_ESPRESSO_VERSION . ' on ' . site_url();
103
+        $subject = $type.' '.$message.' in '.EVENT_ESPRESSO_VERSION.' on '.site_url();
104 104
         $msg = EE_Error::_format_error($type, $message, $file, $line);
105 105
         if (function_exists('wp_mail')) {
106 106
             add_filter('wp_mail_content_type', array('EE_Error', 'set_content_type'));
107 107
             wp_mail($to, $subject, $msg);
108 108
         }
109 109
         echo '<div id="message" class="espresso-notices error"><p>';
110
-        echo wp_kses($type . ': ' . $message . '<br />' . $file . ' line ' . $line, AllowedTags::getWithFormTags());
110
+        echo wp_kses($type.': '.$message.'<br />'.$file.' line '.$line, AllowedTags::getWithFormTags());
111 111
         echo '<br /></p></div>';
112 112
     }
113 113
 
@@ -223,13 +223,13 @@  discard block
 block discarded – undo
223 223
         $msg = WP_DEBUG ? $dev_msg : $user_msg;
224 224
         // add details to _all_exceptions array
225 225
         $x_time = time();
226
-        self::$_all_exceptions[ $x_time ]['name'] = get_class($this);
227
-        self::$_all_exceptions[ $x_time ]['file'] = $this->getFile();
228
-        self::$_all_exceptions[ $x_time ]['line'] = $this->getLine();
229
-        self::$_all_exceptions[ $x_time ]['msg'] = $msg;
230
-        self::$_all_exceptions[ $x_time ]['code'] = $this->getCode();
231
-        self::$_all_exceptions[ $x_time ]['trace'] = $this->getTrace();
232
-        self::$_all_exceptions[ $x_time ]['string'] = $this->getTraceAsString();
226
+        self::$_all_exceptions[$x_time]['name'] = get_class($this);
227
+        self::$_all_exceptions[$x_time]['file'] = $this->getFile();
228
+        self::$_all_exceptions[$x_time]['line'] = $this->getLine();
229
+        self::$_all_exceptions[$x_time]['msg'] = $msg;
230
+        self::$_all_exceptions[$x_time]['code'] = $this->getCode();
231
+        self::$_all_exceptions[$x_time]['trace'] = $this->getTrace();
232
+        self::$_all_exceptions[$x_time]['string'] = $this->getTraceAsString();
233 233
         self::$_error_count++;
234 234
         // add_action( 'shutdown', array( $this, 'display_errors' ));
235 235
         $this->display_errors();
@@ -246,8 +246,8 @@  discard block
 block discarded – undo
246 246
      */
247 247
     public static function has_error($check_stored = false, $type_to_check = 'errors')
248 248
     {
249
-        $has_error = isset(self::$_espresso_notices[ $type_to_check ])
250
-                     && ! empty(self::$_espresso_notices[ $type_to_check ])
249
+        $has_error = isset(self::$_espresso_notices[$type_to_check])
250
+                     && ! empty(self::$_espresso_notices[$type_to_check])
251 251
             ? true
252 252
             : false;
253 253
         if ($check_stored && ! $has_error) {
@@ -271,7 +271,7 @@  discard block
 block discarded – undo
271 271
         $trace_details = '';
272 272
         $output = '
273 273
         <div id="ee-error-message" class="error">';
274
-        if (! WP_DEBUG) {
274
+        if ( ! WP_DEBUG) {
275 275
             $output .= '
276 276
 	        <p>';
277 277
         }
@@ -330,14 +330,14 @@  discard block
 block discarded – undo
330 330
                     $class_dsply = ! empty($class) ? $class : '&nbsp;';
331 331
                     $type_dsply = ! empty($type) ? $type : '&nbsp;';
332 332
                     $function_dsply = ! empty($function) ? $function : '&nbsp;';
333
-                    $args_dsply = ! empty($args) ? '( ' . $args . ' )' : '';
333
+                    $args_dsply = ! empty($args) ? '( '.$args.' )' : '';
334 334
                     $trace_details .= '
335 335
 					<tr>
336
-						<td align="right" class="' . $zebra . '">' . $nmbr_dsply . '</td>
337
-						<td align="right" class="' . $zebra . '">' . $line_dsply . '</td>
338
-						<td align="left" class="' . $zebra . '">' . $file_dsply . '</td>
339
-						<td align="left" class="' . $zebra . '">' . $class_dsply . '</td>
340
-						<td align="left" class="' . $zebra . '">' . $type_dsply . $function_dsply . $args_dsply . '</td>
336
+						<td align="right" class="' . $zebra.'">'.$nmbr_dsply.'</td>
337
+						<td align="right" class="' . $zebra.'">'.$line_dsply.'</td>
338
+						<td align="left" class="' . $zebra.'">'.$file_dsply.'</td>
339
+						<td align="left" class="' . $zebra.'">'.$class_dsply.'</td>
340
+						<td align="left" class="' . $zebra.'">'.$type_dsply.$function_dsply.$args_dsply.'</td>
341 341
 					</tr>';
342 342
                 }
343 343
                 $trace_details .= '
@@ -346,7 +346,7 @@  discard block
 block discarded – undo
346 346
             }
347 347
             $ex['code'] = $ex['code'] ? $ex['code'] : $error_code;
348 348
             // add generic non-identifying messages for non-privileged users
349
-            if (! WP_DEBUG) {
349
+            if ( ! WP_DEBUG) {
350 350
                 $output .= '<span class="ee-error-user-msg-spn">'
351 351
                            . trim($ex['msg'])
352 352
                            . '</span> &nbsp; <sup>'
@@ -388,14 +388,14 @@  discard block
 block discarded – undo
388 388
                            . '-dv" class="ee-error-trace-dv" style="display: none;">
389 389
 				'
390 390
                            . $trace_details;
391
-                if (! empty($class)) {
391
+                if ( ! empty($class)) {
392 392
                     $output .= '
393 393
 				<div style="padding:3px; margin:0 0 1em; border:1px solid #666; background:#fff; border-radius:3px;">
394 394
 					<div style="padding:1em 2em; border:1px solid #666; background:#f9f9f9;">
395 395
 						<h3>Class Details</h3>';
396 396
                     $a = new ReflectionClass($class);
397 397
                     $output .= '
398
-						<pre>' . $a . '</pre>
398
+						<pre>' . $a.'</pre>
399 399
 					</div>
400 400
 				</div>';
401 401
                 }
@@ -408,7 +408,7 @@  discard block
 block discarded – undo
408 408
         }
409 409
         // remove last linebreak
410 410
         $output = substr($output, 0, -6);
411
-        if (! WP_DEBUG) {
411
+        if ( ! WP_DEBUG) {
412 412
             $output .= '
413 413
 	        </p>';
414 414
         }
@@ -434,20 +434,20 @@  discard block
 block discarded – undo
434 434
     private function _convert_args_to_string($arguments = array(), $array = false)
435 435
     {
436 436
         $arg_string = '';
437
-        if (! empty($arguments)) {
437
+        if ( ! empty($arguments)) {
438 438
             $args = array();
439 439
             foreach ($arguments as $arg) {
440
-                if (! empty($arg)) {
440
+                if ( ! empty($arg)) {
441 441
                     if (is_string($arg)) {
442
-                        $args[] = " '" . $arg . "'";
442
+                        $args[] = " '".$arg."'";
443 443
                     } elseif (is_array($arg)) {
444
-                        $args[] = 'ARRAY(' . $this->_convert_args_to_string($arg, true);
444
+                        $args[] = 'ARRAY('.$this->_convert_args_to_string($arg, true);
445 445
                     } elseif ($arg === null) {
446 446
                         $args[] = ' NULL';
447 447
                     } elseif (is_bool($arg)) {
448 448
                         $args[] = ($arg) ? ' TRUE' : ' FALSE';
449 449
                     } elseif (is_object($arg)) {
450
-                        $args[] = ' OBJECT ' . get_class($arg);
450
+                        $args[] = ' OBJECT '.get_class($arg);
451 451
                     } elseif (is_resource($arg)) {
452 452
                         $args[] = get_resource_type($arg);
453 453
                     } else {
@@ -545,7 +545,7 @@  discard block
 block discarded – undo
545 545
     {
546 546
         if (empty($msg)) {
547 547
             EE_Error::doing_it_wrong(
548
-                'EE_Error::add_' . $type . '()',
548
+                'EE_Error::add_'.$type.'()',
549 549
                 sprintf(
550 550
                     esc_html__(
551 551
                         'Notifications are not much use without a message! Please add a message to the EE_Error::add_%s() call made in %s on line %d',
@@ -585,17 +585,17 @@  discard block
 block discarded – undo
585 585
         do_action('AHEE__EE_Error___add_notice', $type, $user_msg, $dev_msg, $file, $func, $line);
586 586
         $msg = WP_DEBUG ? $dev_msg : $user_msg;
587 587
         // add notice if message exists
588
-        if (! empty($msg)) {
588
+        if ( ! empty($msg)) {
589 589
             // get error code
590 590
             $notice_code = EE_Error::generate_error_code($file, $func, $line);
591 591
             if (WP_DEBUG && $type === 'errors') {
592
-                $msg .= '<br/><span class="tiny-text">' . $notice_code . '</span>';
592
+                $msg .= '<br/><span class="tiny-text">'.$notice_code.'</span>';
593 593
             }
594 594
             // add notice. Index by code if it's not blank
595 595
             if ($notice_code) {
596
-                self::$_espresso_notices[ $type ][ $notice_code ] = $msg;
596
+                self::$_espresso_notices[$type][$notice_code] = $msg;
597 597
             } else {
598
-                self::$_espresso_notices[ $type ][] = $msg;
598
+                self::$_espresso_notices[$type][] = $msg;
599 599
             }
600 600
             add_action('wp_footer', array('EE_Error', 'enqueue_error_scripts'), 1);
601 601
         }
@@ -829,7 +829,7 @@  discard block
 block discarded – undo
829 829
                 ? esc_html__('The following errors have occurred:', 'event_espresso')
830 830
                 : esc_html__('An error has occurred:', 'event_espresso');
831 831
             // combine messages
832
-            $error_messages .= '<br />' . implode('<br />', self::$_espresso_notices['errors']);
832
+            $error_messages .= '<br />'.implode('<br />', self::$_espresso_notices['errors']);
833 833
             $print_scripts = true;
834 834
         }
835 835
         if ($format_output) {
@@ -848,7 +848,7 @@  discard block
 block discarded – undo
848 848
                 // remove empty notices
849 849
                 foreach ($notices as $type => $notice) {
850 850
                     if (empty($notice)) {
851
-                        unset($notices[ $type ]);
851
+                        unset($notices[$type]);
852 852
                     }
853 853
                 }
854 854
             }
@@ -871,16 +871,16 @@  discard block
 block discarded – undo
871 871
         $print_scripts = false;
872 872
         // grab any notices that have been previously saved
873 873
         $notices = EE_Error::getStoredNotices();
874
-        if (! empty($notices)) {
874
+        if ( ! empty($notices)) {
875 875
             foreach ($notices as $type => $notice) {
876 876
                 if (is_array($notice) && ! empty($notice)) {
877 877
                     // make sure that existing notice type is an array
878
-                    self::$_espresso_notices[ $type ] = is_array(self::$_espresso_notices[ $type ])
879
-                                                        && ! empty(self::$_espresso_notices[ $type ])
880
-                        ? self::$_espresso_notices[ $type ]
878
+                    self::$_espresso_notices[$type] = is_array(self::$_espresso_notices[$type])
879
+                                                        && ! empty(self::$_espresso_notices[$type])
880
+                        ? self::$_espresso_notices[$type]
881 881
                         : array();
882 882
                     // add newly created notices to existing ones
883
-                    self::$_espresso_notices[ $type ] += $notice;
883
+                    self::$_espresso_notices[$type] += $notice;
884 884
                     $print_scripts = true;
885 885
                 }
886 886
             }
@@ -907,10 +907,10 @@  discard block
 block discarded – undo
907 907
             $css_id = is_admin() ? 'ee-success-message' : 'espresso-notices-success';
908 908
             $css_class = is_admin() ? 'updated fade' : 'success fade-away';
909 909
             // showMessage( $success_messages );
910
-            $notices .= '<div id="' . $css_id . '" '
911
-                        . 'class="espresso-notices ' . $css_class . '" '
910
+            $notices .= '<div id="'.$css_id.'" '
911
+                        . 'class="espresso-notices '.$css_class.'" '
912 912
                         . 'style="display:none;">'
913
-                        . '<p>' . $success_messages . '</p>'
913
+                        . '<p>'.$success_messages.'</p>'
914 914
                         . $close
915 915
                         . '</div>';
916 916
         }
@@ -918,10 +918,10 @@  discard block
 block discarded – undo
918 918
             $css_id = is_admin() ? 'ee-attention-message' : 'espresso-notices-attention';
919 919
             $css_class = is_admin() ? 'updated ee-notices-attention' : 'attention fade-away';
920 920
             // showMessage( $error_messages, TRUE );
921
-            $notices .= '<div id="' . $css_id . '" '
922
-                        . 'class="espresso-notices ' . $css_class . '" '
921
+            $notices .= '<div id="'.$css_id.'" '
922
+                        . 'class="espresso-notices '.$css_class.'" '
923 923
                         . 'style="display:none;">'
924
-                        . '<p>' . $attention_messages . '</p>'
924
+                        . '<p>'.$attention_messages.'</p>'
925 925
                         . $close
926 926
                         . '</div>';
927 927
         }
@@ -929,10 +929,10 @@  discard block
 block discarded – undo
929 929
             $css_id = is_admin() ? 'ee-error-message' : 'espresso-notices-error';
930 930
             $css_class = is_admin() ? 'error' : 'error fade-away';
931 931
             // showMessage( $error_messages, TRUE );
932
-            $notices .= '<div id="' . $css_id . '" '
933
-                        . 'class="espresso-notices ' . $css_class . '" '
932
+            $notices .= '<div id="'.$css_id.'" '
933
+                        . 'class="espresso-notices '.$css_class.'" '
934 934
                         . 'style="display:none;">'
935
-                        . '<p>' . $error_messages . '</p>'
935
+                        . '<p>'.$error_messages.'</p>'
936 936
                         . $close
937 937
                         . '</div>';
938 938
         }
@@ -949,7 +949,7 @@  discard block
 block discarded – undo
949 949
      */
950 950
     private static function _print_scripts($force_print = false)
951 951
     {
952
-        if (! $force_print && (did_action('admin_enqueue_scripts') || did_action('wp_enqueue_scripts'))) {
952
+        if ( ! $force_print && (did_action('admin_enqueue_scripts') || did_action('wp_enqueue_scripts'))) {
953 953
             if (wp_script_is('ee_error_js', 'registered')) {
954 954
                 wp_enqueue_style('espresso_default');
955 955
                 wp_enqueue_style('espresso_custom_css');
@@ -963,12 +963,12 @@  discard block
 block discarded – undo
963 963
             return '
964 964
                 <script>
965 965
                 /* <![CDATA[ */
966
-                var ee_settings = {"wp_debug":"' . WP_DEBUG . '"};
966
+                var ee_settings = {"wp_debug":"' . WP_DEBUG.'"};
967 967
                 /* ]]> */
968 968
                 </script>
969
-                <script src="' . includes_url() . 'js/jquery/jquery.js" type="text/javascript"></script>
970
-                <script src="' . EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js' . '?ver=' . espresso_version() . '" type="text/javascript"></script>
971
-                <script src="' . EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js' . '?ver=' . espresso_version() . '" type="text/javascript"></script>
969
+                <script src="' . includes_url().'js/jquery/jquery.js" type="text/javascript"></script>
970
+                <script src="' . EE_GLOBAL_ASSETS_URL.'scripts/espresso_core.js'.'?ver='.espresso_version().'" type="text/javascript"></script>
971
+                <script src="' . EE_GLOBAL_ASSETS_URL.'scripts/EE_Error.js'.'?ver='.espresso_version().'" type="text/javascript"></script>
972 972
             ';
973 973
         }
974 974
         return '';
@@ -997,8 +997,8 @@  discard block
 block discarded – undo
997 997
     {
998 998
         $file = explode('.', basename($file));
999 999
         $error_code = ! empty($file[0]) ? $file[0] : '';
1000
-        $error_code .= ! empty($func) ? ' - ' . $func : '';
1001
-        $error_code .= ! empty($line) ? ' - ' . $line : '';
1000
+        $error_code .= ! empty($func) ? ' - '.$func : '';
1001
+        $error_code .= ! empty($line) ? ' - '.$line : '';
1002 1002
         return $error_code;
1003 1003
     }
1004 1004
 
@@ -1017,18 +1017,18 @@  discard block
 block discarded – undo
1017 1017
         if (empty($ex)) {
1018 1018
             return;
1019 1019
         }
1020
-        if (! $time) {
1020
+        if ( ! $time) {
1021 1021
             $time = time();
1022 1022
         }
1023 1023
         $exception_log = '----------------------------------------------------------------------------------------'
1024 1024
                          . PHP_EOL;
1025
-        $exception_log .= '[' . date('Y-m-d H:i:s', $time) . ']  Exception Details' . PHP_EOL;
1026
-        $exception_log .= 'Message: ' . $ex['msg'] . PHP_EOL;
1027
-        $exception_log .= 'Code: ' . $ex['code'] . PHP_EOL;
1028
-        $exception_log .= 'File: ' . $ex['file'] . PHP_EOL;
1029
-        $exception_log .= 'Line No: ' . $ex['line'] . PHP_EOL;
1030
-        $exception_log .= 'Stack trace: ' . PHP_EOL;
1031
-        $exception_log .= $ex['string'] . PHP_EOL;
1025
+        $exception_log .= '['.date('Y-m-d H:i:s', $time).']  Exception Details'.PHP_EOL;
1026
+        $exception_log .= 'Message: '.$ex['msg'].PHP_EOL;
1027
+        $exception_log .= 'Code: '.$ex['code'].PHP_EOL;
1028
+        $exception_log .= 'File: '.$ex['file'].PHP_EOL;
1029
+        $exception_log .= 'Line No: '.$ex['line'].PHP_EOL;
1030
+        $exception_log .= 'Stack trace: '.PHP_EOL;
1031
+        $exception_log .= $ex['string'].PHP_EOL;
1032 1032
         $exception_log .= '----------------------------------------------------------------------------------------'
1033 1033
                           . PHP_EOL;
1034 1034
         try {
@@ -1198,10 +1198,10 @@  discard block
 block discarded – undo
1198 1198
 function espresso_error_enqueue_scripts()
1199 1199
 {
1200 1200
     // js for error handling
1201
-    if (! wp_script_is('espresso_core', 'registered')) {
1201
+    if ( ! wp_script_is('espresso_core', 'registered')) {
1202 1202
         wp_register_script(
1203 1203
             'espresso_core',
1204
-            EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js',
1204
+            EE_GLOBAL_ASSETS_URL.'scripts/espresso_core.js',
1205 1205
             ['jquery'],
1206 1206
             EVENT_ESPRESSO_VERSION,
1207 1207
             false
@@ -1209,7 +1209,7 @@  discard block
 block discarded – undo
1209 1209
     }
1210 1210
     wp_register_script(
1211 1211
         'ee_error_js',
1212
-        EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js',
1212
+        EE_GLOBAL_ASSETS_URL.'scripts/EE_Error.js',
1213 1213
         array('espresso_core'),
1214 1214
         EVENT_ESPRESSO_VERSION
1215 1215
     );
Please login to merge, or discard this patch.