Completed
Branch FET-9856-direct-instantiation (36dffe)
by
unknown
95:52 queued 84:39
created
admin/extend/registration_form/Extend_Registration_Form_Admin_Page.core.php 1 patch
Indentation   +1088 added lines, -1088 removed lines patch added patch discarded remove patch
@@ -1,6 +1,6 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 if (! defined('EVENT_ESPRESSO_VERSION')) {
3
-    exit('NO direct script access allowed');
3
+	exit('NO direct script access allowed');
4 4
 }
5 5
 
6 6
 /**
@@ -25,1093 +25,1093 @@  discard block
 block discarded – undo
25 25
 {
26 26
 
27 27
 
28
-    /**
29
-     * @Constructor
30
-     * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
31
-     * @access public
32
-     */
33
-    public function __construct($routing = true)
34
-    {
35
-        define('REGISTRATION_FORM_CAF_ADMIN', EE_CORE_CAF_ADMIN_EXTEND . 'registration_form' . DS);
36
-        define('REGISTRATION_FORM_CAF_ASSETS_PATH', REGISTRATION_FORM_CAF_ADMIN . 'assets' . DS);
37
-        define('REGISTRATION_FORM_CAF_ASSETS_URL', EE_CORE_CAF_ADMIN_EXTEND_URL . 'registration_form/assets/');
38
-        define('REGISTRATION_FORM_CAF_TEMPLATE_PATH', REGISTRATION_FORM_CAF_ADMIN . 'templates' . DS);
39
-        define('REGISTRATION_FORM_CAF_TEMPLATE_URL', EE_CORE_CAF_ADMIN_EXTEND_URL . 'registration_form/templates/');
40
-        parent::__construct($routing);
41
-    }
42
-
43
-
44
-    protected function _extend_page_config()
45
-    {
46
-        $this->_admin_base_path = REGISTRATION_FORM_CAF_ADMIN;
47
-        $qst_id = ! empty($this->_req_data['QST_ID']) && ! is_array($this->_req_data['QST_ID']) ? $this->_req_data['QST_ID'] : 0;
48
-        $qsg_id = ! empty($this->_req_data['QSG_ID']) && ! is_array($this->_req_data['QSG_ID']) ? $this->_req_data['QSG_ID'] : 0;
49
-
50
-        $new_page_routes    = array(
51
-            'question_groups'    => array(
52
-                'func'       => '_question_groups_overview_list_table',
53
-                'capability' => 'ee_read_question_groups',
54
-            ),
55
-            'add_question'       => array(
56
-                'func'       => '_edit_question',
57
-                'capability' => 'ee_edit_questions',
58
-            ),
59
-            'insert_question'    => array(
60
-                'func'       => '_insert_or_update_question',
61
-                'args'       => array('new_question' => true),
62
-                'capability' => 'ee_edit_questions',
63
-                'noheader'   => true,
64
-            ),
65
-            'duplicate_question' => array(
66
-                'func'       => '_duplicate_question',
67
-                'capability' => 'ee_edit_questions',
68
-                'noheader'   => true,
69
-            ),
70
-            'trash_question'     => array(
71
-                'func'       => '_trash_question',
72
-                'capability' => 'ee_delete_question',
73
-                'obj_id'     => $qst_id,
74
-                'noheader'   => true,
75
-            ),
76
-
77
-            'restore_question' => array(
78
-                'func'       => '_trash_or_restore_questions',
79
-                'capability' => 'ee_delete_question',
80
-                'obj_id'     => $qst_id,
81
-                'args'       => array('trash' => false),
82
-                'noheader'   => true,
83
-            ),
84
-
85
-            'delete_question' => array(
86
-                'func'       => '_delete_question',
87
-                'capability' => 'ee_delete_question',
88
-                'obj_id'     => $qst_id,
89
-                'noheader'   => true,
90
-            ),
91
-
92
-            'trash_questions' => array(
93
-                'func'       => '_trash_or_restore_questions',
94
-                'capability' => 'ee_delete_questions',
95
-                'args'       => array('trash' => true),
96
-                'noheader'   => true,
97
-            ),
98
-
99
-            'restore_questions' => array(
100
-                'func'       => '_trash_or_restore_questions',
101
-                'capability' => 'ee_delete_questions',
102
-                'args'       => array('trash' => false),
103
-                'noheader'   => true,
104
-            ),
105
-
106
-            'delete_questions' => array(
107
-                'func'       => '_delete_questions',
108
-                'args'       => array(),
109
-                'capability' => 'ee_delete_questions',
110
-                'noheader'   => true,
111
-            ),
112
-
113
-            'add_question_group' => array(
114
-                'func'       => '_edit_question_group',
115
-                'capability' => 'ee_edit_question_groups',
116
-            ),
117
-
118
-            'edit_question_group' => array(
119
-                'func'       => '_edit_question_group',
120
-                'capability' => 'ee_edit_question_group',
121
-                'obj_id'     => $qsg_id,
122
-                'args'       => array('edit'),
123
-            ),
124
-
125
-            'delete_question_groups' => array(
126
-                'func'       => '_delete_question_groups',
127
-                'capability' => 'ee_delete_question_groups',
128
-                'noheader'   => true,
129
-            ),
130
-
131
-            'delete_question_group' => array(
132
-                'func'       => '_delete_question_groups',
133
-                'capability' => 'ee_delete_question_group',
134
-                'obj_id'     => $qsg_id,
135
-                'noheader'   => true,
136
-            ),
137
-
138
-            'trash_question_group' => array(
139
-                'func'       => '_trash_or_restore_question_groups',
140
-                'args'       => array('trash' => true),
141
-                'capability' => 'ee_delete_question_group',
142
-                'obj_id'     => $qsg_id,
143
-                'noheader'   => true,
144
-            ),
145
-
146
-            'restore_question_group' => array(
147
-                'func'       => '_trash_or_restore_question_groups',
148
-                'args'       => array('trash' => false),
149
-                'capability' => 'ee_delete_question_group',
150
-                'obj_id'     => $qsg_id,
151
-                'noheader'   => true,
152
-            ),
153
-
154
-            'insert_question_group' => array(
155
-                'func'       => '_insert_or_update_question_group',
156
-                'args'       => array('new_question_group' => true),
157
-                'capability' => 'ee_edit_question_groups',
158
-                'noheader'   => true,
159
-            ),
160
-
161
-            'update_question_group' => array(
162
-                'func'       => '_insert_or_update_question_group',
163
-                'args'       => array('new_question_group' => false),
164
-                'capability' => 'ee_edit_question_group',
165
-                'obj_id'     => $qsg_id,
166
-                'noheader'   => true,
167
-            ),
168
-
169
-            'trash_question_groups' => array(
170
-                'func'       => '_trash_or_restore_question_groups',
171
-                'args'       => array('trash' => true),
172
-                'capability' => 'ee_delete_question_groups',
173
-                'noheader'   => array('trash' => false),
174
-            ),
175
-
176
-            'restore_question_groups' => array(
177
-                'func'       => '_trash_or_restore_question_groups',
178
-                'args'       => array('trash' => false),
179
-                'capability' => 'ee_delete_question_groups',
180
-                'noheader'   => true,
181
-            ),
182
-
183
-
184
-            'espresso_update_question_group_order' => array(
185
-                'func'       => 'update_question_group_order',
186
-                'capability' => 'ee_edit_question_groups',
187
-                'noheader'   => true,
188
-            ),
189
-
190
-            'view_reg_form_settings' => array(
191
-                'func'       => '_reg_form_settings',
192
-                'capability' => 'manage_options',
193
-            ),
194
-
195
-            'update_reg_form_settings' => array(
196
-                'func'       => '_update_reg_form_settings',
197
-                'capability' => 'manage_options',
198
-                'noheader'   => true,
199
-            ),
200
-        );
201
-        $this->_page_routes = array_merge($this->_page_routes, $new_page_routes);
202
-
203
-        $new_page_config    = array(
204
-
205
-            'question_groups' => array(
206
-                'nav'           => array(
207
-                    'label' => esc_html__('Question Groups', 'event_espresso'),
208
-                    'order' => 20,
209
-                ),
210
-                'list_table'    => 'Registration_Form_Question_Groups_Admin_List_Table',
211
-                'help_tabs'     => array(
212
-                    'registration_form_question_groups_help_tab'                           => array(
213
-                        'title'    => esc_html__('Question Groups', 'event_espresso'),
214
-                        'filename' => 'registration_form_question_groups',
215
-                    ),
216
-                    'registration_form_question_groups_table_column_headings_help_tab'     => array(
217
-                        'title'    => esc_html__('Question Groups Table Column Headings', 'event_espresso'),
218
-                        'filename' => 'registration_form_question_groups_table_column_headings',
219
-                    ),
220
-                    'registration_form_question_groups_views_bulk_actions_search_help_tab' => array(
221
-                        'title'    => esc_html__('Question Groups Views & Bulk Actions & Search', 'event_espresso'),
222
-                        'filename' => 'registration_form_question_groups_views_bulk_actions_search',
223
-                    ),
224
-                ),
225
-                'help_tour'     => array('Registration_Form_Question_Groups_Help_Tour'),
226
-                'metaboxes'     => $this->_default_espresso_metaboxes,
227
-                'require_nonce' => false,
228
-                'qtips'         => array(
229
-                    'EE_Registration_Form_Tips',
230
-                ),
231
-            ),
232
-
233
-            'add_question' => array(
234
-                'nav'           => array(
235
-                    'label'      => esc_html__('Add Question', 'event_espresso'),
236
-                    'order'      => 5,
237
-                    'persistent' => false,
238
-                ),
239
-                'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
240
-                'help_tabs'     => array(
241
-                    'registration_form_add_question_help_tab' => array(
242
-                        'title'    => esc_html__('Add Question', 'event_espresso'),
243
-                        'filename' => 'registration_form_add_question',
244
-                    ),
245
-                ),
246
-                'help_tour'     => array('Registration_Form_Add_Question_Help_Tour'),
247
-                'require_nonce' => false,
248
-            ),
249
-
250
-            'add_question_group' => array(
251
-                'nav'           => array(
252
-                    'label'      => esc_html__('Add Question Group', 'event_espresso'),
253
-                    'order'      => 5,
254
-                    'persistent' => false,
255
-                ),
256
-                'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
257
-                'help_tabs'     => array(
258
-                    'registration_form_add_question_group_help_tab' => array(
259
-                        'title'    => esc_html__('Add Question Group', 'event_espresso'),
260
-                        'filename' => 'registration_form_add_question_group',
261
-                    ),
262
-                ),
263
-                'help_tour'     => array('Registration_Form_Add_Question_Group_Help_Tour'),
264
-                'require_nonce' => false,
265
-            ),
266
-
267
-            'edit_question_group' => array(
268
-                'nav'           => array(
269
-                    'label'      => esc_html__('Edit Question Group', 'event_espresso'),
270
-                    'order'      => 5,
271
-                    'persistent' => false,
272
-                    'url'        => isset($this->_req_data['question_group_id']) ? add_query_arg(array('question_group_id' => $this->_req_data['question_group_id']),
273
-                        $this->_current_page_view_url) : $this->_admin_base_url,
274
-                ),
275
-                'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
276
-                'help_tabs'     => array(
277
-                    'registration_form_edit_question_group_help_tab' => array(
278
-                        'title'    => esc_html__('Edit Question Group', 'event_espresso'),
279
-                        'filename' => 'registration_form_edit_question_group',
280
-                    ),
281
-                ),
282
-                'help_tour'     => array('Registration_Form_Edit_Question_Group_Help_Tour'),
283
-                'require_nonce' => false,
284
-            ),
285
-
286
-            'view_reg_form_settings' => array(
287
-                'nav'           => array(
288
-                    'label' => esc_html__('Reg Form Settings', 'event_espresso'),
289
-                    'order' => 40,
290
-                ),
291
-                'labels'        => array(
292
-                    'publishbox' => esc_html__('Update Settings', 'event_espresso'),
293
-                ),
294
-                'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
295
-                'help_tabs'     => array(
296
-                    'registration_form_reg_form_settings_help_tab' => array(
297
-                        'title'    => esc_html__('Registration Form Settings', 'event_espresso'),
298
-                        'filename' => 'registration_form_reg_form_settings',
299
-                    ),
300
-                ),
301
-                'help_tour'     => array('Registration_Form_Settings_Help_Tour'),
302
-                'require_nonce' => false,
303
-            ),
304
-
305
-        );
306
-        $this->_page_config = array_merge($this->_page_config, $new_page_config);
307
-
308
-        //change the list table we're going to use so it's the NEW list table!
309
-        $this->_page_config['default']['list_table'] = 'Extend_Registration_Form_Questions_Admin_List_Table';
310
-
311
-
312
-        //additional labels
313
-        $new_labels               = array(
314
-            'add_question'          => esc_html__('Add New Question', 'event_espresso'),
315
-            'delete_question'       => esc_html__('Delete Question', 'event_espresso'),
316
-            'add_question_group'    => esc_html__('Add New Question Group', 'event_espresso'),
317
-            'edit_question_group'   => esc_html__('Edit Question Group', 'event_espresso'),
318
-            'delete_question_group' => esc_html__('Delete Question Group', 'event_espresso'),
319
-        );
320
-        $this->_labels['buttons'] = array_merge($this->_labels['buttons'], $new_labels);
321
-
322
-    }
323
-
324
-
325
-    protected function _ajax_hooks()
326
-    {
327
-        add_action('wp_ajax_espresso_update_question_group_order', array($this, 'update_question_group_order'));
328
-    }
329
-
330
-
331
-    public function load_scripts_styles_question_groups()
332
-    {
333
-        wp_enqueue_script('espresso_ajax_table_sorting');
334
-    }
335
-
336
-
337
-    public function load_scripts_styles_add_question_group()
338
-    {
339
-        $this->load_scripts_styles_forms();
340
-        $this->load_sortable_question_script();
341
-    }
342
-
343
-    public function load_scripts_styles_edit_question_group()
344
-    {
345
-        $this->load_scripts_styles_forms();
346
-        $this->load_sortable_question_script();
347
-    }
348
-
349
-
350
-    /**
351
-     * registers and enqueues script for questions
352
-     *
353
-     * @return void
354
-     */
355
-    public function load_sortable_question_script()
356
-    {
357
-        wp_register_script('ee-question-sortable', REGISTRATION_FORM_CAF_ASSETS_URL . 'ee_question_order.js',
358
-            array('jquery-ui-sortable'), EVENT_ESPRESSO_VERSION, true);
359
-        wp_enqueue_script('ee-question-sortable');
360
-    }
361
-
362
-
363
-    protected function _set_list_table_views_default()
364
-    {
365
-        $this->_views = array(
366
-            'all' => array(
367
-                'slug'        => 'all',
368
-                'label'       => esc_html__('View All Questions', 'event_espresso'),
369
-                'count'       => 0,
370
-                'bulk_action' => array(
371
-                    'trash_questions' => esc_html__('Trash', 'event_espresso'),
372
-                ),
373
-            ),
374
-        );
375
-
376
-        if (EE_Registry::instance()->CAP->current_user_can('ee_delete_questions',
377
-            'espresso_registration_form_trash_questions')
378
-        ) {
379
-            $this->_views['trash'] = array(
380
-                'slug'        => 'trash',
381
-                'label'       => esc_html__('Trash', 'event_espresso'),
382
-                'count'       => 0,
383
-                'bulk_action' => array(
384
-                    'delete_questions'  => esc_html__('Delete Permanently', 'event_espresso'),
385
-                    'restore_questions' => esc_html__('Restore', 'event_espresso'),
386
-                ),
387
-            );
388
-        }
389
-    }
390
-
391
-
392
-    protected function _set_list_table_views_question_groups()
393
-    {
394
-        $this->_views = array(
395
-            'all' => array(
396
-                'slug'        => 'all',
397
-                'label'       => esc_html__('All', 'event_espresso'),
398
-                'count'       => 0,
399
-                'bulk_action' => array(
400
-                    'trash_question_groups' => esc_html__('Trash', 'event_espresso'),
401
-                ),
402
-            ),
403
-        );
404
-
405
-        if (EE_Registry::instance()->CAP->current_user_can('ee_delete_question_groups',
406
-            'espresso_registration_form_trash_question_groups')
407
-        ) {
408
-            $this->_views['trash'] = array(
409
-                'slug'        => 'trash',
410
-                'label'       => esc_html__('Trash', 'event_espresso'),
411
-                'count'       => 0,
412
-                'bulk_action' => array(
413
-                    'delete_question_groups'  => esc_html__('Delete Permanently', 'event_espresso'),
414
-                    'restore_question_groups' => esc_html__('Restore', 'event_espresso'),
415
-                ),
416
-            );
417
-        }
418
-    }
419
-
420
-
421
-    protected function _questions_overview_list_table()
422
-    {
423
-        $this->_admin_page_title .= ' ' . $this->get_action_link_or_button(
424
-                'add_question',
425
-                'add_question',
426
-                array(),
427
-                'add-new-h2'
428
-            );
429
-        parent::_questions_overview_list_table();
430
-    }
431
-
432
-
433
-    protected function _question_groups_overview_list_table()
434
-    {
435
-        $this->_search_btn_label = esc_html__('Question Groups', 'event_espresso');
436
-        $this->_admin_page_title .= ' ' . $this->get_action_link_or_button(
437
-                'add_question_group',
438
-                'add_question_group',
439
-                array(),
440
-                'add-new-h2'
441
-            );
442
-        $this->display_admin_list_table_page_with_sidebar();
443
-    }
444
-
445
-
446
-    protected function _delete_question()
447
-    {
448
-        $success = $this->_delete_items($this->_question_model);
449
-        $this->_redirect_after_action(
450
-            $success,
451
-            $this->_question_model->item_name($success),
452
-            'deleted',
453
-            array('action' => 'default', 'status' => 'all')
454
-        );
455
-    }
456
-
457
-
458
-    protected function _delete_questions()
459
-    {
460
-        $success = $this->_delete_items($this->_question_model);
461
-        $this->_redirect_after_action(
462
-            $success,
463
-            $this->_question_model->item_name($success),
464
-            'deleted permanently',
465
-            array('action' => 'default', 'status' => 'trash')
466
-        );
467
-    }
468
-
469
-
470
-    /**
471
-     * Performs the deletion of a single or multiple questions or question groups.
472
-     *
473
-     * @param EEM_Soft_Delete_Base $model
474
-     * @return int number of items deleted permanently
475
-     */
476
-    private function _delete_items(EEM_Soft_Delete_Base $model)
477
-    {
478
-        $success = 0;
479
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
480
-        if (! empty($this->_req_data['checkbox']) && is_array($this->_req_data['checkbox'])) {
481
-            // if array has more than one element than success message should be plural
482
-            $success = count($this->_req_data['checkbox']) > 1 ? 2 : 1;
483
-            // cycle thru bulk action checkboxes
484
-            while (list($ID, $value) = each($this->_req_data['checkbox'])) {
485
-                if (! $this->_delete_item($ID, $model)) {
486
-                    $success = 0;
487
-                }
488
-            }
489
-
490
-        } elseif (! empty($this->_req_data['QSG_ID'])) {
491
-            $success = $this->_delete_item($this->_req_data['QSG_ID'], $model);
492
-
493
-        } elseif (! empty($this->_req_data['QST_ID'])) {
494
-            $success = $this->_delete_item($this->_req_data['QST_ID'], $model);
495
-        } else {
496
-            EE_Error::add_error(sprintf(esc_html__("No Questions or Question Groups were selected for deleting. This error usually shows when you've attempted to delete via bulk action but there were no selections.",
497
-                "event_espresso")), __FILE__, __FUNCTION__, __LINE__);
498
-        }
499
-        return $success;
500
-    }
501
-
502
-    /**
503
-     * Deletes the specified question (and its associated question options) or question group
504
-     *
505
-     * @param int                  $id
506
-     * @param EEM_Soft_Delete_Base $model
507
-     * @return boolean
508
-     */
509
-    protected function _delete_item($id, $model)
510
-    {
511
-        if ($model instanceof EEM_Question) {
512
-            EEM_Question_Option::instance()->delete_permanently(array(array('QST_ID' => absint($id))));
513
-        }
514
-        return $model->delete_permanently_by_ID(absint($id));
515
-    }
516
-
517
-
518
-    /******************************    QUESTION GROUPS    ******************************/
519
-
520
-
521
-    protected function _edit_question_group($type = 'add')
522
-    {
523
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
524
-        $ID = isset($this->_req_data['QSG_ID']) && ! empty($this->_req_data['QSG_ID']) ? absint($this->_req_data['QSG_ID']) : false;
525
-
526
-        switch ($this->_req_action) {
527
-            case 'add_question_group' :
528
-                $this->_admin_page_title = esc_html__('Add Question Group', 'event_espresso');
529
-                break;
530
-            case 'edit_question_group' :
531
-                $this->_admin_page_title = esc_html__('Edit Question Group', 'event_espresso');
532
-                break;
533
-            default :
534
-                $this->_admin_page_title = ucwords(str_replace('_', ' ', $this->_req_action));
535
-        }
536
-        // add ID to title if editing
537
-        $this->_admin_page_title = $ID ? $this->_admin_page_title . ' # ' . $ID : $this->_admin_page_title;
538
-        if ($ID) {
539
-            /** @var EE_Question_Group $questionGroup */
540
-            $questionGroup            = $this->_question_group_model->get_one_by_ID($ID);
541
-            $additional_hidden_fields = array('QSG_ID' => array('type' => 'hidden', 'value' => $ID));
542
-            $this->_set_add_edit_form_tags('update_question_group', $additional_hidden_fields);
543
-        } else {
544
-            /** @var EE_Question_Group $questionGroup */
545
-            $questionGroup = EEM_Question_Group::instance()->create_default_object();
546
-            $questionGroup->set_order_to_latest();
547
-            $this->_set_add_edit_form_tags('insert_question_group');
548
-        }
549
-        $this->_template_args['values']         = $this->_yes_no_values;
550
-        $this->_template_args['all_questions']  = $questionGroup->questions_in_and_not_in_group();
551
-        $this->_template_args['QSG_ID']         = $ID ? $ID : true;
552
-        $this->_template_args['question_group'] = $questionGroup;
553
-
554
-        $redirect_URL = add_query_arg(array('action' => 'question_groups'), $this->_admin_base_url);
555
-        $this->_set_publish_post_box_vars('id', $ID, false, $redirect_URL);
556
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(REGISTRATION_FORM_CAF_TEMPLATE_PATH . 'question_groups_main_meta_box.template.php',
557
-            $this->_template_args, true);
558
-
559
-        // the details template wrapper
560
-        $this->display_admin_page_with_sidebar();
561
-    }
562
-
563
-
564
-    protected function _delete_question_groups()
565
-    {
566
-        $success = $this->_delete_items($this->_question_group_model);
567
-        $this->_redirect_after_action($success, $this->_question_group_model->item_name($success),
568
-            'deleted permanently', array('action' => 'question_groups', 'status' => 'trash'));
569
-    }
570
-
571
-
572
-    /**
573
-     * @param bool $new_question_group
574
-     */
575
-    protected function _insert_or_update_question_group($new_question_group = true)
576
-    {
577
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
578
-        $set_column_values = $this->_set_column_values_for($this->_question_group_model);
579
-        if ($new_question_group) {
580
-            $QSG_ID  = $this->_question_group_model->insert($set_column_values);
581
-            $success = $QSG_ID ? 1 : 0;
582
-        } else {
583
-            $QSG_ID = absint($this->_req_data['QSG_ID']);
584
-            unset($set_column_values['QSG_ID']);
585
-            $success = $this->_question_group_model->update($set_column_values, array(array('QSG_ID' => $QSG_ID)));
586
-        }
587
-        $phone_question_id = EEM_Question::instance()->get_Question_ID_from_system_string(EEM_Attendee::system_question_phone);
588
-        // update the existing related questions
589
-        // BUT FIRST...  delete the phone question from the Question_Group_Question if it is being added to this question group (therefore removed from the existing group)
590
-        if (isset($this->_req_data['questions'], $this->_req_data['questions'][$phone_question_id])) {
591
-            // delete where QST ID = system phone question ID and Question Group ID is NOT this group
592
-            EEM_Question_Group_Question::instance()->delete(array(
593
-                array(
594
-                    'QST_ID' => $phone_question_id,
595
-                    'QSG_ID' => array('!=', $QSG_ID),
596
-                ),
597
-            ));
598
-        }
599
-        /** @type EE_Question_Group $question_group */
600
-        $question_group = $this->_question_group_model->get_one_by_ID($QSG_ID);
601
-        $questions      = $question_group->questions();
602
-        // make sure system phone question is added to list of questions for this group
603
-        if (! isset($questions[$phone_question_id])) {
604
-            $questions[$phone_question_id] = EEM_Question::instance()->get_one_by_ID($phone_question_id);
605
-        }
606
-
607
-        foreach ($questions as $question_ID => $question) {
608
-            // first we always check for order.
609
-            if (! empty($this->_req_data['question_orders'][$question_ID])) {
610
-                //update question order
611
-                $question_group->update_question_order($question_ID, $this->_req_data['question_orders'][$question_ID]);
612
-            }
613
-
614
-            // then we always check if adding or removing.
615
-            if (isset($this->_req_data['questions'], $this->_req_data['questions'][$question_ID])) {
616
-                $question_group->add_question($question_ID);
617
-            } else {
618
-                // not found, remove it (but only if not a system question for the personal group with the exception of lname system question - we allow removal of it)
619
-                if (
620
-                in_array(
621
-                    $question->system_ID(),
622
-                    EEM_Question::instance()->required_system_questions_in_system_question_group($question_group->system_group())
623
-                )
624
-                ) {
625
-                    continue;
626
-                } else {
627
-                    $question_group->remove_question($question_ID);
628
-                }
629
-            }
630
-        }
631
-        // save new related questions
632
-        if (isset($this->_req_data['questions'])) {
633
-            foreach ($this->_req_data['questions'] as $QST_ID) {
634
-                $question_group->add_question($QST_ID);
635
-                if (isset($this->_req_data['question_orders'][$QST_ID])) {
636
-                    $question_group->update_question_order($QST_ID, $this->_req_data['question_orders'][$QST_ID]);
637
-                }
638
-            }
639
-        }
640
-
641
-        if ($success !== false) {
642
-            $msg = $new_question_group ? sprintf(esc_html__('The %s has been created', 'event_espresso'),
643
-                $this->_question_group_model->item_name()) : sprintf(esc_html__('The %s has been updated',
644
-                'event_espresso'), $this->_question_group_model->item_name());
645
-            EE_Error::add_success($msg);
646
-        }
647
-        $this->_redirect_after_action(false, '', '', array('action' => 'edit_question_group', 'QSG_ID' => $QSG_ID),
648
-            true);
649
-
650
-    }
651
-
652
-    /**
653
-     * duplicates a question and all its question options and redirects to the new question.
654
-     */
655
-    public function _duplicate_question()
656
-    {
657
-        $question_ID = (int)$this->_req_data['QST_ID'];
658
-        $question    = EEM_Question::instance()->get_one_by_ID($question_ID);
659
-        if ($question instanceof EE_Question) {
660
-            $new_question = $question->duplicate();
661
-            if ($new_question instanceof EE_Question) {
662
-                $this->_redirect_after_action(true, esc_html__('Question', 'event_espresso'),
663
-                    esc_html__('Duplicated', 'event_espresso'),
664
-                    array('action' => 'edit_question', 'QST_ID' => $new_question->ID()), true);
665
-            } else {
666
-                global $wpdb;
667
-                EE_Error::add_error(sprintf(esc_html__('Could not duplicate question with ID %1$d because: %2$s',
668
-                    'event_espresso'), $question_ID, $wpdb->last_error), __FILE__, __FUNCTION__, __LINE__);
669
-                $this->_redirect_after_action(false, '', '', array('action' => 'default'), false);
670
-            }
671
-        } else {
672
-            EE_Error::add_error(sprintf(esc_html__('Could not duplicate question with ID %d because it didn\'t exist!',
673
-                'event_espresso'), $question_ID), __FILE__, __FUNCTION__, __LINE__);
674
-            $this->_redirect_after_action(false, '', '', array('action' => 'default'), false);
675
-        }
676
-    }
677
-
678
-
679
-    /**
680
-     * @param bool $trash
681
-     */
682
-    protected function _trash_or_restore_question_groups($trash = true)
683
-    {
684
-        $this->_trash_or_restore_items($this->_question_group_model, $trash);
685
-    }
686
-
687
-
688
-    /**
689
-     *_trash_question
690
-     */
691
-    protected function _trash_question()
692
-    {
693
-        $success    = $this->_question_model->delete_by_ID((int)$this->_req_data['QST_ID']);
694
-        $query_args = array('action' => 'default', 'status' => 'all');
695
-        $this->_redirect_after_action($success, $this->_question_model->item_name($success), 'trashed', $query_args);
696
-    }
697
-
698
-
699
-    /**
700
-     * @param bool $trash
701
-     */
702
-    protected function _trash_or_restore_questions($trash = true)
703
-    {
704
-        $this->_trash_or_restore_items($this->_question_model, $trash);
705
-    }
706
-
707
-
708
-    /**
709
-     * Internally used to delete or restore items, using the request data. Meant to be
710
-     * flexible between question or question groups
711
-     *
712
-     * @param EEM_Soft_Delete_Base $model
713
-     * @param boolean              $trash whether to trash or restore
714
-     */
715
-    private function _trash_or_restore_items(EEM_Soft_Delete_Base $model, $trash = true)
716
-    {
717
-
718
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
719
-
720
-        $success = 1;
721
-        //Checkboxes
722
-        //echo "trash $trash";
723
-        //var_dump($this->_req_data['checkbox']);die;
724
-        if (isset($this->_req_data['checkbox'])) {
725
-            if (! empty($this->_req_data['checkbox']) && is_array($this->_req_data['checkbox'])) {
726
-                // if array has more than one element than success message should be plural
727
-                $success = count($this->_req_data['checkbox']) > 1 ? 2 : 1;
728
-                // cycle thru bulk action checkboxes
729
-                while (list($ID, $value) = each($this->_req_data['checkbox'])) {
730
-                    if (! $model->delete_or_restore_by_ID($trash, absint($ID))) {
731
-                        $success = 0;
732
-                    }
733
-                }
734
-
735
-            } else {
736
-                // grab single id and delete
737
-                $ID = absint($this->_req_data['checkbox']);
738
-                if (! $model->delete_or_restore_by_ID($trash, $ID)) {
739
-                    $success = 0;
740
-                }
741
-            }
742
-
743
-        } else {
744
-            // delete via trash link
745
-            // grab single id and delete
746
-            $ID = absint($this->_req_data[$model->primary_key_name()]);
747
-            if (! $model->delete_or_restore_by_ID($trash, $ID)) {
748
-                $success = 0;
749
-            }
750
-
751
-        }
752
-
753
-
754
-        $action = $model instanceof EEM_Question ? 'default' : 'question_groups';//strtolower( $model->item_name(2) );
755
-        //echo "action :$action";
756
-        //$action = 'questions' ? 'default' : $action;
757
-        if ($trash) {
758
-            $action_desc = 'trashed';
759
-            $status      = 'trash';
760
-        } else {
761
-            $action_desc = 'restored';
762
-            $status      = 'all';
763
-        }
764
-        $this->_redirect_after_action($success, $model->item_name($success), $action_desc,
765
-            array('action' => $action, 'status' => $status));
766
-    }
767
-
768
-
769
-    /**
770
-     * @param            $per_page
771
-     * @param int        $current_page
772
-     * @param bool|false $count
773
-     * @return \EE_Soft_Delete_Base_Class[]|int
774
-     */
775
-    public function get_trashed_questions($per_page, $current_page = 1, $count = false)
776
-    {
777
-        $query_params = $this->get_query_params(EEM_Question::instance(), $per_page, $current_page);
778
-
779
-        if ($count) {
780
-            //note: this a subclass of EEM_Soft_Delete_Base, so this is actually only getting non-trashed items
781
-            $where   = isset($query_params[0]) ? array($query_params[0]) : array();
782
-            $results = $this->_question_model->count_deleted($where);
783
-        } else {
784
-            //note: this a subclass of EEM_Soft_Delete_Base, so this is actually only getting non-trashed items
785
-            $results = $this->_question_model->get_all_deleted($query_params);
786
-        }
787
-        return $results;
788
-    }
789
-
790
-
791
-    /**
792
-     * @param            $per_page
793
-     * @param int        $current_page
794
-     * @param bool|false $count
795
-     * @return \EE_Soft_Delete_Base_Class[]
796
-     */
797
-    public function get_question_groups($per_page, $current_page = 1, $count = false)
798
-    {
799
-        $questionGroupModel = EEM_Question_Group::instance();
800
-        $query_params       = $this->get_query_params($questionGroupModel, $per_page, $current_page);
801
-        if ($count) {
802
-            $where   = isset($query_params[0]) ? array($query_params[0]) : array();
803
-            $results = $questionGroupModel->count($where);
804
-        } else {
805
-            $results = $questionGroupModel->get_all($query_params);
806
-        }
807
-        return $results;
808
-    }
809
-
810
-
811
-    /**
812
-     * @param      $per_page
813
-     * @param int  $current_page
814
-     * @param bool $count
815
-     * @return \EE_Soft_Delete_Base_Class[]|int
816
-     */
817
-    public function get_trashed_question_groups($per_page, $current_page = 1, $count = false)
818
-    {
819
-        $questionGroupModel = EEM_Question_Group::instance();
820
-        $query_params       = $this->get_query_params($questionGroupModel, $per_page, $current_page);
821
-        if ($count) {
822
-            $where                 = isset($query_params[0]) ? array($query_params[0]) : array();
823
-            $query_params['limit'] = null;
824
-            $results               = $questionGroupModel->count_deleted($where);
825
-        } else {
826
-            $results = $questionGroupModel->get_all_deleted($query_params);
827
-        }
828
-        return $results;
829
-    }
830
-
831
-
832
-    /**
833
-     * method for performing updates to question order
834
-     *
835
-     * @return array results array
836
-     */
837
-    public function update_question_group_order()
838
-    {
839
-
840
-        $success = esc_html__('Question group order was updated successfully.', 'event_espresso');
841
-
842
-        // grab our row IDs
843
-        $row_ids = isset($this->_req_data['row_ids']) && ! empty($this->_req_data['row_ids'])
844
-            ? explode(',', rtrim($this->_req_data['row_ids'], ','))
845
-            : array();
846
-
847
-        $perpage = ! empty($this->_req_data['perpage'])
848
-            ? (int)$this->_req_data['perpage']
849
-            : null;
850
-        $curpage = ! empty($this->_req_data['curpage'])
851
-            ? (int)$this->_req_data['curpage']
852
-            : null;
853
-
854
-        if (! empty($row_ids)) {
855
-            //figure out where we start the row_id count at for the current page.
856
-            $qsgcount = empty($curpage) ? 0 : ($curpage - 1) * $perpage;
857
-
858
-            $row_count = count($row_ids);
859
-            for ($i = 0; $i < $row_count; $i++) {
860
-                //Update the questions when re-ordering
861
-                $updated = EEM_Question_Group::instance()->update(
862
-                    array('QSG_order' => $qsgcount),
863
-                    array(array('QSG_ID' => $row_ids[$i]))
864
-                );
865
-                if ($updated === false) {
866
-                    $success = false;
867
-                }
868
-                $qsgcount++;
869
-            }
870
-        } else {
871
-            $success = false;
872
-        }
873
-
874
-        $errors = ! $success
875
-            ? esc_html__('An error occurred. The question group order was not updated.', 'event_espresso')
876
-            : false;
877
-
878
-        echo wp_json_encode(array('return_data' => false, 'success' => $success, 'errors' => $errors));
879
-        die();
880
-
881
-    }
882
-
883
-
884
-
885
-    /***************************************        REGISTRATION SETTINGS        ***************************************/
886
-
887
-
888
-    /**
889
-     * _reg_form_settings
890
-     *
891
-     * @throws \EE_Error
892
-     */
893
-    protected function _reg_form_settings()
894
-    {
895
-        $this->_template_args['values'] = $this->_yes_no_values;
896
-        add_action(
897
-            'AHEE__Extend_Registration_Form_Admin_Page___reg_form_settings_template',
898
-            array($this, 'email_validation_settings_form'),
899
-            2
900
-        );
901
-        $this->_template_args = (array)apply_filters(
902
-            'FHEE__Extend_Registration_Form_Admin_Page___reg_form_settings___template_args',
903
-            $this->_template_args
904
-        );
905
-        $this->_set_add_edit_form_tags('update_reg_form_settings');
906
-        $this->_set_publish_post_box_vars(null, false, false, null, false);
907
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
908
-            REGISTRATION_FORM_CAF_TEMPLATE_PATH . 'reg_form_settings.template.php',
909
-            $this->_template_args,
910
-            true
911
-        );
912
-        $this->display_admin_page_with_sidebar();
913
-    }
914
-
915
-
916
-    /**
917
-     * _update_reg_form_settings
918
-     */
919
-    protected function _update_reg_form_settings()
920
-    {
921
-        EE_Registry::instance()->CFG->registration = $this->update_email_validation_settings_form(
922
-            EE_Registry::instance()->CFG->registration
923
-        );
924
-        EE_Registry::instance()->CFG->registration = apply_filters(
925
-            'FHEE__Extend_Registration_Form_Admin_Page___update_reg_form_settings__CFG_registration',
926
-            EE_Registry::instance()->CFG->registration
927
-        );
928
-        $success                                   = $this->_update_espresso_configuration(
929
-            esc_html__('Registration Form Options', 'event_espresso'),
930
-            EE_Registry::instance()->CFG,
931
-            __FILE__, __FUNCTION__, __LINE__
932
-        );
933
-        $this->_redirect_after_action($success, esc_html__('Registration Form Options', 'event_espresso'), 'updated',
934
-            array('action' => 'view_reg_form_settings'));
935
-    }
936
-
937
-
938
-    /**
939
-     * email_validation_settings_form
940
-     *
941
-     * @access    public
942
-     * @return    void
943
-     * @throws \EE_Error
944
-     */
945
-    public function email_validation_settings_form()
946
-    {
947
-        echo $this->_email_validation_settings_form()->get_html();
948
-    }
949
-
950
-
951
-    /**
952
-     * _email_validation_settings_form
953
-     *
954
-     * @access protected
955
-     * @return EE_Form_Section_Proper
956
-     * @throws \EE_Error
957
-     */
958
-    protected function _email_validation_settings_form()
959
-    {
960
-        return new EE_Form_Section_Proper(
961
-            array(
962
-                'name'            => 'email_validation_settings',
963
-                'html_id'         => 'email_validation_settings',
964
-                'layout_strategy' => new EE_Admin_Two_Column_Layout(),
965
-                'subsections'     => apply_filters(
966
-                    'FHEE__Extend_Registration_Form_Admin_Page___email_validation_settings_form__form_subsections',
967
-                    array(
968
-                        'email_validation_hdr'   => new EE_Form_Section_HTML(
969
-                            EEH_HTML::h2(esc_html__('Email Validation Settings', 'event_espresso'))
970
-                        ),
971
-                        'email_validation_level' => new EE_Select_Input(
972
-                            array(
973
-                                'basic'      => esc_html__('Basic', 'event_espresso'),
974
-                                'wp_default' => esc_html__('WordPress Default', 'event_espresso'),
975
-                                'i18n'       => esc_html__('International', 'event_espresso'),
976
-                                'i18n_dns'   => esc_html__('International + DNS Check', 'event_espresso'),
977
-                            ),
978
-                            array(
979
-                                'html_label_text' => esc_html__('Email Validation Level', 'event_espresso')
980
-                                                     . EEH_Template::get_help_tab_link('email_validation_info'),
981
-                                'html_help_text'  => esc_html__('These levels range from basic validation ( ie: [email protected] ) to more advanced checks against international email addresses (ie: üñîçøðé@example.com ) with additional MX and A record checks to confirm the domain actually exists. More information on on each level can be found within the help section.',
982
-                                    'event_espresso'),
983
-                                'default'         => isset(EE_Registry::instance()->CFG->registration->email_validation_level)
984
-                                    ? EE_Registry::instance()->CFG->registration->email_validation_level
985
-                                    : 'wp_default',
986
-                                'required'        => false,
987
-                            )
988
-                        ),
989
-                    )
990
-                ),
991
-            )
992
-        );
993
-    }
994
-
995
-
996
-    /**
997
-     * update_email_validation_settings_form
998
-     *
999
-     * @access    public
1000
-     * @param \EE_Registration_Config $EE_Registration_Config
1001
-     * @return \EE_Registration_Config
1002
-     */
1003
-    public function update_email_validation_settings_form(EE_Registration_Config $EE_Registration_Config)
1004
-    {
1005
-        $prev_email_validation_level = $EE_Registration_Config->email_validation_level;
1006
-        try {
1007
-            $email_validation_settings_form = $this->_email_validation_settings_form();
1008
-            // if not displaying a form, then check for form submission
1009
-            if ($email_validation_settings_form->was_submitted()) {
1010
-                // capture form data
1011
-                $email_validation_settings_form->receive_form_submission();
1012
-                // validate form data
1013
-                if ($email_validation_settings_form->is_valid()) {
1014
-                    // grab validated data from form
1015
-                    $valid_data = $email_validation_settings_form->valid_data();
1016
-                    if (isset($valid_data['email_validation_level'])) {
1017
-                        $email_validation_level = $valid_data['email_validation_level'];
1018
-                        // now if they want to use international email addresses
1019
-                        if ($email_validation_level === 'i18n' || $email_validation_level === 'i18n_dns') {
1020
-                            // in case we need to reset their email validation level,
1021
-                            // make sure that the previous value wasn't already set to one of the i18n options.
1022
-                            if ($prev_email_validation_level === 'i18n' || $prev_email_validation_level === 'i18n_dns') {
1023
-                                // if so, then reset it back to "basic" since that is the only other option that,
1024
-                                // despite offering poor validation, supports i18n email addresses
1025
-                                $prev_email_validation_level = 'basic';
1026
-                            }
1027
-                            // confirm our i18n email validation will work on the server
1028
-                            if (! $this->_verify_pcre_support($EE_Registration_Config, $email_validation_level)) {
1029
-                                // or reset email validation level to previous value
1030
-                                $email_validation_level = $prev_email_validation_level;
1031
-                            }
1032
-                        }
1033
-                        $EE_Registration_Config->email_validation_level = $email_validation_level;
1034
-                    } else {
1035
-                        EE_Error::add_error(
1036
-                            esc_html__(
1037
-                                'Invalid or missing Email Validation settings. Please refresh the form and try again.',
1038
-                                'event_espresso'
1039
-                            ),
1040
-                            __FILE__, __FUNCTION__, __LINE__
1041
-                        );
1042
-                    }
1043
-                } else {
1044
-                    if ($email_validation_settings_form->submission_error_message() !== '') {
1045
-                        EE_Error::add_error(
1046
-                            $email_validation_settings_form->submission_error_message(),
1047
-                            __FILE__, __FUNCTION__, __LINE__
1048
-                        );
1049
-                    }
1050
-                }
1051
-            }
1052
-        } catch (EE_Error $e) {
1053
-            $e->get_error();
1054
-        }
1055
-        return $EE_Registration_Config;
1056
-    }
1057
-
1058
-
1059
-    /**
1060
-     * confirms that the server's PHP version has the PCRE module enabled,
1061
-     * and that the PCRE version works with our i18n email validation
1062
-     *
1063
-     * @param \EE_Registration_Config $EE_Registration_Config
1064
-     * @param string                  $email_validation_level
1065
-     * @return bool
1066
-     */
1067
-    private function _verify_pcre_support(EE_Registration_Config $EE_Registration_Config, $email_validation_level)
1068
-    {
1069
-        // first check that PCRE is enabled
1070
-        if (! defined('PREG_BAD_UTF8_ERROR')) {
1071
-            EE_Error::add_error(
1072
-                sprintf(
1073
-                    esc_html__(
1074
-                        'We\'re sorry, but it appears that your server\'s version of PHP was not compiled with PCRE unicode support.%1$sPlease contact your hosting company and ask them whether the PCRE compiled with your version of PHP on your server can be been built with the "--enable-unicode-properties" and "--enable-utf8" configuration switches to enable more complex regex expressions.%1$sIf they are unable, or unwilling to do so, then your server will not support international email addresses using UTF-8 unicode characters. This means you will either have to lower your email validation level to "Basic" or "WordPress Default", or switch to a hosting company that has/can enable PCRE unicode support on the server.',
1075
-                        'event_espresso'
1076
-                    ),
1077
-                    '<br />'
1078
-                ),
1079
-                __FILE__,
1080
-                __FUNCTION__,
1081
-                __LINE__
1082
-            );
1083
-            return false;
1084
-        } else {
1085
-            // PCRE support is enabled, but let's still
1086
-            // perform a test to see if the server will support it.
1087
-            // but first, save the updated validation level to the config,
1088
-            // so that the validation strategy picks it up.
1089
-            // this will get bumped back down if it doesn't work
1090
-            $EE_Registration_Config->email_validation_level = $email_validation_level;
1091
-            try {
1092
-                $email_validator    = new EE_Email_Validation_Strategy();
1093
-                $i18n_email_address = apply_filters(
1094
-                    'FHEE__Extend_Registration_Form_Admin_Page__update_email_validation_settings_form__i18n_email_address',
1095
-                    'jägerjü[email protected]'
1096
-                );
1097
-                $email_validator->validate($i18n_email_address);
1098
-            } catch (Exception $e) {
1099
-                EE_Error::add_error(
1100
-                    sprintf(
1101
-                        esc_html__(
1102
-                            'We\'re sorry, but it appears that your server\'s configuration will not support the "International" or "International + DNS Check" email validation levels.%1$sTo correct this issue, please consult with your hosting company regarding your server\'s PCRE settings.%1$sIt is recommended that your PHP version be configured to use PCRE 8.10 or newer.%1$sMore information regarding PCRE versions and installation can be found here: %2$s',
1103
-                            'event_espresso'
1104
-                        ),
1105
-                        '<br />',
1106
-                        '<a href="http://php.net/manual/en/pcre.installation.php" target="_blank">http://php.net/manual/en/pcre.installation.php</a>'
1107
-                    ),
1108
-                    __FILE__, __FUNCTION__, __LINE__
1109
-                );
1110
-                return false;
1111
-            }
1112
-        }
1113
-        return true;
1114
-    }
28
+	/**
29
+	 * @Constructor
30
+	 * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
31
+	 * @access public
32
+	 */
33
+	public function __construct($routing = true)
34
+	{
35
+		define('REGISTRATION_FORM_CAF_ADMIN', EE_CORE_CAF_ADMIN_EXTEND . 'registration_form' . DS);
36
+		define('REGISTRATION_FORM_CAF_ASSETS_PATH', REGISTRATION_FORM_CAF_ADMIN . 'assets' . DS);
37
+		define('REGISTRATION_FORM_CAF_ASSETS_URL', EE_CORE_CAF_ADMIN_EXTEND_URL . 'registration_form/assets/');
38
+		define('REGISTRATION_FORM_CAF_TEMPLATE_PATH', REGISTRATION_FORM_CAF_ADMIN . 'templates' . DS);
39
+		define('REGISTRATION_FORM_CAF_TEMPLATE_URL', EE_CORE_CAF_ADMIN_EXTEND_URL . 'registration_form/templates/');
40
+		parent::__construct($routing);
41
+	}
42
+
43
+
44
+	protected function _extend_page_config()
45
+	{
46
+		$this->_admin_base_path = REGISTRATION_FORM_CAF_ADMIN;
47
+		$qst_id = ! empty($this->_req_data['QST_ID']) && ! is_array($this->_req_data['QST_ID']) ? $this->_req_data['QST_ID'] : 0;
48
+		$qsg_id = ! empty($this->_req_data['QSG_ID']) && ! is_array($this->_req_data['QSG_ID']) ? $this->_req_data['QSG_ID'] : 0;
49
+
50
+		$new_page_routes    = array(
51
+			'question_groups'    => array(
52
+				'func'       => '_question_groups_overview_list_table',
53
+				'capability' => 'ee_read_question_groups',
54
+			),
55
+			'add_question'       => array(
56
+				'func'       => '_edit_question',
57
+				'capability' => 'ee_edit_questions',
58
+			),
59
+			'insert_question'    => array(
60
+				'func'       => '_insert_or_update_question',
61
+				'args'       => array('new_question' => true),
62
+				'capability' => 'ee_edit_questions',
63
+				'noheader'   => true,
64
+			),
65
+			'duplicate_question' => array(
66
+				'func'       => '_duplicate_question',
67
+				'capability' => 'ee_edit_questions',
68
+				'noheader'   => true,
69
+			),
70
+			'trash_question'     => array(
71
+				'func'       => '_trash_question',
72
+				'capability' => 'ee_delete_question',
73
+				'obj_id'     => $qst_id,
74
+				'noheader'   => true,
75
+			),
76
+
77
+			'restore_question' => array(
78
+				'func'       => '_trash_or_restore_questions',
79
+				'capability' => 'ee_delete_question',
80
+				'obj_id'     => $qst_id,
81
+				'args'       => array('trash' => false),
82
+				'noheader'   => true,
83
+			),
84
+
85
+			'delete_question' => array(
86
+				'func'       => '_delete_question',
87
+				'capability' => 'ee_delete_question',
88
+				'obj_id'     => $qst_id,
89
+				'noheader'   => true,
90
+			),
91
+
92
+			'trash_questions' => array(
93
+				'func'       => '_trash_or_restore_questions',
94
+				'capability' => 'ee_delete_questions',
95
+				'args'       => array('trash' => true),
96
+				'noheader'   => true,
97
+			),
98
+
99
+			'restore_questions' => array(
100
+				'func'       => '_trash_or_restore_questions',
101
+				'capability' => 'ee_delete_questions',
102
+				'args'       => array('trash' => false),
103
+				'noheader'   => true,
104
+			),
105
+
106
+			'delete_questions' => array(
107
+				'func'       => '_delete_questions',
108
+				'args'       => array(),
109
+				'capability' => 'ee_delete_questions',
110
+				'noheader'   => true,
111
+			),
112
+
113
+			'add_question_group' => array(
114
+				'func'       => '_edit_question_group',
115
+				'capability' => 'ee_edit_question_groups',
116
+			),
117
+
118
+			'edit_question_group' => array(
119
+				'func'       => '_edit_question_group',
120
+				'capability' => 'ee_edit_question_group',
121
+				'obj_id'     => $qsg_id,
122
+				'args'       => array('edit'),
123
+			),
124
+
125
+			'delete_question_groups' => array(
126
+				'func'       => '_delete_question_groups',
127
+				'capability' => 'ee_delete_question_groups',
128
+				'noheader'   => true,
129
+			),
130
+
131
+			'delete_question_group' => array(
132
+				'func'       => '_delete_question_groups',
133
+				'capability' => 'ee_delete_question_group',
134
+				'obj_id'     => $qsg_id,
135
+				'noheader'   => true,
136
+			),
137
+
138
+			'trash_question_group' => array(
139
+				'func'       => '_trash_or_restore_question_groups',
140
+				'args'       => array('trash' => true),
141
+				'capability' => 'ee_delete_question_group',
142
+				'obj_id'     => $qsg_id,
143
+				'noheader'   => true,
144
+			),
145
+
146
+			'restore_question_group' => array(
147
+				'func'       => '_trash_or_restore_question_groups',
148
+				'args'       => array('trash' => false),
149
+				'capability' => 'ee_delete_question_group',
150
+				'obj_id'     => $qsg_id,
151
+				'noheader'   => true,
152
+			),
153
+
154
+			'insert_question_group' => array(
155
+				'func'       => '_insert_or_update_question_group',
156
+				'args'       => array('new_question_group' => true),
157
+				'capability' => 'ee_edit_question_groups',
158
+				'noheader'   => true,
159
+			),
160
+
161
+			'update_question_group' => array(
162
+				'func'       => '_insert_or_update_question_group',
163
+				'args'       => array('new_question_group' => false),
164
+				'capability' => 'ee_edit_question_group',
165
+				'obj_id'     => $qsg_id,
166
+				'noheader'   => true,
167
+			),
168
+
169
+			'trash_question_groups' => array(
170
+				'func'       => '_trash_or_restore_question_groups',
171
+				'args'       => array('trash' => true),
172
+				'capability' => 'ee_delete_question_groups',
173
+				'noheader'   => array('trash' => false),
174
+			),
175
+
176
+			'restore_question_groups' => array(
177
+				'func'       => '_trash_or_restore_question_groups',
178
+				'args'       => array('trash' => false),
179
+				'capability' => 'ee_delete_question_groups',
180
+				'noheader'   => true,
181
+			),
182
+
183
+
184
+			'espresso_update_question_group_order' => array(
185
+				'func'       => 'update_question_group_order',
186
+				'capability' => 'ee_edit_question_groups',
187
+				'noheader'   => true,
188
+			),
189
+
190
+			'view_reg_form_settings' => array(
191
+				'func'       => '_reg_form_settings',
192
+				'capability' => 'manage_options',
193
+			),
194
+
195
+			'update_reg_form_settings' => array(
196
+				'func'       => '_update_reg_form_settings',
197
+				'capability' => 'manage_options',
198
+				'noheader'   => true,
199
+			),
200
+		);
201
+		$this->_page_routes = array_merge($this->_page_routes, $new_page_routes);
202
+
203
+		$new_page_config    = array(
204
+
205
+			'question_groups' => array(
206
+				'nav'           => array(
207
+					'label' => esc_html__('Question Groups', 'event_espresso'),
208
+					'order' => 20,
209
+				),
210
+				'list_table'    => 'Registration_Form_Question_Groups_Admin_List_Table',
211
+				'help_tabs'     => array(
212
+					'registration_form_question_groups_help_tab'                           => array(
213
+						'title'    => esc_html__('Question Groups', 'event_espresso'),
214
+						'filename' => 'registration_form_question_groups',
215
+					),
216
+					'registration_form_question_groups_table_column_headings_help_tab'     => array(
217
+						'title'    => esc_html__('Question Groups Table Column Headings', 'event_espresso'),
218
+						'filename' => 'registration_form_question_groups_table_column_headings',
219
+					),
220
+					'registration_form_question_groups_views_bulk_actions_search_help_tab' => array(
221
+						'title'    => esc_html__('Question Groups Views & Bulk Actions & Search', 'event_espresso'),
222
+						'filename' => 'registration_form_question_groups_views_bulk_actions_search',
223
+					),
224
+				),
225
+				'help_tour'     => array('Registration_Form_Question_Groups_Help_Tour'),
226
+				'metaboxes'     => $this->_default_espresso_metaboxes,
227
+				'require_nonce' => false,
228
+				'qtips'         => array(
229
+					'EE_Registration_Form_Tips',
230
+				),
231
+			),
232
+
233
+			'add_question' => array(
234
+				'nav'           => array(
235
+					'label'      => esc_html__('Add Question', 'event_espresso'),
236
+					'order'      => 5,
237
+					'persistent' => false,
238
+				),
239
+				'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
240
+				'help_tabs'     => array(
241
+					'registration_form_add_question_help_tab' => array(
242
+						'title'    => esc_html__('Add Question', 'event_espresso'),
243
+						'filename' => 'registration_form_add_question',
244
+					),
245
+				),
246
+				'help_tour'     => array('Registration_Form_Add_Question_Help_Tour'),
247
+				'require_nonce' => false,
248
+			),
249
+
250
+			'add_question_group' => array(
251
+				'nav'           => array(
252
+					'label'      => esc_html__('Add Question Group', 'event_espresso'),
253
+					'order'      => 5,
254
+					'persistent' => false,
255
+				),
256
+				'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
257
+				'help_tabs'     => array(
258
+					'registration_form_add_question_group_help_tab' => array(
259
+						'title'    => esc_html__('Add Question Group', 'event_espresso'),
260
+						'filename' => 'registration_form_add_question_group',
261
+					),
262
+				),
263
+				'help_tour'     => array('Registration_Form_Add_Question_Group_Help_Tour'),
264
+				'require_nonce' => false,
265
+			),
266
+
267
+			'edit_question_group' => array(
268
+				'nav'           => array(
269
+					'label'      => esc_html__('Edit Question Group', 'event_espresso'),
270
+					'order'      => 5,
271
+					'persistent' => false,
272
+					'url'        => isset($this->_req_data['question_group_id']) ? add_query_arg(array('question_group_id' => $this->_req_data['question_group_id']),
273
+						$this->_current_page_view_url) : $this->_admin_base_url,
274
+				),
275
+				'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
276
+				'help_tabs'     => array(
277
+					'registration_form_edit_question_group_help_tab' => array(
278
+						'title'    => esc_html__('Edit Question Group', 'event_espresso'),
279
+						'filename' => 'registration_form_edit_question_group',
280
+					),
281
+				),
282
+				'help_tour'     => array('Registration_Form_Edit_Question_Group_Help_Tour'),
283
+				'require_nonce' => false,
284
+			),
285
+
286
+			'view_reg_form_settings' => array(
287
+				'nav'           => array(
288
+					'label' => esc_html__('Reg Form Settings', 'event_espresso'),
289
+					'order' => 40,
290
+				),
291
+				'labels'        => array(
292
+					'publishbox' => esc_html__('Update Settings', 'event_espresso'),
293
+				),
294
+				'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
295
+				'help_tabs'     => array(
296
+					'registration_form_reg_form_settings_help_tab' => array(
297
+						'title'    => esc_html__('Registration Form Settings', 'event_espresso'),
298
+						'filename' => 'registration_form_reg_form_settings',
299
+					),
300
+				),
301
+				'help_tour'     => array('Registration_Form_Settings_Help_Tour'),
302
+				'require_nonce' => false,
303
+			),
304
+
305
+		);
306
+		$this->_page_config = array_merge($this->_page_config, $new_page_config);
307
+
308
+		//change the list table we're going to use so it's the NEW list table!
309
+		$this->_page_config['default']['list_table'] = 'Extend_Registration_Form_Questions_Admin_List_Table';
310
+
311
+
312
+		//additional labels
313
+		$new_labels               = array(
314
+			'add_question'          => esc_html__('Add New Question', 'event_espresso'),
315
+			'delete_question'       => esc_html__('Delete Question', 'event_espresso'),
316
+			'add_question_group'    => esc_html__('Add New Question Group', 'event_espresso'),
317
+			'edit_question_group'   => esc_html__('Edit Question Group', 'event_espresso'),
318
+			'delete_question_group' => esc_html__('Delete Question Group', 'event_espresso'),
319
+		);
320
+		$this->_labels['buttons'] = array_merge($this->_labels['buttons'], $new_labels);
321
+
322
+	}
323
+
324
+
325
+	protected function _ajax_hooks()
326
+	{
327
+		add_action('wp_ajax_espresso_update_question_group_order', array($this, 'update_question_group_order'));
328
+	}
329
+
330
+
331
+	public function load_scripts_styles_question_groups()
332
+	{
333
+		wp_enqueue_script('espresso_ajax_table_sorting');
334
+	}
335
+
336
+
337
+	public function load_scripts_styles_add_question_group()
338
+	{
339
+		$this->load_scripts_styles_forms();
340
+		$this->load_sortable_question_script();
341
+	}
342
+
343
+	public function load_scripts_styles_edit_question_group()
344
+	{
345
+		$this->load_scripts_styles_forms();
346
+		$this->load_sortable_question_script();
347
+	}
348
+
349
+
350
+	/**
351
+	 * registers and enqueues script for questions
352
+	 *
353
+	 * @return void
354
+	 */
355
+	public function load_sortable_question_script()
356
+	{
357
+		wp_register_script('ee-question-sortable', REGISTRATION_FORM_CAF_ASSETS_URL . 'ee_question_order.js',
358
+			array('jquery-ui-sortable'), EVENT_ESPRESSO_VERSION, true);
359
+		wp_enqueue_script('ee-question-sortable');
360
+	}
361
+
362
+
363
+	protected function _set_list_table_views_default()
364
+	{
365
+		$this->_views = array(
366
+			'all' => array(
367
+				'slug'        => 'all',
368
+				'label'       => esc_html__('View All Questions', 'event_espresso'),
369
+				'count'       => 0,
370
+				'bulk_action' => array(
371
+					'trash_questions' => esc_html__('Trash', 'event_espresso'),
372
+				),
373
+			),
374
+		);
375
+
376
+		if (EE_Registry::instance()->CAP->current_user_can('ee_delete_questions',
377
+			'espresso_registration_form_trash_questions')
378
+		) {
379
+			$this->_views['trash'] = array(
380
+				'slug'        => 'trash',
381
+				'label'       => esc_html__('Trash', 'event_espresso'),
382
+				'count'       => 0,
383
+				'bulk_action' => array(
384
+					'delete_questions'  => esc_html__('Delete Permanently', 'event_espresso'),
385
+					'restore_questions' => esc_html__('Restore', 'event_espresso'),
386
+				),
387
+			);
388
+		}
389
+	}
390
+
391
+
392
+	protected function _set_list_table_views_question_groups()
393
+	{
394
+		$this->_views = array(
395
+			'all' => array(
396
+				'slug'        => 'all',
397
+				'label'       => esc_html__('All', 'event_espresso'),
398
+				'count'       => 0,
399
+				'bulk_action' => array(
400
+					'trash_question_groups' => esc_html__('Trash', 'event_espresso'),
401
+				),
402
+			),
403
+		);
404
+
405
+		if (EE_Registry::instance()->CAP->current_user_can('ee_delete_question_groups',
406
+			'espresso_registration_form_trash_question_groups')
407
+		) {
408
+			$this->_views['trash'] = array(
409
+				'slug'        => 'trash',
410
+				'label'       => esc_html__('Trash', 'event_espresso'),
411
+				'count'       => 0,
412
+				'bulk_action' => array(
413
+					'delete_question_groups'  => esc_html__('Delete Permanently', 'event_espresso'),
414
+					'restore_question_groups' => esc_html__('Restore', 'event_espresso'),
415
+				),
416
+			);
417
+		}
418
+	}
419
+
420
+
421
+	protected function _questions_overview_list_table()
422
+	{
423
+		$this->_admin_page_title .= ' ' . $this->get_action_link_or_button(
424
+				'add_question',
425
+				'add_question',
426
+				array(),
427
+				'add-new-h2'
428
+			);
429
+		parent::_questions_overview_list_table();
430
+	}
431
+
432
+
433
+	protected function _question_groups_overview_list_table()
434
+	{
435
+		$this->_search_btn_label = esc_html__('Question Groups', 'event_espresso');
436
+		$this->_admin_page_title .= ' ' . $this->get_action_link_or_button(
437
+				'add_question_group',
438
+				'add_question_group',
439
+				array(),
440
+				'add-new-h2'
441
+			);
442
+		$this->display_admin_list_table_page_with_sidebar();
443
+	}
444
+
445
+
446
+	protected function _delete_question()
447
+	{
448
+		$success = $this->_delete_items($this->_question_model);
449
+		$this->_redirect_after_action(
450
+			$success,
451
+			$this->_question_model->item_name($success),
452
+			'deleted',
453
+			array('action' => 'default', 'status' => 'all')
454
+		);
455
+	}
456
+
457
+
458
+	protected function _delete_questions()
459
+	{
460
+		$success = $this->_delete_items($this->_question_model);
461
+		$this->_redirect_after_action(
462
+			$success,
463
+			$this->_question_model->item_name($success),
464
+			'deleted permanently',
465
+			array('action' => 'default', 'status' => 'trash')
466
+		);
467
+	}
468
+
469
+
470
+	/**
471
+	 * Performs the deletion of a single or multiple questions or question groups.
472
+	 *
473
+	 * @param EEM_Soft_Delete_Base $model
474
+	 * @return int number of items deleted permanently
475
+	 */
476
+	private function _delete_items(EEM_Soft_Delete_Base $model)
477
+	{
478
+		$success = 0;
479
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
480
+		if (! empty($this->_req_data['checkbox']) && is_array($this->_req_data['checkbox'])) {
481
+			// if array has more than one element than success message should be plural
482
+			$success = count($this->_req_data['checkbox']) > 1 ? 2 : 1;
483
+			// cycle thru bulk action checkboxes
484
+			while (list($ID, $value) = each($this->_req_data['checkbox'])) {
485
+				if (! $this->_delete_item($ID, $model)) {
486
+					$success = 0;
487
+				}
488
+			}
489
+
490
+		} elseif (! empty($this->_req_data['QSG_ID'])) {
491
+			$success = $this->_delete_item($this->_req_data['QSG_ID'], $model);
492
+
493
+		} elseif (! empty($this->_req_data['QST_ID'])) {
494
+			$success = $this->_delete_item($this->_req_data['QST_ID'], $model);
495
+		} else {
496
+			EE_Error::add_error(sprintf(esc_html__("No Questions or Question Groups were selected for deleting. This error usually shows when you've attempted to delete via bulk action but there were no selections.",
497
+				"event_espresso")), __FILE__, __FUNCTION__, __LINE__);
498
+		}
499
+		return $success;
500
+	}
501
+
502
+	/**
503
+	 * Deletes the specified question (and its associated question options) or question group
504
+	 *
505
+	 * @param int                  $id
506
+	 * @param EEM_Soft_Delete_Base $model
507
+	 * @return boolean
508
+	 */
509
+	protected function _delete_item($id, $model)
510
+	{
511
+		if ($model instanceof EEM_Question) {
512
+			EEM_Question_Option::instance()->delete_permanently(array(array('QST_ID' => absint($id))));
513
+		}
514
+		return $model->delete_permanently_by_ID(absint($id));
515
+	}
516
+
517
+
518
+	/******************************    QUESTION GROUPS    ******************************/
519
+
520
+
521
+	protected function _edit_question_group($type = 'add')
522
+	{
523
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
524
+		$ID = isset($this->_req_data['QSG_ID']) && ! empty($this->_req_data['QSG_ID']) ? absint($this->_req_data['QSG_ID']) : false;
525
+
526
+		switch ($this->_req_action) {
527
+			case 'add_question_group' :
528
+				$this->_admin_page_title = esc_html__('Add Question Group', 'event_espresso');
529
+				break;
530
+			case 'edit_question_group' :
531
+				$this->_admin_page_title = esc_html__('Edit Question Group', 'event_espresso');
532
+				break;
533
+			default :
534
+				$this->_admin_page_title = ucwords(str_replace('_', ' ', $this->_req_action));
535
+		}
536
+		// add ID to title if editing
537
+		$this->_admin_page_title = $ID ? $this->_admin_page_title . ' # ' . $ID : $this->_admin_page_title;
538
+		if ($ID) {
539
+			/** @var EE_Question_Group $questionGroup */
540
+			$questionGroup            = $this->_question_group_model->get_one_by_ID($ID);
541
+			$additional_hidden_fields = array('QSG_ID' => array('type' => 'hidden', 'value' => $ID));
542
+			$this->_set_add_edit_form_tags('update_question_group', $additional_hidden_fields);
543
+		} else {
544
+			/** @var EE_Question_Group $questionGroup */
545
+			$questionGroup = EEM_Question_Group::instance()->create_default_object();
546
+			$questionGroup->set_order_to_latest();
547
+			$this->_set_add_edit_form_tags('insert_question_group');
548
+		}
549
+		$this->_template_args['values']         = $this->_yes_no_values;
550
+		$this->_template_args['all_questions']  = $questionGroup->questions_in_and_not_in_group();
551
+		$this->_template_args['QSG_ID']         = $ID ? $ID : true;
552
+		$this->_template_args['question_group'] = $questionGroup;
553
+
554
+		$redirect_URL = add_query_arg(array('action' => 'question_groups'), $this->_admin_base_url);
555
+		$this->_set_publish_post_box_vars('id', $ID, false, $redirect_URL);
556
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(REGISTRATION_FORM_CAF_TEMPLATE_PATH . 'question_groups_main_meta_box.template.php',
557
+			$this->_template_args, true);
558
+
559
+		// the details template wrapper
560
+		$this->display_admin_page_with_sidebar();
561
+	}
562
+
563
+
564
+	protected function _delete_question_groups()
565
+	{
566
+		$success = $this->_delete_items($this->_question_group_model);
567
+		$this->_redirect_after_action($success, $this->_question_group_model->item_name($success),
568
+			'deleted permanently', array('action' => 'question_groups', 'status' => 'trash'));
569
+	}
570
+
571
+
572
+	/**
573
+	 * @param bool $new_question_group
574
+	 */
575
+	protected function _insert_or_update_question_group($new_question_group = true)
576
+	{
577
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
578
+		$set_column_values = $this->_set_column_values_for($this->_question_group_model);
579
+		if ($new_question_group) {
580
+			$QSG_ID  = $this->_question_group_model->insert($set_column_values);
581
+			$success = $QSG_ID ? 1 : 0;
582
+		} else {
583
+			$QSG_ID = absint($this->_req_data['QSG_ID']);
584
+			unset($set_column_values['QSG_ID']);
585
+			$success = $this->_question_group_model->update($set_column_values, array(array('QSG_ID' => $QSG_ID)));
586
+		}
587
+		$phone_question_id = EEM_Question::instance()->get_Question_ID_from_system_string(EEM_Attendee::system_question_phone);
588
+		// update the existing related questions
589
+		// BUT FIRST...  delete the phone question from the Question_Group_Question if it is being added to this question group (therefore removed from the existing group)
590
+		if (isset($this->_req_data['questions'], $this->_req_data['questions'][$phone_question_id])) {
591
+			// delete where QST ID = system phone question ID and Question Group ID is NOT this group
592
+			EEM_Question_Group_Question::instance()->delete(array(
593
+				array(
594
+					'QST_ID' => $phone_question_id,
595
+					'QSG_ID' => array('!=', $QSG_ID),
596
+				),
597
+			));
598
+		}
599
+		/** @type EE_Question_Group $question_group */
600
+		$question_group = $this->_question_group_model->get_one_by_ID($QSG_ID);
601
+		$questions      = $question_group->questions();
602
+		// make sure system phone question is added to list of questions for this group
603
+		if (! isset($questions[$phone_question_id])) {
604
+			$questions[$phone_question_id] = EEM_Question::instance()->get_one_by_ID($phone_question_id);
605
+		}
606
+
607
+		foreach ($questions as $question_ID => $question) {
608
+			// first we always check for order.
609
+			if (! empty($this->_req_data['question_orders'][$question_ID])) {
610
+				//update question order
611
+				$question_group->update_question_order($question_ID, $this->_req_data['question_orders'][$question_ID]);
612
+			}
613
+
614
+			// then we always check if adding or removing.
615
+			if (isset($this->_req_data['questions'], $this->_req_data['questions'][$question_ID])) {
616
+				$question_group->add_question($question_ID);
617
+			} else {
618
+				// not found, remove it (but only if not a system question for the personal group with the exception of lname system question - we allow removal of it)
619
+				if (
620
+				in_array(
621
+					$question->system_ID(),
622
+					EEM_Question::instance()->required_system_questions_in_system_question_group($question_group->system_group())
623
+				)
624
+				) {
625
+					continue;
626
+				} else {
627
+					$question_group->remove_question($question_ID);
628
+				}
629
+			}
630
+		}
631
+		// save new related questions
632
+		if (isset($this->_req_data['questions'])) {
633
+			foreach ($this->_req_data['questions'] as $QST_ID) {
634
+				$question_group->add_question($QST_ID);
635
+				if (isset($this->_req_data['question_orders'][$QST_ID])) {
636
+					$question_group->update_question_order($QST_ID, $this->_req_data['question_orders'][$QST_ID]);
637
+				}
638
+			}
639
+		}
640
+
641
+		if ($success !== false) {
642
+			$msg = $new_question_group ? sprintf(esc_html__('The %s has been created', 'event_espresso'),
643
+				$this->_question_group_model->item_name()) : sprintf(esc_html__('The %s has been updated',
644
+				'event_espresso'), $this->_question_group_model->item_name());
645
+			EE_Error::add_success($msg);
646
+		}
647
+		$this->_redirect_after_action(false, '', '', array('action' => 'edit_question_group', 'QSG_ID' => $QSG_ID),
648
+			true);
649
+
650
+	}
651
+
652
+	/**
653
+	 * duplicates a question and all its question options and redirects to the new question.
654
+	 */
655
+	public function _duplicate_question()
656
+	{
657
+		$question_ID = (int)$this->_req_data['QST_ID'];
658
+		$question    = EEM_Question::instance()->get_one_by_ID($question_ID);
659
+		if ($question instanceof EE_Question) {
660
+			$new_question = $question->duplicate();
661
+			if ($new_question instanceof EE_Question) {
662
+				$this->_redirect_after_action(true, esc_html__('Question', 'event_espresso'),
663
+					esc_html__('Duplicated', 'event_espresso'),
664
+					array('action' => 'edit_question', 'QST_ID' => $new_question->ID()), true);
665
+			} else {
666
+				global $wpdb;
667
+				EE_Error::add_error(sprintf(esc_html__('Could not duplicate question with ID %1$d because: %2$s',
668
+					'event_espresso'), $question_ID, $wpdb->last_error), __FILE__, __FUNCTION__, __LINE__);
669
+				$this->_redirect_after_action(false, '', '', array('action' => 'default'), false);
670
+			}
671
+		} else {
672
+			EE_Error::add_error(sprintf(esc_html__('Could not duplicate question with ID %d because it didn\'t exist!',
673
+				'event_espresso'), $question_ID), __FILE__, __FUNCTION__, __LINE__);
674
+			$this->_redirect_after_action(false, '', '', array('action' => 'default'), false);
675
+		}
676
+	}
677
+
678
+
679
+	/**
680
+	 * @param bool $trash
681
+	 */
682
+	protected function _trash_or_restore_question_groups($trash = true)
683
+	{
684
+		$this->_trash_or_restore_items($this->_question_group_model, $trash);
685
+	}
686
+
687
+
688
+	/**
689
+	 *_trash_question
690
+	 */
691
+	protected function _trash_question()
692
+	{
693
+		$success    = $this->_question_model->delete_by_ID((int)$this->_req_data['QST_ID']);
694
+		$query_args = array('action' => 'default', 'status' => 'all');
695
+		$this->_redirect_after_action($success, $this->_question_model->item_name($success), 'trashed', $query_args);
696
+	}
697
+
698
+
699
+	/**
700
+	 * @param bool $trash
701
+	 */
702
+	protected function _trash_or_restore_questions($trash = true)
703
+	{
704
+		$this->_trash_or_restore_items($this->_question_model, $trash);
705
+	}
706
+
707
+
708
+	/**
709
+	 * Internally used to delete or restore items, using the request data. Meant to be
710
+	 * flexible between question or question groups
711
+	 *
712
+	 * @param EEM_Soft_Delete_Base $model
713
+	 * @param boolean              $trash whether to trash or restore
714
+	 */
715
+	private function _trash_or_restore_items(EEM_Soft_Delete_Base $model, $trash = true)
716
+	{
717
+
718
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
719
+
720
+		$success = 1;
721
+		//Checkboxes
722
+		//echo "trash $trash";
723
+		//var_dump($this->_req_data['checkbox']);die;
724
+		if (isset($this->_req_data['checkbox'])) {
725
+			if (! empty($this->_req_data['checkbox']) && is_array($this->_req_data['checkbox'])) {
726
+				// if array has more than one element than success message should be plural
727
+				$success = count($this->_req_data['checkbox']) > 1 ? 2 : 1;
728
+				// cycle thru bulk action checkboxes
729
+				while (list($ID, $value) = each($this->_req_data['checkbox'])) {
730
+					if (! $model->delete_or_restore_by_ID($trash, absint($ID))) {
731
+						$success = 0;
732
+					}
733
+				}
734
+
735
+			} else {
736
+				// grab single id and delete
737
+				$ID = absint($this->_req_data['checkbox']);
738
+				if (! $model->delete_or_restore_by_ID($trash, $ID)) {
739
+					$success = 0;
740
+				}
741
+			}
742
+
743
+		} else {
744
+			// delete via trash link
745
+			// grab single id and delete
746
+			$ID = absint($this->_req_data[$model->primary_key_name()]);
747
+			if (! $model->delete_or_restore_by_ID($trash, $ID)) {
748
+				$success = 0;
749
+			}
750
+
751
+		}
752
+
753
+
754
+		$action = $model instanceof EEM_Question ? 'default' : 'question_groups';//strtolower( $model->item_name(2) );
755
+		//echo "action :$action";
756
+		//$action = 'questions' ? 'default' : $action;
757
+		if ($trash) {
758
+			$action_desc = 'trashed';
759
+			$status      = 'trash';
760
+		} else {
761
+			$action_desc = 'restored';
762
+			$status      = 'all';
763
+		}
764
+		$this->_redirect_after_action($success, $model->item_name($success), $action_desc,
765
+			array('action' => $action, 'status' => $status));
766
+	}
767
+
768
+
769
+	/**
770
+	 * @param            $per_page
771
+	 * @param int        $current_page
772
+	 * @param bool|false $count
773
+	 * @return \EE_Soft_Delete_Base_Class[]|int
774
+	 */
775
+	public function get_trashed_questions($per_page, $current_page = 1, $count = false)
776
+	{
777
+		$query_params = $this->get_query_params(EEM_Question::instance(), $per_page, $current_page);
778
+
779
+		if ($count) {
780
+			//note: this a subclass of EEM_Soft_Delete_Base, so this is actually only getting non-trashed items
781
+			$where   = isset($query_params[0]) ? array($query_params[0]) : array();
782
+			$results = $this->_question_model->count_deleted($where);
783
+		} else {
784
+			//note: this a subclass of EEM_Soft_Delete_Base, so this is actually only getting non-trashed items
785
+			$results = $this->_question_model->get_all_deleted($query_params);
786
+		}
787
+		return $results;
788
+	}
789
+
790
+
791
+	/**
792
+	 * @param            $per_page
793
+	 * @param int        $current_page
794
+	 * @param bool|false $count
795
+	 * @return \EE_Soft_Delete_Base_Class[]
796
+	 */
797
+	public function get_question_groups($per_page, $current_page = 1, $count = false)
798
+	{
799
+		$questionGroupModel = EEM_Question_Group::instance();
800
+		$query_params       = $this->get_query_params($questionGroupModel, $per_page, $current_page);
801
+		if ($count) {
802
+			$where   = isset($query_params[0]) ? array($query_params[0]) : array();
803
+			$results = $questionGroupModel->count($where);
804
+		} else {
805
+			$results = $questionGroupModel->get_all($query_params);
806
+		}
807
+		return $results;
808
+	}
809
+
810
+
811
+	/**
812
+	 * @param      $per_page
813
+	 * @param int  $current_page
814
+	 * @param bool $count
815
+	 * @return \EE_Soft_Delete_Base_Class[]|int
816
+	 */
817
+	public function get_trashed_question_groups($per_page, $current_page = 1, $count = false)
818
+	{
819
+		$questionGroupModel = EEM_Question_Group::instance();
820
+		$query_params       = $this->get_query_params($questionGroupModel, $per_page, $current_page);
821
+		if ($count) {
822
+			$where                 = isset($query_params[0]) ? array($query_params[0]) : array();
823
+			$query_params['limit'] = null;
824
+			$results               = $questionGroupModel->count_deleted($where);
825
+		} else {
826
+			$results = $questionGroupModel->get_all_deleted($query_params);
827
+		}
828
+		return $results;
829
+	}
830
+
831
+
832
+	/**
833
+	 * method for performing updates to question order
834
+	 *
835
+	 * @return array results array
836
+	 */
837
+	public function update_question_group_order()
838
+	{
839
+
840
+		$success = esc_html__('Question group order was updated successfully.', 'event_espresso');
841
+
842
+		// grab our row IDs
843
+		$row_ids = isset($this->_req_data['row_ids']) && ! empty($this->_req_data['row_ids'])
844
+			? explode(',', rtrim($this->_req_data['row_ids'], ','))
845
+			: array();
846
+
847
+		$perpage = ! empty($this->_req_data['perpage'])
848
+			? (int)$this->_req_data['perpage']
849
+			: null;
850
+		$curpage = ! empty($this->_req_data['curpage'])
851
+			? (int)$this->_req_data['curpage']
852
+			: null;
853
+
854
+		if (! empty($row_ids)) {
855
+			//figure out where we start the row_id count at for the current page.
856
+			$qsgcount = empty($curpage) ? 0 : ($curpage - 1) * $perpage;
857
+
858
+			$row_count = count($row_ids);
859
+			for ($i = 0; $i < $row_count; $i++) {
860
+				//Update the questions when re-ordering
861
+				$updated = EEM_Question_Group::instance()->update(
862
+					array('QSG_order' => $qsgcount),
863
+					array(array('QSG_ID' => $row_ids[$i]))
864
+				);
865
+				if ($updated === false) {
866
+					$success = false;
867
+				}
868
+				$qsgcount++;
869
+			}
870
+		} else {
871
+			$success = false;
872
+		}
873
+
874
+		$errors = ! $success
875
+			? esc_html__('An error occurred. The question group order was not updated.', 'event_espresso')
876
+			: false;
877
+
878
+		echo wp_json_encode(array('return_data' => false, 'success' => $success, 'errors' => $errors));
879
+		die();
880
+
881
+	}
882
+
883
+
884
+
885
+	/***************************************        REGISTRATION SETTINGS        ***************************************/
886
+
887
+
888
+	/**
889
+	 * _reg_form_settings
890
+	 *
891
+	 * @throws \EE_Error
892
+	 */
893
+	protected function _reg_form_settings()
894
+	{
895
+		$this->_template_args['values'] = $this->_yes_no_values;
896
+		add_action(
897
+			'AHEE__Extend_Registration_Form_Admin_Page___reg_form_settings_template',
898
+			array($this, 'email_validation_settings_form'),
899
+			2
900
+		);
901
+		$this->_template_args = (array)apply_filters(
902
+			'FHEE__Extend_Registration_Form_Admin_Page___reg_form_settings___template_args',
903
+			$this->_template_args
904
+		);
905
+		$this->_set_add_edit_form_tags('update_reg_form_settings');
906
+		$this->_set_publish_post_box_vars(null, false, false, null, false);
907
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
908
+			REGISTRATION_FORM_CAF_TEMPLATE_PATH . 'reg_form_settings.template.php',
909
+			$this->_template_args,
910
+			true
911
+		);
912
+		$this->display_admin_page_with_sidebar();
913
+	}
914
+
915
+
916
+	/**
917
+	 * _update_reg_form_settings
918
+	 */
919
+	protected function _update_reg_form_settings()
920
+	{
921
+		EE_Registry::instance()->CFG->registration = $this->update_email_validation_settings_form(
922
+			EE_Registry::instance()->CFG->registration
923
+		);
924
+		EE_Registry::instance()->CFG->registration = apply_filters(
925
+			'FHEE__Extend_Registration_Form_Admin_Page___update_reg_form_settings__CFG_registration',
926
+			EE_Registry::instance()->CFG->registration
927
+		);
928
+		$success                                   = $this->_update_espresso_configuration(
929
+			esc_html__('Registration Form Options', 'event_espresso'),
930
+			EE_Registry::instance()->CFG,
931
+			__FILE__, __FUNCTION__, __LINE__
932
+		);
933
+		$this->_redirect_after_action($success, esc_html__('Registration Form Options', 'event_espresso'), 'updated',
934
+			array('action' => 'view_reg_form_settings'));
935
+	}
936
+
937
+
938
+	/**
939
+	 * email_validation_settings_form
940
+	 *
941
+	 * @access    public
942
+	 * @return    void
943
+	 * @throws \EE_Error
944
+	 */
945
+	public function email_validation_settings_form()
946
+	{
947
+		echo $this->_email_validation_settings_form()->get_html();
948
+	}
949
+
950
+
951
+	/**
952
+	 * _email_validation_settings_form
953
+	 *
954
+	 * @access protected
955
+	 * @return EE_Form_Section_Proper
956
+	 * @throws \EE_Error
957
+	 */
958
+	protected function _email_validation_settings_form()
959
+	{
960
+		return new EE_Form_Section_Proper(
961
+			array(
962
+				'name'            => 'email_validation_settings',
963
+				'html_id'         => 'email_validation_settings',
964
+				'layout_strategy' => new EE_Admin_Two_Column_Layout(),
965
+				'subsections'     => apply_filters(
966
+					'FHEE__Extend_Registration_Form_Admin_Page___email_validation_settings_form__form_subsections',
967
+					array(
968
+						'email_validation_hdr'   => new EE_Form_Section_HTML(
969
+							EEH_HTML::h2(esc_html__('Email Validation Settings', 'event_espresso'))
970
+						),
971
+						'email_validation_level' => new EE_Select_Input(
972
+							array(
973
+								'basic'      => esc_html__('Basic', 'event_espresso'),
974
+								'wp_default' => esc_html__('WordPress Default', 'event_espresso'),
975
+								'i18n'       => esc_html__('International', 'event_espresso'),
976
+								'i18n_dns'   => esc_html__('International + DNS Check', 'event_espresso'),
977
+							),
978
+							array(
979
+								'html_label_text' => esc_html__('Email Validation Level', 'event_espresso')
980
+													 . EEH_Template::get_help_tab_link('email_validation_info'),
981
+								'html_help_text'  => esc_html__('These levels range from basic validation ( ie: [email protected] ) to more advanced checks against international email addresses (ie: üñîçøðé@example.com ) with additional MX and A record checks to confirm the domain actually exists. More information on on each level can be found within the help section.',
982
+									'event_espresso'),
983
+								'default'         => isset(EE_Registry::instance()->CFG->registration->email_validation_level)
984
+									? EE_Registry::instance()->CFG->registration->email_validation_level
985
+									: 'wp_default',
986
+								'required'        => false,
987
+							)
988
+						),
989
+					)
990
+				),
991
+			)
992
+		);
993
+	}
994
+
995
+
996
+	/**
997
+	 * update_email_validation_settings_form
998
+	 *
999
+	 * @access    public
1000
+	 * @param \EE_Registration_Config $EE_Registration_Config
1001
+	 * @return \EE_Registration_Config
1002
+	 */
1003
+	public function update_email_validation_settings_form(EE_Registration_Config $EE_Registration_Config)
1004
+	{
1005
+		$prev_email_validation_level = $EE_Registration_Config->email_validation_level;
1006
+		try {
1007
+			$email_validation_settings_form = $this->_email_validation_settings_form();
1008
+			// if not displaying a form, then check for form submission
1009
+			if ($email_validation_settings_form->was_submitted()) {
1010
+				// capture form data
1011
+				$email_validation_settings_form->receive_form_submission();
1012
+				// validate form data
1013
+				if ($email_validation_settings_form->is_valid()) {
1014
+					// grab validated data from form
1015
+					$valid_data = $email_validation_settings_form->valid_data();
1016
+					if (isset($valid_data['email_validation_level'])) {
1017
+						$email_validation_level = $valid_data['email_validation_level'];
1018
+						// now if they want to use international email addresses
1019
+						if ($email_validation_level === 'i18n' || $email_validation_level === 'i18n_dns') {
1020
+							// in case we need to reset their email validation level,
1021
+							// make sure that the previous value wasn't already set to one of the i18n options.
1022
+							if ($prev_email_validation_level === 'i18n' || $prev_email_validation_level === 'i18n_dns') {
1023
+								// if so, then reset it back to "basic" since that is the only other option that,
1024
+								// despite offering poor validation, supports i18n email addresses
1025
+								$prev_email_validation_level = 'basic';
1026
+							}
1027
+							// confirm our i18n email validation will work on the server
1028
+							if (! $this->_verify_pcre_support($EE_Registration_Config, $email_validation_level)) {
1029
+								// or reset email validation level to previous value
1030
+								$email_validation_level = $prev_email_validation_level;
1031
+							}
1032
+						}
1033
+						$EE_Registration_Config->email_validation_level = $email_validation_level;
1034
+					} else {
1035
+						EE_Error::add_error(
1036
+							esc_html__(
1037
+								'Invalid or missing Email Validation settings. Please refresh the form and try again.',
1038
+								'event_espresso'
1039
+							),
1040
+							__FILE__, __FUNCTION__, __LINE__
1041
+						);
1042
+					}
1043
+				} else {
1044
+					if ($email_validation_settings_form->submission_error_message() !== '') {
1045
+						EE_Error::add_error(
1046
+							$email_validation_settings_form->submission_error_message(),
1047
+							__FILE__, __FUNCTION__, __LINE__
1048
+						);
1049
+					}
1050
+				}
1051
+			}
1052
+		} catch (EE_Error $e) {
1053
+			$e->get_error();
1054
+		}
1055
+		return $EE_Registration_Config;
1056
+	}
1057
+
1058
+
1059
+	/**
1060
+	 * confirms that the server's PHP version has the PCRE module enabled,
1061
+	 * and that the PCRE version works with our i18n email validation
1062
+	 *
1063
+	 * @param \EE_Registration_Config $EE_Registration_Config
1064
+	 * @param string                  $email_validation_level
1065
+	 * @return bool
1066
+	 */
1067
+	private function _verify_pcre_support(EE_Registration_Config $EE_Registration_Config, $email_validation_level)
1068
+	{
1069
+		// first check that PCRE is enabled
1070
+		if (! defined('PREG_BAD_UTF8_ERROR')) {
1071
+			EE_Error::add_error(
1072
+				sprintf(
1073
+					esc_html__(
1074
+						'We\'re sorry, but it appears that your server\'s version of PHP was not compiled with PCRE unicode support.%1$sPlease contact your hosting company and ask them whether the PCRE compiled with your version of PHP on your server can be been built with the "--enable-unicode-properties" and "--enable-utf8" configuration switches to enable more complex regex expressions.%1$sIf they are unable, or unwilling to do so, then your server will not support international email addresses using UTF-8 unicode characters. This means you will either have to lower your email validation level to "Basic" or "WordPress Default", or switch to a hosting company that has/can enable PCRE unicode support on the server.',
1075
+						'event_espresso'
1076
+					),
1077
+					'<br />'
1078
+				),
1079
+				__FILE__,
1080
+				__FUNCTION__,
1081
+				__LINE__
1082
+			);
1083
+			return false;
1084
+		} else {
1085
+			// PCRE support is enabled, but let's still
1086
+			// perform a test to see if the server will support it.
1087
+			// but first, save the updated validation level to the config,
1088
+			// so that the validation strategy picks it up.
1089
+			// this will get bumped back down if it doesn't work
1090
+			$EE_Registration_Config->email_validation_level = $email_validation_level;
1091
+			try {
1092
+				$email_validator    = new EE_Email_Validation_Strategy();
1093
+				$i18n_email_address = apply_filters(
1094
+					'FHEE__Extend_Registration_Form_Admin_Page__update_email_validation_settings_form__i18n_email_address',
1095
+					'jägerjü[email protected]'
1096
+				);
1097
+				$email_validator->validate($i18n_email_address);
1098
+			} catch (Exception $e) {
1099
+				EE_Error::add_error(
1100
+					sprintf(
1101
+						esc_html__(
1102
+							'We\'re sorry, but it appears that your server\'s configuration will not support the "International" or "International + DNS Check" email validation levels.%1$sTo correct this issue, please consult with your hosting company regarding your server\'s PCRE settings.%1$sIt is recommended that your PHP version be configured to use PCRE 8.10 or newer.%1$sMore information regarding PCRE versions and installation can be found here: %2$s',
1103
+							'event_espresso'
1104
+						),
1105
+						'<br />',
1106
+						'<a href="http://php.net/manual/en/pcre.installation.php" target="_blank">http://php.net/manual/en/pcre.installation.php</a>'
1107
+					),
1108
+					__FILE__, __FUNCTION__, __LINE__
1109
+				);
1110
+				return false;
1111
+			}
1112
+		}
1113
+		return true;
1114
+	}
1115 1115
 
1116 1116
 
1117 1117
 }
Please login to merge, or discard this patch.
core/libraries/rest_api/Model_Data_Translator.php 2 patches
Indentation   +521 added lines, -521 removed lines patch added patch discarded remove patch
@@ -2,7 +2,7 @@  discard block
 block discarded – undo
2 2
 namespace EventEspresso\core\libraries\rest_api;
3 3
 
4 4
 if (! defined('EVENT_ESPRESSO_VERSION')) {
5
-    exit('No direct script access allowed');
5
+	exit('No direct script access allowed');
6 6
 }
7 7
 
8 8
 
@@ -26,525 +26,525 @@  discard block
 block discarded – undo
26 26
 class Model_Data_Translator
27 27
 {
28 28
 
29
-    /**
30
-     * We used to use -1 for infinity in the rest api, but that's ambiguous for
31
-     * fields that COULD contain -1; so we use null
32
-     */
33
-    const ee_inf_in_rest = null;
34
-
35
-
36
-
37
-    /**
38
-     * Prepares a possible array of input values from JSON for use by the models
39
-     *
40
-     * @param \EE_Model_Field_Base $field_obj
41
-     * @param mixed                $original_value_maybe_array
42
-     * @param string               $requested_version
43
-     * @param string               $timezone_string treat values as being in this timezone
44
-     * @return mixed
45
-     * @throws \DomainException
46
-     */
47
-    public static function prepare_field_values_from_json(
48
-        $field_obj,
49
-        $original_value_maybe_array,
50
-        $requested_version,
51
-        $timezone_string = 'UTC'
52
-    ) {
53
-        if (is_array($original_value_maybe_array)) {
54
-            $new_value_maybe_array = array();
55
-            foreach ($original_value_maybe_array as $array_key => $array_item) {
56
-                $new_value_maybe_array[$array_key] = Model_Data_Translator::prepare_field_value_from_json(
57
-                    $field_obj,
58
-                    $array_item,
59
-                    $requested_version,
60
-                    $timezone_string
61
-                );
62
-            }
63
-        } else {
64
-            $new_value_maybe_array = Model_Data_Translator::prepare_field_value_from_json(
65
-                $field_obj,
66
-                $original_value_maybe_array,
67
-                $requested_version,
68
-                $timezone_string
69
-            );
70
-        }
71
-        return $new_value_maybe_array;
72
-    }
73
-
74
-
75
-
76
-    /**
77
-     * Prepares an array of field values FOR use in JSON/REST API
78
-     *
79
-     * @param \EE_Model_Field_Base $field_obj
80
-     * @param mixed                $original_value_maybe_array
81
-     * @param string               $request_version (eg 4.8.36)
82
-     * @return array
83
-     */
84
-    public static function prepare_field_values_for_json($field_obj, $original_value_maybe_array, $request_version)
85
-    {
86
-        if (is_array($original_value_maybe_array)) {
87
-            $new_value_maybe_array = array();
88
-            foreach ($original_value_maybe_array as $array_key => $array_item) {
89
-                $new_value_maybe_array[$array_key] = Model_Data_Translator::prepare_field_value_for_json(
90
-                    $field_obj,
91
-                    $array_item,
92
-                    $request_version
93
-                );
94
-            }
95
-        } else {
96
-            $new_value_maybe_array = Model_Data_Translator::prepare_field_value_for_json(
97
-                $field_obj,
98
-                $original_value_maybe_array,
99
-                $request_version
100
-            );
101
-        }
102
-        return $new_value_maybe_array;
103
-    }
104
-
105
-
106
-
107
-    /**
108
-     * Prepares incoming data from the json or $_REQUEST parameters for the models'
109
-     * "$query_params".
110
-     *
111
-     * @param \EE_Model_Field_Base $field_obj
112
-     * @param mixed                $original_value
113
-     * @param string               $requested_version
114
-     * @param string               $timezone_string treat values as being in this timezone
115
-     * @return mixed
116
-     * @throws \DomainException
117
-     */
118
-    public static function prepare_field_value_from_json(
119
-        $field_obj,
120
-        $original_value,
121
-        $requested_version,
122
-        $timezone_string = 'UTC' // UTC
123
-    )
124
-    {
125
-        $timezone_string = $timezone_string !== '' ? $timezone_string : get_option('timezone_string', '');
126
-        $new_value = null;
127
-        if ($field_obj instanceof \EE_Infinite_Integer_Field
128
-            && in_array($original_value, array(null, ''), true)
129
-        ) {
130
-            $new_value = EE_INF;
131
-        } elseif ($field_obj instanceof \EE_Datetime_Field) {
132
-            list($offset_sign, $offset_secs) = Model_Data_Translator::parse_timezone_offset(
133
-                $field_obj->get_timezone_offset(
134
-                    new \DateTimeZone($timezone_string)
135
-                )
136
-            );
137
-            $offset_string =
138
-                str_pad(
139
-                    floor($offset_secs / HOUR_IN_SECONDS),
140
-                    2,
141
-                    '0',
142
-                    STR_PAD_LEFT
143
-                )
144
-                . ':'
145
-                . str_pad(
146
-                    ($offset_secs % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS,
147
-                    2,
148
-                    '0',
149
-                    STR_PAD_LEFT
150
-                );
151
-            $new_value = rest_parse_date($original_value . $offset_sign . $offset_string);
152
-        } else {
153
-            $new_value = $original_value;
154
-        }
155
-        return $new_value;
156
-    }
157
-
158
-
159
-
160
-    /**
161
-     * determines what's going on with them timezone strings
162
-     *
163
-     * @param int $timezone_offset
164
-     * @return array
165
-     */
166
-    private static function parse_timezone_offset($timezone_offset)
167
-    {
168
-        $first_char = substr((string)$timezone_offset, 0, 1);
169
-        if ($first_char === '+' || $first_char === '-') {
170
-            $offset_sign = $first_char;
171
-            $offset_secs = substr((string)$timezone_offset, 1);
172
-        } else {
173
-            $offset_sign = '+';
174
-            $offset_secs = $timezone_offset;
175
-        }
176
-        return array($offset_sign, $offset_secs);
177
-    }
178
-
179
-
180
-
181
-    /**
182
-     * Prepares a field's value for display in the API.
183
-     * The $original_value should be in the model object's domain of values, see the explanation at the top of EEM_Base.
184
-     * However, for backward compatibility, we also attempt to handle $original_values from the
185
-     * model client-code domain, and from the database domain.
186
-     * E.g., when working with EE_Datetime_Fields, $original_value should be a DateTime or DbSafeDateTime
187
-     * (model object domain). However, for backward compatibility, we also accept a unix timestamp
188
-     * (old model object domain), MySQL datetime string (database domain) or string formatted according to the
189
-     * WP Datetime format (model client-code domain)
190
-     *
191
-     * @param \EE_Model_Field_Base $field_obj
192
-     * @param mixed                $original_value
193
-     * @param string               $requested_version
194
-     * @return mixed
195
-     */
196
-    public static function prepare_field_value_for_json($field_obj, $original_value, $requested_version)
197
-    {
198
-        if ($original_value === EE_INF) {
199
-            $new_value = Model_Data_Translator::ee_inf_in_rest;
200
-        } elseif ($field_obj instanceof \EE_Datetime_Field) {
201
-            if (is_string($original_value)) {
202
-                //did they submit a string of a unix timestamp?
203
-                if (is_numeric($original_value)) {
204
-                    $datetime_obj = new \DateTime();
205
-                    $datetime_obj->setTimestamp((int)$original_value);
206
-                } else {
207
-                    //first, check if its a MySQL timestamp in GMT
208
-                    $datetime_obj = \DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
209
-                }
210
-                if (! $datetime_obj instanceof \DateTime) {
211
-                    //so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
212
-                    $datetime_obj = $field_obj->prepare_for_set($original_value);
213
-                }
214
-                $original_value = $datetime_obj;
215
-            }
216
-            if ($original_value instanceof \DateTime) {
217
-                $new_value = $original_value->format('Y-m-d H:i:s');
218
-            } elseif (is_int($original_value)) {
219
-                $new_value = date('Y-m-d H:i:s', $original_value);
220
-            } elseif($original_value === null || $original_value === '') {
221
-                $new_value = null;
222
-            } else {
223
-                //so it's not a datetime object, unix timestamp (as string or int),
224
-                //MySQL timestamp, or even a string in the field object's format. So no idea what it is
225
-                throw new \EE_Error(
226
-                    sprintf(
227
-                        esc_html__(
228
-                            // @codingStandardsIgnoreStart
229
-                            'The value "%1$s" for the field "%2$s" on model "%3$s" could not be understood. It should be a PHP DateTime, unix timestamp, MySQL date, or string in the format "%4$s".',
230
-                            // @codingStandardsIgnoreEnd
231
-                            'event_espressso'
232
-                        ),
233
-                        $original_value,
234
-                        $field_obj->get_name(),
235
-                        $field_obj->get_model_name(),
236
-                        $field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
237
-                    )
238
-                );
239
-            }
240
-            $new_value = mysql_to_rfc3339($new_value);
241
-        } else {
242
-            $new_value = $original_value;
243
-        }
244
-        return apply_filters(
245
-            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_field_for_rest_api',
246
-            $new_value,
247
-            $field_obj,
248
-            $original_value,
249
-            $requested_version
250
-        );
251
-    }
252
-
253
-
254
-
255
-    /**
256
-     * Prepares condition-query-parameters (like what's in where and having) from
257
-     * the format expected in the API to use in the models
258
-     *
259
-     * @param array     $inputted_query_params_of_this_type
260
-     * @param \EEM_Base $model
261
-     * @param string    $requested_version
262
-     * @return array
263
-     * @throws \DomainException
264
-     * @throws \EE_Error
265
-     */
266
-    public static function prepare_conditions_query_params_for_models(
267
-        $inputted_query_params_of_this_type,
268
-        \EEM_Base $model,
269
-        $requested_version
270
-    ) {
271
-        $query_param_for_models = array();
272
-        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
273
-            $query_param_sans_stars = Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($query_param_key);
274
-            $field = Model_Data_Translator::deduce_field_from_query_param(
275
-                $query_param_sans_stars,
276
-                $model
277
-            );
278
-            //double-check is it a *_gmt field?
279
-            if (! $field instanceof \EE_Model_Field_Base
280
-                && Model_Data_Translator::is_gmt_date_field_name($query_param_sans_stars)
281
-            ) {
282
-                //yep, take off '_gmt', and find the field
283
-                $query_param_key = Model_Data_Translator::remove_gmt_from_field_name($query_param_sans_stars);
284
-                $field = Model_Data_Translator::deduce_field_from_query_param(
285
-                    $query_param_key,
286
-                    $model
287
-                );
288
-                $timezone = 'UTC';
289
-            } else {
290
-                //so it's not a GMT field. Set the timezone on the model to the default
291
-                $timezone = \EEH_DTT_Helper::get_valid_timezone_string();
292
-            }
293
-            if ($field instanceof \EE_Model_Field_Base) {
294
-                //did they specify an operator?
295
-                if (is_array($query_param_value)) {
296
-                    $op = $query_param_value[0];
297
-                    $translated_value = array($op);
298
-                    if (isset($query_param_value[1])) {
299
-                        $value = $query_param_value[1];
300
-                        $translated_value[1] = Model_Data_Translator::prepare_field_values_from_json($field, $value,
301
-                            $requested_version, $timezone);
302
-                    }
303
-                } else {
304
-                    $translated_value = Model_Data_Translator::prepare_field_value_from_json($field, $query_param_value,
305
-                        $requested_version, $timezone);
306
-                }
307
-                $query_param_for_models[$query_param_key] = $translated_value;
308
-            } else {
309
-                //so it's not for a field, assume it's a logic query param key
310
-                $query_param_for_models[$query_param_key] = Model_Data_Translator::prepare_conditions_query_params_for_models($query_param_value,
311
-                    $model, $requested_version);
312
-            }
313
-        }
314
-        return $query_param_for_models;
315
-    }
316
-
317
-
318
-
319
-    /**
320
-     * Mostly checks if the last 4 characters are "_gmt", indicating its a
321
-     * gmt date field name
322
-     *
323
-     * @param string $field_name
324
-     * @return boolean
325
-     */
326
-    public static function is_gmt_date_field_name($field_name)
327
-    {
328
-        return substr(
329
-                   Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($field_name),
330
-                   -4,
331
-                   4
332
-               ) === '_gmt';
333
-    }
334
-
335
-
336
-
337
-    /**
338
-     * Removes the last "_gmt" part of a field name (and if there is no "_gmt" at the end, leave it alone)
339
-     *
340
-     * @param string $field_name
341
-     * @return string
342
-     */
343
-    public static function remove_gmt_from_field_name($field_name)
344
-    {
345
-        if (! Model_Data_Translator::is_gmt_date_field_name($field_name)) {
346
-            return $field_name;
347
-        }
348
-        $query_param_sans_stars = Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($field_name);
349
-        $query_param_sans_gmt_and_sans_stars = substr(
350
-            $query_param_sans_stars,
351
-            0,
352
-            strrpos(
353
-                $field_name,
354
-                '_gmt'
355
-            )
356
-        );
357
-        return str_replace($query_param_sans_stars, $query_param_sans_gmt_and_sans_stars, $field_name);
358
-    }
359
-
360
-
361
-
362
-    /**
363
-     * Takes a field name from the REST API and prepares it for the model querying
364
-     *
365
-     * @param string $field_name
366
-     * @return string
367
-     */
368
-    public static function prepare_field_name_from_json($field_name)
369
-    {
370
-        if (Model_Data_Translator::is_gmt_date_field_name($field_name)) {
371
-            return Model_Data_Translator::remove_gmt_from_field_name($field_name);
372
-        }
373
-        return $field_name;
374
-    }
375
-
376
-
377
-
378
-    /**
379
-     * Takes array of field names from REST API and prepares for models
380
-     *
381
-     * @param array $field_names
382
-     * @return array of field names (possibly include model prefixes)
383
-     */
384
-    public static function prepare_field_names_from_json(array $field_names)
385
-    {
386
-        $new_array = array();
387
-        foreach ($field_names as $key => $field_name) {
388
-            $new_array[$key] = Model_Data_Translator::prepare_field_name_from_json($field_name);
389
-        }
390
-        return $new_array;
391
-    }
392
-
393
-
394
-
395
-    /**
396
-     * Takes array where array keys are field names (possibly with model path prefixes)
397
-     * from the REST API and prepares them for model querying
398
-     *
399
-     * @param array $field_names_as_keys
400
-     * @return array
401
-     */
402
-    public static function prepare_field_names_in_array_keys_from_json(array $field_names_as_keys)
403
-    {
404
-        $new_array = array();
405
-        foreach ($field_names_as_keys as $field_name => $value) {
406
-            $new_array[Model_Data_Translator::prepare_field_name_from_json($field_name)] = $value;
407
-        }
408
-        return $new_array;
409
-    }
410
-
411
-
412
-
413
-    /**
414
-     * Prepares an array of model query params for use in the REST API
415
-     *
416
-     * @param array     $model_query_params
417
-     * @param \EEM_Base $model
418
-     * @param string    $requested_version eg "4.8.36". If null is provided, defaults to the latest release of the EE4
419
-     *                                     REST API
420
-     * @return array which can be passed into the EE4 REST API when querying a model resource
421
-     * @throws \EE_Error
422
-     */
423
-    public static function prepare_query_params_for_rest_api(
424
-        array $model_query_params,
425
-        \EEM_Base $model,
426
-        $requested_version = null
427
-    ) {
428
-        if ($requested_version === null) {
429
-            $requested_version = \EED_Core_Rest_Api::latest_rest_api_version();
430
-        }
431
-        $rest_query_params = $model_query_params;
432
-        if (isset($model_query_params[0])) {
433
-            $rest_query_params['where'] = Model_Data_Translator::prepare_conditions_query_params_for_rest_api(
434
-                $model_query_params[0],
435
-                $model,
436
-                $requested_version
437
-            );
438
-            unset($rest_query_params[0]);
439
-        }
440
-        if (isset($model_query_params['having'])) {
441
-            $rest_query_params['having'] = Model_Data_Translator::prepare_conditions_query_params_for_rest_api(
442
-                $model_query_params['having'],
443
-                $model,
444
-                $requested_version
445
-            );
446
-        }
447
-        return apply_filters('FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_query_params_for_rest_api',
448
-            $rest_query_params, $model_query_params, $model, $requested_version);
449
-    }
450
-
451
-
452
-
453
-    /**
454
-     * Prepares all the sub-conditions query parameters (eg having or where conditions) for use in the rest api
455
-     *
456
-     * @param array     $inputted_query_params_of_this_type eg like the "where" or "having" conditions query params
457
-     *                                                      passed into EEM_Base::get_all()
458
-     * @param \EEM_Base $model
459
-     * @param string    $requested_version                  eg "4.8.36"
460
-     * @return array ready for use in the rest api query params
461
-     * @throws \EE_Error
462
-     */
463
-    public static function prepare_conditions_query_params_for_rest_api(
464
-        $inputted_query_params_of_this_type,
465
-        \EEM_Base $model,
466
-        $requested_version
467
-    ) {
468
-        $query_param_for_models = array();
469
-        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
470
-            $field = Model_Data_Translator::deduce_field_from_query_param(
471
-                Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($query_param_key),
472
-                $model
473
-            );
474
-            if ($field instanceof \EE_Model_Field_Base) {
475
-                //did they specify an operator?
476
-                if (is_array($query_param_value)) {
477
-                    $op = $query_param_value[0];
478
-                    $translated_value = array($op);
479
-                    if (isset($query_param_value[1])) {
480
-                        $value = $query_param_value[1];
481
-                        $translated_value[1] = Model_Data_Translator::prepare_field_values_for_json($field, $value,
482
-                            $requested_version);
483
-                    }
484
-                } else {
485
-                    $translated_value = Model_Data_Translator::prepare_field_value_for_json($field, $query_param_value,
486
-                        $requested_version);
487
-                }
488
-                $query_param_for_models[$query_param_key] = $translated_value;
489
-            } else {
490
-                //so it's not for a field, assume it's a logic query param key
491
-                $query_param_for_models[$query_param_key] = Model_Data_Translator::prepare_conditions_query_params_for_rest_api($query_param_value,
492
-                    $model, $requested_version);
493
-            }
494
-        }
495
-        return $query_param_for_models;
496
-    }
497
-
498
-
499
-
500
-    /**
501
-     * @param $condition_query_param_key
502
-     * @return string
503
-     */
504
-    public static function remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
505
-    {
506
-        $pos_of_star = strpos($condition_query_param_key, '*');
507
-        if ($pos_of_star === false) {
508
-            return $condition_query_param_key;
509
-        } else {
510
-            $condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
511
-            return $condition_query_param_sans_star;
512
-        }
513
-    }
514
-
515
-
516
-
517
-    /**
518
-     * Takes the input parameter and finds the model field that it indicates.
519
-     *
520
-     * @param string    $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
521
-     * @param \EEM_Base $model
522
-     * @return \EE_Model_Field_Base
523
-     * @throws \EE_Error
524
-     */
525
-    public static function deduce_field_from_query_param($query_param_name, \EEM_Base $model)
526
-    {
527
-        //ok, now proceed with deducing which part is the model's name, and which is the field's name
528
-        //which will help us find the database table and column
529
-        $query_param_parts = explode('.', $query_param_name);
530
-        if (empty($query_param_parts)) {
531
-            throw new \EE_Error(sprintf(__('_extract_column_name is empty when trying to extract column and table name from %s',
532
-                'event_espresso'), $query_param_name));
533
-        }
534
-        $number_of_parts = count($query_param_parts);
535
-        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
536
-        if ($number_of_parts === 1) {
537
-            $field_name = $last_query_param_part;
538
-        } else {// $number_of_parts >= 2
539
-            //the last part is the column name, and there are only 2parts. therefore...
540
-            $field_name = $last_query_param_part;
541
-            $model = \EE_Registry::instance()->load_model($query_param_parts[$number_of_parts - 2]);
542
-        }
543
-        try {
544
-            return $model->field_settings_for($field_name);
545
-        } catch (\EE_Error $e) {
546
-            return null;
547
-        }
548
-    }
29
+	/**
30
+	 * We used to use -1 for infinity in the rest api, but that's ambiguous for
31
+	 * fields that COULD contain -1; so we use null
32
+	 */
33
+	const ee_inf_in_rest = null;
34
+
35
+
36
+
37
+	/**
38
+	 * Prepares a possible array of input values from JSON for use by the models
39
+	 *
40
+	 * @param \EE_Model_Field_Base $field_obj
41
+	 * @param mixed                $original_value_maybe_array
42
+	 * @param string               $requested_version
43
+	 * @param string               $timezone_string treat values as being in this timezone
44
+	 * @return mixed
45
+	 * @throws \DomainException
46
+	 */
47
+	public static function prepare_field_values_from_json(
48
+		$field_obj,
49
+		$original_value_maybe_array,
50
+		$requested_version,
51
+		$timezone_string = 'UTC'
52
+	) {
53
+		if (is_array($original_value_maybe_array)) {
54
+			$new_value_maybe_array = array();
55
+			foreach ($original_value_maybe_array as $array_key => $array_item) {
56
+				$new_value_maybe_array[$array_key] = Model_Data_Translator::prepare_field_value_from_json(
57
+					$field_obj,
58
+					$array_item,
59
+					$requested_version,
60
+					$timezone_string
61
+				);
62
+			}
63
+		} else {
64
+			$new_value_maybe_array = Model_Data_Translator::prepare_field_value_from_json(
65
+				$field_obj,
66
+				$original_value_maybe_array,
67
+				$requested_version,
68
+				$timezone_string
69
+			);
70
+		}
71
+		return $new_value_maybe_array;
72
+	}
73
+
74
+
75
+
76
+	/**
77
+	 * Prepares an array of field values FOR use in JSON/REST API
78
+	 *
79
+	 * @param \EE_Model_Field_Base $field_obj
80
+	 * @param mixed                $original_value_maybe_array
81
+	 * @param string               $request_version (eg 4.8.36)
82
+	 * @return array
83
+	 */
84
+	public static function prepare_field_values_for_json($field_obj, $original_value_maybe_array, $request_version)
85
+	{
86
+		if (is_array($original_value_maybe_array)) {
87
+			$new_value_maybe_array = array();
88
+			foreach ($original_value_maybe_array as $array_key => $array_item) {
89
+				$new_value_maybe_array[$array_key] = Model_Data_Translator::prepare_field_value_for_json(
90
+					$field_obj,
91
+					$array_item,
92
+					$request_version
93
+				);
94
+			}
95
+		} else {
96
+			$new_value_maybe_array = Model_Data_Translator::prepare_field_value_for_json(
97
+				$field_obj,
98
+				$original_value_maybe_array,
99
+				$request_version
100
+			);
101
+		}
102
+		return $new_value_maybe_array;
103
+	}
104
+
105
+
106
+
107
+	/**
108
+	 * Prepares incoming data from the json or $_REQUEST parameters for the models'
109
+	 * "$query_params".
110
+	 *
111
+	 * @param \EE_Model_Field_Base $field_obj
112
+	 * @param mixed                $original_value
113
+	 * @param string               $requested_version
114
+	 * @param string               $timezone_string treat values as being in this timezone
115
+	 * @return mixed
116
+	 * @throws \DomainException
117
+	 */
118
+	public static function prepare_field_value_from_json(
119
+		$field_obj,
120
+		$original_value,
121
+		$requested_version,
122
+		$timezone_string = 'UTC' // UTC
123
+	)
124
+	{
125
+		$timezone_string = $timezone_string !== '' ? $timezone_string : get_option('timezone_string', '');
126
+		$new_value = null;
127
+		if ($field_obj instanceof \EE_Infinite_Integer_Field
128
+			&& in_array($original_value, array(null, ''), true)
129
+		) {
130
+			$new_value = EE_INF;
131
+		} elseif ($field_obj instanceof \EE_Datetime_Field) {
132
+			list($offset_sign, $offset_secs) = Model_Data_Translator::parse_timezone_offset(
133
+				$field_obj->get_timezone_offset(
134
+					new \DateTimeZone($timezone_string)
135
+				)
136
+			);
137
+			$offset_string =
138
+				str_pad(
139
+					floor($offset_secs / HOUR_IN_SECONDS),
140
+					2,
141
+					'0',
142
+					STR_PAD_LEFT
143
+				)
144
+				. ':'
145
+				. str_pad(
146
+					($offset_secs % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS,
147
+					2,
148
+					'0',
149
+					STR_PAD_LEFT
150
+				);
151
+			$new_value = rest_parse_date($original_value . $offset_sign . $offset_string);
152
+		} else {
153
+			$new_value = $original_value;
154
+		}
155
+		return $new_value;
156
+	}
157
+
158
+
159
+
160
+	/**
161
+	 * determines what's going on with them timezone strings
162
+	 *
163
+	 * @param int $timezone_offset
164
+	 * @return array
165
+	 */
166
+	private static function parse_timezone_offset($timezone_offset)
167
+	{
168
+		$first_char = substr((string)$timezone_offset, 0, 1);
169
+		if ($first_char === '+' || $first_char === '-') {
170
+			$offset_sign = $first_char;
171
+			$offset_secs = substr((string)$timezone_offset, 1);
172
+		} else {
173
+			$offset_sign = '+';
174
+			$offset_secs = $timezone_offset;
175
+		}
176
+		return array($offset_sign, $offset_secs);
177
+	}
178
+
179
+
180
+
181
+	/**
182
+	 * Prepares a field's value for display in the API.
183
+	 * The $original_value should be in the model object's domain of values, see the explanation at the top of EEM_Base.
184
+	 * However, for backward compatibility, we also attempt to handle $original_values from the
185
+	 * model client-code domain, and from the database domain.
186
+	 * E.g., when working with EE_Datetime_Fields, $original_value should be a DateTime or DbSafeDateTime
187
+	 * (model object domain). However, for backward compatibility, we also accept a unix timestamp
188
+	 * (old model object domain), MySQL datetime string (database domain) or string formatted according to the
189
+	 * WP Datetime format (model client-code domain)
190
+	 *
191
+	 * @param \EE_Model_Field_Base $field_obj
192
+	 * @param mixed                $original_value
193
+	 * @param string               $requested_version
194
+	 * @return mixed
195
+	 */
196
+	public static function prepare_field_value_for_json($field_obj, $original_value, $requested_version)
197
+	{
198
+		if ($original_value === EE_INF) {
199
+			$new_value = Model_Data_Translator::ee_inf_in_rest;
200
+		} elseif ($field_obj instanceof \EE_Datetime_Field) {
201
+			if (is_string($original_value)) {
202
+				//did they submit a string of a unix timestamp?
203
+				if (is_numeric($original_value)) {
204
+					$datetime_obj = new \DateTime();
205
+					$datetime_obj->setTimestamp((int)$original_value);
206
+				} else {
207
+					//first, check if its a MySQL timestamp in GMT
208
+					$datetime_obj = \DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
209
+				}
210
+				if (! $datetime_obj instanceof \DateTime) {
211
+					//so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
212
+					$datetime_obj = $field_obj->prepare_for_set($original_value);
213
+				}
214
+				$original_value = $datetime_obj;
215
+			}
216
+			if ($original_value instanceof \DateTime) {
217
+				$new_value = $original_value->format('Y-m-d H:i:s');
218
+			} elseif (is_int($original_value)) {
219
+				$new_value = date('Y-m-d H:i:s', $original_value);
220
+			} elseif($original_value === null || $original_value === '') {
221
+				$new_value = null;
222
+			} else {
223
+				//so it's not a datetime object, unix timestamp (as string or int),
224
+				//MySQL timestamp, or even a string in the field object's format. So no idea what it is
225
+				throw new \EE_Error(
226
+					sprintf(
227
+						esc_html__(
228
+							// @codingStandardsIgnoreStart
229
+							'The value "%1$s" for the field "%2$s" on model "%3$s" could not be understood. It should be a PHP DateTime, unix timestamp, MySQL date, or string in the format "%4$s".',
230
+							// @codingStandardsIgnoreEnd
231
+							'event_espressso'
232
+						),
233
+						$original_value,
234
+						$field_obj->get_name(),
235
+						$field_obj->get_model_name(),
236
+						$field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
237
+					)
238
+				);
239
+			}
240
+			$new_value = mysql_to_rfc3339($new_value);
241
+		} else {
242
+			$new_value = $original_value;
243
+		}
244
+		return apply_filters(
245
+			'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_field_for_rest_api',
246
+			$new_value,
247
+			$field_obj,
248
+			$original_value,
249
+			$requested_version
250
+		);
251
+	}
252
+
253
+
254
+
255
+	/**
256
+	 * Prepares condition-query-parameters (like what's in where and having) from
257
+	 * the format expected in the API to use in the models
258
+	 *
259
+	 * @param array     $inputted_query_params_of_this_type
260
+	 * @param \EEM_Base $model
261
+	 * @param string    $requested_version
262
+	 * @return array
263
+	 * @throws \DomainException
264
+	 * @throws \EE_Error
265
+	 */
266
+	public static function prepare_conditions_query_params_for_models(
267
+		$inputted_query_params_of_this_type,
268
+		\EEM_Base $model,
269
+		$requested_version
270
+	) {
271
+		$query_param_for_models = array();
272
+		foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
273
+			$query_param_sans_stars = Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($query_param_key);
274
+			$field = Model_Data_Translator::deduce_field_from_query_param(
275
+				$query_param_sans_stars,
276
+				$model
277
+			);
278
+			//double-check is it a *_gmt field?
279
+			if (! $field instanceof \EE_Model_Field_Base
280
+				&& Model_Data_Translator::is_gmt_date_field_name($query_param_sans_stars)
281
+			) {
282
+				//yep, take off '_gmt', and find the field
283
+				$query_param_key = Model_Data_Translator::remove_gmt_from_field_name($query_param_sans_stars);
284
+				$field = Model_Data_Translator::deduce_field_from_query_param(
285
+					$query_param_key,
286
+					$model
287
+				);
288
+				$timezone = 'UTC';
289
+			} else {
290
+				//so it's not a GMT field. Set the timezone on the model to the default
291
+				$timezone = \EEH_DTT_Helper::get_valid_timezone_string();
292
+			}
293
+			if ($field instanceof \EE_Model_Field_Base) {
294
+				//did they specify an operator?
295
+				if (is_array($query_param_value)) {
296
+					$op = $query_param_value[0];
297
+					$translated_value = array($op);
298
+					if (isset($query_param_value[1])) {
299
+						$value = $query_param_value[1];
300
+						$translated_value[1] = Model_Data_Translator::prepare_field_values_from_json($field, $value,
301
+							$requested_version, $timezone);
302
+					}
303
+				} else {
304
+					$translated_value = Model_Data_Translator::prepare_field_value_from_json($field, $query_param_value,
305
+						$requested_version, $timezone);
306
+				}
307
+				$query_param_for_models[$query_param_key] = $translated_value;
308
+			} else {
309
+				//so it's not for a field, assume it's a logic query param key
310
+				$query_param_for_models[$query_param_key] = Model_Data_Translator::prepare_conditions_query_params_for_models($query_param_value,
311
+					$model, $requested_version);
312
+			}
313
+		}
314
+		return $query_param_for_models;
315
+	}
316
+
317
+
318
+
319
+	/**
320
+	 * Mostly checks if the last 4 characters are "_gmt", indicating its a
321
+	 * gmt date field name
322
+	 *
323
+	 * @param string $field_name
324
+	 * @return boolean
325
+	 */
326
+	public static function is_gmt_date_field_name($field_name)
327
+	{
328
+		return substr(
329
+				   Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($field_name),
330
+				   -4,
331
+				   4
332
+			   ) === '_gmt';
333
+	}
334
+
335
+
336
+
337
+	/**
338
+	 * Removes the last "_gmt" part of a field name (and if there is no "_gmt" at the end, leave it alone)
339
+	 *
340
+	 * @param string $field_name
341
+	 * @return string
342
+	 */
343
+	public static function remove_gmt_from_field_name($field_name)
344
+	{
345
+		if (! Model_Data_Translator::is_gmt_date_field_name($field_name)) {
346
+			return $field_name;
347
+		}
348
+		$query_param_sans_stars = Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($field_name);
349
+		$query_param_sans_gmt_and_sans_stars = substr(
350
+			$query_param_sans_stars,
351
+			0,
352
+			strrpos(
353
+				$field_name,
354
+				'_gmt'
355
+			)
356
+		);
357
+		return str_replace($query_param_sans_stars, $query_param_sans_gmt_and_sans_stars, $field_name);
358
+	}
359
+
360
+
361
+
362
+	/**
363
+	 * Takes a field name from the REST API and prepares it for the model querying
364
+	 *
365
+	 * @param string $field_name
366
+	 * @return string
367
+	 */
368
+	public static function prepare_field_name_from_json($field_name)
369
+	{
370
+		if (Model_Data_Translator::is_gmt_date_field_name($field_name)) {
371
+			return Model_Data_Translator::remove_gmt_from_field_name($field_name);
372
+		}
373
+		return $field_name;
374
+	}
375
+
376
+
377
+
378
+	/**
379
+	 * Takes array of field names from REST API and prepares for models
380
+	 *
381
+	 * @param array $field_names
382
+	 * @return array of field names (possibly include model prefixes)
383
+	 */
384
+	public static function prepare_field_names_from_json(array $field_names)
385
+	{
386
+		$new_array = array();
387
+		foreach ($field_names as $key => $field_name) {
388
+			$new_array[$key] = Model_Data_Translator::prepare_field_name_from_json($field_name);
389
+		}
390
+		return $new_array;
391
+	}
392
+
393
+
394
+
395
+	/**
396
+	 * Takes array where array keys are field names (possibly with model path prefixes)
397
+	 * from the REST API and prepares them for model querying
398
+	 *
399
+	 * @param array $field_names_as_keys
400
+	 * @return array
401
+	 */
402
+	public static function prepare_field_names_in_array_keys_from_json(array $field_names_as_keys)
403
+	{
404
+		$new_array = array();
405
+		foreach ($field_names_as_keys as $field_name => $value) {
406
+			$new_array[Model_Data_Translator::prepare_field_name_from_json($field_name)] = $value;
407
+		}
408
+		return $new_array;
409
+	}
410
+
411
+
412
+
413
+	/**
414
+	 * Prepares an array of model query params for use in the REST API
415
+	 *
416
+	 * @param array     $model_query_params
417
+	 * @param \EEM_Base $model
418
+	 * @param string    $requested_version eg "4.8.36". If null is provided, defaults to the latest release of the EE4
419
+	 *                                     REST API
420
+	 * @return array which can be passed into the EE4 REST API when querying a model resource
421
+	 * @throws \EE_Error
422
+	 */
423
+	public static function prepare_query_params_for_rest_api(
424
+		array $model_query_params,
425
+		\EEM_Base $model,
426
+		$requested_version = null
427
+	) {
428
+		if ($requested_version === null) {
429
+			$requested_version = \EED_Core_Rest_Api::latest_rest_api_version();
430
+		}
431
+		$rest_query_params = $model_query_params;
432
+		if (isset($model_query_params[0])) {
433
+			$rest_query_params['where'] = Model_Data_Translator::prepare_conditions_query_params_for_rest_api(
434
+				$model_query_params[0],
435
+				$model,
436
+				$requested_version
437
+			);
438
+			unset($rest_query_params[0]);
439
+		}
440
+		if (isset($model_query_params['having'])) {
441
+			$rest_query_params['having'] = Model_Data_Translator::prepare_conditions_query_params_for_rest_api(
442
+				$model_query_params['having'],
443
+				$model,
444
+				$requested_version
445
+			);
446
+		}
447
+		return apply_filters('FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_query_params_for_rest_api',
448
+			$rest_query_params, $model_query_params, $model, $requested_version);
449
+	}
450
+
451
+
452
+
453
+	/**
454
+	 * Prepares all the sub-conditions query parameters (eg having or where conditions) for use in the rest api
455
+	 *
456
+	 * @param array     $inputted_query_params_of_this_type eg like the "where" or "having" conditions query params
457
+	 *                                                      passed into EEM_Base::get_all()
458
+	 * @param \EEM_Base $model
459
+	 * @param string    $requested_version                  eg "4.8.36"
460
+	 * @return array ready for use in the rest api query params
461
+	 * @throws \EE_Error
462
+	 */
463
+	public static function prepare_conditions_query_params_for_rest_api(
464
+		$inputted_query_params_of_this_type,
465
+		\EEM_Base $model,
466
+		$requested_version
467
+	) {
468
+		$query_param_for_models = array();
469
+		foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
470
+			$field = Model_Data_Translator::deduce_field_from_query_param(
471
+				Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($query_param_key),
472
+				$model
473
+			);
474
+			if ($field instanceof \EE_Model_Field_Base) {
475
+				//did they specify an operator?
476
+				if (is_array($query_param_value)) {
477
+					$op = $query_param_value[0];
478
+					$translated_value = array($op);
479
+					if (isset($query_param_value[1])) {
480
+						$value = $query_param_value[1];
481
+						$translated_value[1] = Model_Data_Translator::prepare_field_values_for_json($field, $value,
482
+							$requested_version);
483
+					}
484
+				} else {
485
+					$translated_value = Model_Data_Translator::prepare_field_value_for_json($field, $query_param_value,
486
+						$requested_version);
487
+				}
488
+				$query_param_for_models[$query_param_key] = $translated_value;
489
+			} else {
490
+				//so it's not for a field, assume it's a logic query param key
491
+				$query_param_for_models[$query_param_key] = Model_Data_Translator::prepare_conditions_query_params_for_rest_api($query_param_value,
492
+					$model, $requested_version);
493
+			}
494
+		}
495
+		return $query_param_for_models;
496
+	}
497
+
498
+
499
+
500
+	/**
501
+	 * @param $condition_query_param_key
502
+	 * @return string
503
+	 */
504
+	public static function remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
505
+	{
506
+		$pos_of_star = strpos($condition_query_param_key, '*');
507
+		if ($pos_of_star === false) {
508
+			return $condition_query_param_key;
509
+		} else {
510
+			$condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
511
+			return $condition_query_param_sans_star;
512
+		}
513
+	}
514
+
515
+
516
+
517
+	/**
518
+	 * Takes the input parameter and finds the model field that it indicates.
519
+	 *
520
+	 * @param string    $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
521
+	 * @param \EEM_Base $model
522
+	 * @return \EE_Model_Field_Base
523
+	 * @throws \EE_Error
524
+	 */
525
+	public static function deduce_field_from_query_param($query_param_name, \EEM_Base $model)
526
+	{
527
+		//ok, now proceed with deducing which part is the model's name, and which is the field's name
528
+		//which will help us find the database table and column
529
+		$query_param_parts = explode('.', $query_param_name);
530
+		if (empty($query_param_parts)) {
531
+			throw new \EE_Error(sprintf(__('_extract_column_name is empty when trying to extract column and table name from %s',
532
+				'event_espresso'), $query_param_name));
533
+		}
534
+		$number_of_parts = count($query_param_parts);
535
+		$last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
536
+		if ($number_of_parts === 1) {
537
+			$field_name = $last_query_param_part;
538
+		} else {// $number_of_parts >= 2
539
+			//the last part is the column name, and there are only 2parts. therefore...
540
+			$field_name = $last_query_param_part;
541
+			$model = \EE_Registry::instance()->load_model($query_param_parts[$number_of_parts - 2]);
542
+		}
543
+		try {
544
+			return $model->field_settings_for($field_name);
545
+		} catch (\EE_Error $e) {
546
+			return null;
547
+		}
548
+	}
549 549
 
550 550
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -1,7 +1,7 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 namespace EventEspresso\core\libraries\rest_api;
3 3
 
4
-if (! defined('EVENT_ESPRESSO_VERSION')) {
4
+if ( ! defined('EVENT_ESPRESSO_VERSION')) {
5 5
     exit('No direct script access allowed');
6 6
 }
7 7
 
@@ -148,7 +148,7 @@  discard block
 block discarded – undo
148 148
                     '0',
149 149
                     STR_PAD_LEFT
150 150
                 );
151
-            $new_value = rest_parse_date($original_value . $offset_sign . $offset_string);
151
+            $new_value = rest_parse_date($original_value.$offset_sign.$offset_string);
152 152
         } else {
153 153
             $new_value = $original_value;
154 154
         }
@@ -165,10 +165,10 @@  discard block
 block discarded – undo
165 165
      */
166 166
     private static function parse_timezone_offset($timezone_offset)
167 167
     {
168
-        $first_char = substr((string)$timezone_offset, 0, 1);
168
+        $first_char = substr((string) $timezone_offset, 0, 1);
169 169
         if ($first_char === '+' || $first_char === '-') {
170 170
             $offset_sign = $first_char;
171
-            $offset_secs = substr((string)$timezone_offset, 1);
171
+            $offset_secs = substr((string) $timezone_offset, 1);
172 172
         } else {
173 173
             $offset_sign = '+';
174 174
             $offset_secs = $timezone_offset;
@@ -202,12 +202,12 @@  discard block
 block discarded – undo
202 202
                 //did they submit a string of a unix timestamp?
203 203
                 if (is_numeric($original_value)) {
204 204
                     $datetime_obj = new \DateTime();
205
-                    $datetime_obj->setTimestamp((int)$original_value);
205
+                    $datetime_obj->setTimestamp((int) $original_value);
206 206
                 } else {
207 207
                     //first, check if its a MySQL timestamp in GMT
208 208
                     $datetime_obj = \DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
209 209
                 }
210
-                if (! $datetime_obj instanceof \DateTime) {
210
+                if ( ! $datetime_obj instanceof \DateTime) {
211 211
                     //so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
212 212
                     $datetime_obj = $field_obj->prepare_for_set($original_value);
213 213
                 }
@@ -217,7 +217,7 @@  discard block
 block discarded – undo
217 217
                 $new_value = $original_value->format('Y-m-d H:i:s');
218 218
             } elseif (is_int($original_value)) {
219 219
                 $new_value = date('Y-m-d H:i:s', $original_value);
220
-            } elseif($original_value === null || $original_value === '') {
220
+            } elseif ($original_value === null || $original_value === '') {
221 221
                 $new_value = null;
222 222
             } else {
223 223
                 //so it's not a datetime object, unix timestamp (as string or int),
@@ -233,7 +233,7 @@  discard block
 block discarded – undo
233 233
                         $original_value,
234 234
                         $field_obj->get_name(),
235 235
                         $field_obj->get_model_name(),
236
-                        $field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
236
+                        $field_obj->get_time_format().' '.$field_obj->get_time_format()
237 237
                     )
238 238
                 );
239 239
             }
@@ -276,7 +276,7 @@  discard block
 block discarded – undo
276 276
                 $model
277 277
             );
278 278
             //double-check is it a *_gmt field?
279
-            if (! $field instanceof \EE_Model_Field_Base
279
+            if ( ! $field instanceof \EE_Model_Field_Base
280 280
                 && Model_Data_Translator::is_gmt_date_field_name($query_param_sans_stars)
281 281
             ) {
282 282
                 //yep, take off '_gmt', and find the field
@@ -342,7 +342,7 @@  discard block
 block discarded – undo
342 342
      */
343 343
     public static function remove_gmt_from_field_name($field_name)
344 344
     {
345
-        if (! Model_Data_Translator::is_gmt_date_field_name($field_name)) {
345
+        if ( ! Model_Data_Translator::is_gmt_date_field_name($field_name)) {
346 346
             return $field_name;
347 347
         }
348 348
         $query_param_sans_stars = Model_Data_Translator::remove_stars_and_anything_after_from_condition_query_param_key($field_name);
Please login to merge, or discard this patch.
core/db_classes/EE_Base_Class.class.php 3 patches
Spacing   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -150,8 +150,8 @@  discard block
 block discarded – undo
150 150
             list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
151 151
         } else {
152 152
             //set default formats for date and time
153
-            $this->_dt_frmt = (string)get_option('date_format', 'Y-m-d');
154
-            $this->_tm_frmt = (string)get_option('time_format', 'g:i a');
153
+            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
154
+            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
155 155
         }
156 156
         //if db model is instantiating
157 157
         if ($bydb) {
@@ -472,7 +472,7 @@  discard block
 block discarded – undo
472 472
      */
473 473
     public function get_format($full = true)
474 474
     {
475
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
475
+        return $full ? $this->_dt_frmt.' '.$this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
476 476
     }
477 477
 
478 478
 
@@ -580,7 +580,7 @@  discard block
 block discarded – undo
580 580
         //verify the field exists
581 581
         $this->get_model()->field_settings_for($fieldname);
582 582
         $cache_type = $pretty ? 'pretty' : 'standard';
583
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
583
+        $cache_type .= ! empty($extra_cache_ref) ? '_'.$extra_cache_ref : '';
584 584
         if (isset($this->_cached_properties[$fieldname][$cache_type])) {
585 585
             return $this->_cached_properties[$fieldname][$cache_type];
586 586
         }
@@ -795,7 +795,7 @@  discard block
 block discarded – undo
795 795
         $current_cache_id = ''
796 796
     ) {
797 797
         // verify that incoming object is of the correct type
798
-        $obj_class = 'EE_' . $relationName;
798
+        $obj_class = 'EE_'.$relationName;
799 799
         if ($newly_saved_object instanceof $obj_class) {
800 800
             /* @type EE_Base_Class $newly_saved_object */
801 801
             // now get the type of relation
@@ -1277,7 +1277,7 @@  discard block
 block discarded – undo
1277 1277
      */
1278 1278
     public function get_i18n_datetime($field_name, $format = '')
1279 1279
     {
1280
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1280
+        $format = empty($format) ? $this->_dt_frmt.' '.$this->_tm_frmt : $format;
1281 1281
         return date_i18n(
1282 1282
             $format,
1283 1283
             EEH_DTT_Helper::get_timestamp_with_offset($this->get_raw($field_name), $this->_timezone)
@@ -1415,8 +1415,8 @@  discard block
 block discarded – undo
1415 1415
         }
1416 1416
         $original_timezone = $this->_timezone;
1417 1417
         $this->set_timezone($timezone);
1418
-        $fn = (array)$field_name;
1419
-        $args = array_merge($fn, (array)$args);
1418
+        $fn = (array) $field_name;
1419
+        $args = array_merge($fn, (array) $args);
1420 1420
         if ( ! method_exists($this, $callback)) {
1421 1421
             throw new EE_Error(
1422 1422
                 sprintf(
@@ -1428,8 +1428,8 @@  discard block
 block discarded – undo
1428 1428
                 )
1429 1429
             );
1430 1430
         }
1431
-        $args = (array)$args;
1432
-        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1431
+        $args = (array) $args;
1432
+        $return = $prepend.call_user_func_array(array($this, $callback), $args).$append;
1433 1433
         $this->set_timezone($original_timezone);
1434 1434
         return $return;
1435 1435
     }
@@ -1566,14 +1566,14 @@  discard block
 block discarded – undo
1566 1566
          * @param array         $set_cols_n_values
1567 1567
          * @param EE_Base_Class $model_object
1568 1568
          */
1569
-        $set_cols_n_values = (array)apply_filters('FHEE__EE_Base_Class__save__set_cols_n_values', $set_cols_n_values,
1569
+        $set_cols_n_values = (array) apply_filters('FHEE__EE_Base_Class__save__set_cols_n_values', $set_cols_n_values,
1570 1570
             $this);
1571 1571
         //set attributes as provided in $set_cols_n_values
1572 1572
         foreach ($set_cols_n_values as $column => $value) {
1573 1573
             $this->set($column, $value);
1574 1574
         }
1575 1575
         // no changes ? then don't do anything
1576
-        if (! $this->_has_changes && $this->ID() && $this->get_model()->get_primary_key_field()->is_auto_increment()) {
1576
+        if ( ! $this->_has_changes && $this->ID() && $this->get_model()->get_primary_key_field()->is_auto_increment()) {
1577 1577
             return 0;
1578 1578
         }
1579 1579
         /**
@@ -1625,8 +1625,8 @@  discard block
 block discarded – undo
1625 1625
                                 __('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',
1626 1626
                                     'event_espresso'),
1627 1627
                                 get_class($this),
1628
-                                get_class($this->get_model()) . '::instance()->add_to_entity_map()',
1629
-                                get_class($this->get_model()) . '::instance()->get_one_by_ID()',
1628
+                                get_class($this->get_model()).'::instance()->add_to_entity_map()',
1629
+                                get_class($this->get_model()).'::instance()->get_one_by_ID()',
1630 1630
                                 '<br />'
1631 1631
                             )
1632 1632
                         );
@@ -1894,7 +1894,7 @@  discard block
 block discarded – undo
1894 1894
         if (strpos($model_name, "EE_") === 0) {
1895 1895
             $model_classname = str_replace("EE_", "EEM_", $model_name);
1896 1896
         } else {
1897
-            $model_classname = "EEM_" . $model_name;
1897
+            $model_classname = "EEM_".$model_name;
1898 1898
         }
1899 1899
         return $model_classname;
1900 1900
     }
@@ -2278,7 +2278,7 @@  discard block
 block discarded – undo
2278 2278
      */
2279 2279
     protected function _property_exists($properties)
2280 2280
     {
2281
-        foreach ((array)$properties as $property_name) {
2281
+        foreach ((array) $properties as $property_name) {
2282 2282
             //first make sure this property exists
2283 2283
             if ( ! $this->_fields[$property_name]) {
2284 2284
                 throw new EE_Error(
@@ -2608,8 +2608,8 @@  discard block
 block discarded – undo
2608 2608
                         __('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.',
2609 2609
                             'event_espresso'),
2610 2610
                         $this->ID(),
2611
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2612
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2611
+                        get_class($this->get_model()).'::instance()->add_to_entity_map()',
2612
+                        get_class($this->get_model()).'::instance()->refresh_entity_map()'
2613 2613
                     )
2614 2614
                 );
2615 2615
             }
@@ -2668,7 +2668,7 @@  discard block
 block discarded – undo
2668 2668
     {
2669 2669
         foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
2670 2670
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
2671
-                $classname = 'EE_' . $this->get_model()->get_this_model_name();
2671
+                $classname = 'EE_'.$this->get_model()->get_this_model_name();
2672 2672
                 if (
2673 2673
                     $this->get_one_from_cache($relation_name) instanceof $classname
2674 2674
                     && $this->get_one_from_cache($relation_name)->ID()
Please login to merge, or discard this patch.
Doc Comments   +10 added lines, -9 removed lines patch added patch discarded remove patch
@@ -661,7 +661,7 @@  discard block
 block discarded – undo
661 661
      *
662 662
      * @param \EE_Datetime_Field $datetime_field
663 663
      * @param bool               $pretty
664
-     * @param null $date_or_time
664
+     * @param string|null $date_or_time
665 665
      * @return void
666 666
      * @throws \EE_Error
667 667
      */
@@ -998,7 +998,7 @@  discard block
 block discarded – undo
998 998
      *
999 999
      * @param null  $field_to_order_by  What field is being used as the reference point.
1000 1000
      * @param array $query_params       Any additional conditions on the query.
1001
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1001
+     * @param string  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1002 1002
      *                                  you can indicate just the columns you want returned
1003 1003
      * @return array|EE_Base_Class
1004 1004
      * @throws \EE_Error
@@ -1023,7 +1023,7 @@  discard block
 block discarded – undo
1023 1023
      *
1024 1024
      * @param null  $field_to_order_by  What field is being used as the reference point.
1025 1025
      * @param array $query_params       Any additional conditions on the query.
1026
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1026
+     * @param string  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1027 1027
      *                                  you can indicate just the column you want returned
1028 1028
      * @return array|EE_Base_Class
1029 1029
      * @throws \EE_Error
@@ -1096,7 +1096,7 @@  discard block
 block discarded – undo
1096 1096
      * This method simply returns the RAW unprocessed value for the given property in this class
1097 1097
      *
1098 1098
      * @param  string $field_name A valid fieldname
1099
-     * @return mixed              Whatever the raw value stored on the property is.
1099
+     * @return integer|null              Whatever the raw value stored on the property is.
1100 1100
      * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1101 1101
      */
1102 1102
     public function get_raw($field_name)
@@ -1373,7 +1373,7 @@  discard block
 block discarded – undo
1373 1373
      * sets the time on a datetime property
1374 1374
      *
1375 1375
      * @access protected
1376
-     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1376
+     * @param string $time      a valid time string for php datetime functions (or DateTime object)
1377 1377
      * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1378 1378
      * @throws \EE_Error
1379 1379
      */
@@ -1388,7 +1388,7 @@  discard block
 block discarded – undo
1388 1388
      * sets the date on a datetime property
1389 1389
      *
1390 1390
      * @access protected
1391
-     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1391
+     * @param string $date      a valid date string for php datetime functions ( or DateTime object)
1392 1392
      * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1393 1393
      * @throws \EE_Error
1394 1394
      */
@@ -1451,6 +1451,7 @@  discard block
 block discarded – undo
1451 1451
      * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1452 1452
      * @param string               $prepend    You can include something to prepend on the timestamp
1453 1453
      * @param string               $append     You can include something to append on the timestamp
1454
+     * @param string $args
1454 1455
      * @throws EE_Error
1455 1456
      * @return string timestamp
1456 1457
      */
@@ -1843,7 +1844,7 @@  discard block
 block discarded – undo
1843 1844
      *
1844 1845
      * @param  array  $props_n_values   incoming array of properties and their values
1845 1846
      * @param  string $classname        the classname of the child class
1846
-     * @param null    $timezone
1847
+     * @param string    $timezone
1847 1848
      * @param array   $date_formats     incoming date_formats in an array where the first value is the
1848 1849
      *                                  date_format and the second value is the time format
1849 1850
      * @return mixed (EE_Base_Class|bool)
@@ -1921,7 +1922,7 @@  discard block
 block discarded – undo
1921 1922
      * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
1922 1923
      *
1923 1924
      * @param string $model_classname
1924
-     * @param null   $timezone
1925
+     * @param string|null   $timezone
1925 1926
      * @return EEM_Base
1926 1927
      */
1927 1928
     protected static function _get_model_instance_with_name($model_classname, $timezone = null)
@@ -2420,7 +2421,7 @@  discard block
 block discarded – undo
2420 2421
      *
2421 2422
      * @param string $meta_key
2422 2423
      * @param mixed  $meta_value
2423
-     * @param mixed  $previous_value
2424
+     * @param boolean  $previous_value
2424 2425
      * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2425 2426
      * @throws \EE_Error
2426 2427
      * NOTE: if the values haven't changed, returns 0
Please login to merge, or discard this patch.
Indentation   +2721 added lines, -2721 removed lines patch added patch discarded remove patch
@@ -1,5 +1,5 @@  discard block
 block discarded – undo
1 1
 <?php if ( ! defined('EVENT_ESPRESSO_VERSION')) {
2
-    exit('No direct script access allowed');
2
+	exit('No direct script access allowed');
3 3
 }
4 4
 do_action('AHEE_log', __FILE__, ' FILE LOADED', '');
5 5
 
@@ -25,2726 +25,2726 @@  discard block
 block discarded – undo
25 25
 abstract class EE_Base_Class
26 26
 {
27 27
 
28
-    /**
29
-     * This is an array of the original properties and values provided during construction
30
-     * of this model object. (keys are model field names, values are their values).
31
-     * This list is important to remember so that when we are merging data from the db, we know
32
-     * which values to override and which to not override.
33
-     *
34
-     * @var array
35
-     */
36
-    protected $_props_n_values_provided_in_constructor;
37
-
38
-    /**
39
-     * Timezone
40
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
41
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
42
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
43
-     * access to it.
44
-     *
45
-     * @var string
46
-     */
47
-    protected $_timezone;
48
-
49
-
50
-
51
-    /**
52
-     * date format
53
-     * pattern or format for displaying dates
54
-     *
55
-     * @var string $_dt_frmt
56
-     */
57
-    protected $_dt_frmt;
58
-
59
-
60
-
61
-    /**
62
-     * time format
63
-     * pattern or format for displaying time
64
-     *
65
-     * @var string $_tm_frmt
66
-     */
67
-    protected $_tm_frmt;
68
-
69
-
70
-
71
-    /**
72
-     * This property is for holding a cached array of object properties indexed by property name as the key.
73
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
74
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
75
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
76
-     *
77
-     * @var array
78
-     */
79
-    protected $_cached_properties = array();
80
-
81
-    /**
82
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
83
-     * single
84
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
85
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
86
-     * all others have an array)
87
-     *
88
-     * @var array
89
-     */
90
-    protected $_model_relations = array();
91
-
92
-    /**
93
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
94
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
95
-     *
96
-     * @var array
97
-     */
98
-    protected $_fields = array();
99
-
100
-    /**
101
-     * @var boolean indicating whether or not this model object is intended to ever be saved
102
-     * For example, we might create model objects intended to only be used for the duration
103
-     * of this request and to be thrown away, and if they were accidentally saved
104
-     * it would be a bug.
105
-     */
106
-    protected $_allow_persist = true;
107
-
108
-    /**
109
-     * @var boolean indicating whether or not this model object's properties have changed since construction
110
-     */
111
-    protected $_has_changes = false;
112
-
113
-
114
-
115
-    /**
116
-     * @param array  $fieldValues
117
-     * @param string $timezone
118
-     * @param array  $date_formats
119
-     * @param bool   $bydb
120
-     * @return \EE_Base_Class
121
-     * @throws \EE_Error
122
-     */
123
-    public static function new_instance(
124
-        array $fieldValues = array(),
125
-        $timezone = '',
126
-        array $date_formats = array(),
127
-        $bydb = false
128
-    )
129
-    {
130
-        $className = get_called_class();
131
-        if ( ! $bydb) {
132
-            $cached_object = \EE_Base_Class::_check_for_object($fieldValues, $className, $timezone, $date_formats);
133
-            if ($cached_object) {
134
-                return $cached_object;
135
-            }
136
-        }
137
-        return new static($fieldValues, $bydb, $timezone, $date_formats);
138
-    }
139
-
140
-
141
-
142
-    /**
143
-     * @deprecated
144
-     * @param array  $fieldValues
145
-     * @param string $timezone
146
-     * @param array  $date_formats
147
-     * @return \EE_Base_Class
148
-     * @throws \EE_Error
149
-     */
150
-    public static function new_instance_from_db(array $fieldValues = array(), $timezone = '', array $date_formats = array())
151
-    {
152
-        return static::new_instance($fieldValues, $timezone, $date_formats, true);
153
-    }
154
-
155
-
156
-    /**
157
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children play nice
158
-     *
159
-     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
160
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
161
-     *                                                         TXN_amount, QST_name, etc) and values are their values
162
-     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
163
-     *                                                         corresponding db model or not.
164
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
165
-     *                                                         be in when instantiating a EE_Base_Class object.
166
-     * @param array   $date_formats                            An array of date formats to set on construct where first
167
-     *                                                         value is the date_format and second value is the time
168
-     *                                                         format.
169
-     * @throws EE_Error
170
-     */
171
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
172
-    {
173
-        $className = get_class($this);
174
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
175
-        $model = $this->get_model();
176
-        $model_fields = $model->field_settings(false);
177
-        // ensure $fieldValues is an array
178
-        $fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
179
-        // EEH_Debug_Tools::printr( $fieldValues, '$fieldValues  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
180
-        // verify client code has not passed any invalid field names
181
-        foreach ($fieldValues as $field_name => $field_value) {
182
-            if ( ! isset($model_fields[$field_name])) {
183
-                throw new EE_Error(sprintf(__("Invalid field (%s) passed to constructor of %s. Allowed fields are :%s",
184
-                    "event_espresso"), $field_name, get_class($this), implode(", ", array_keys($model_fields))));
185
-            }
186
-        }
187
-        // EEH_Debug_Tools::printr( $model_fields, '$model_fields  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
188
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
189
-        if ( ! empty($date_formats) && is_array($date_formats)) {
190
-            list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
191
-        } else {
192
-            //set default formats for date and time
193
-            $this->_dt_frmt = (string)get_option('date_format', 'Y-m-d');
194
-            $this->_tm_frmt = (string)get_option('time_format', 'g:i a');
195
-        }
196
-        //if db model is instantiating
197
-        if ($bydb) {
198
-            //client code has indicated these field values are from the database
199
-            foreach ($model_fields as $fieldName => $field) {
200
-                $this->set_from_db($fieldName, isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null);
201
-            }
202
-        } else {
203
-            //we're constructing a brand
204
-            //new instance of the model object. Generally, this means we'll need to do more field validation
205
-            foreach ($model_fields as $fieldName => $field) {
206
-                $this->set($fieldName, isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null, true);
207
-            }
208
-        }
209
-        //remember what values were passed to this constructor
210
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
211
-        //remember in entity mapper
212
-        if ( ! $bydb && $model->has_primary_key_field() && $this->ID()) {
213
-            $model->add_to_entity_map($this);
214
-        }
215
-        //setup all the relations
216
-        foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
217
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
218
-                $this->_model_relations[$relation_name] = null;
219
-            } else {
220
-                $this->_model_relations[$relation_name] = array();
221
-            }
222
-        }
223
-        /**
224
-         * Action done at the end of each model object construction
225
-         *
226
-         * @param EE_Base_Class $this the model object just created
227
-         */
228
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
229
-    }
230
-
231
-
232
-
233
-    /**
234
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
235
-     *
236
-     * @return boolean
237
-     */
238
-    public function allow_persist()
239
-    {
240
-        return $this->_allow_persist;
241
-    }
242
-
243
-
244
-
245
-    /**
246
-     * Sets whether or not this model object should be allowed to be saved to the DB.
247
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
248
-     * you got new information that somehow made you change your mind.
249
-     *
250
-     * @param boolean $allow_persist
251
-     * @return boolean
252
-     */
253
-    public function set_allow_persist($allow_persist)
254
-    {
255
-        return $this->_allow_persist = $allow_persist;
256
-    }
257
-
258
-
259
-
260
-    /**
261
-     * Gets the field's original value when this object was constructed during this request.
262
-     * This can be helpful when determining if a model object has changed or not
263
-     *
264
-     * @param string $field_name
265
-     * @return mixed|null
266
-     * @throws \EE_Error
267
-     */
268
-    public function get_original($field_name)
269
-    {
270
-        if (isset($this->_props_n_values_provided_in_constructor[$field_name])
271
-            && $field_settings = $this->get_model()->field_settings_for($field_name)
272
-        ) {
273
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[$field_name]);
274
-        } else {
275
-            return null;
276
-        }
277
-    }
278
-
279
-
280
-
281
-    /**
282
-     * @param EE_Base_Class $obj
283
-     * @return string
284
-     */
285
-    public function get_class($obj)
286
-    {
287
-        return get_class($obj);
288
-    }
289
-
290
-
291
-
292
-    /**
293
-     * Overrides parent because parent expects old models.
294
-     * This also doesn't do any validation, and won't work for serialized arrays
295
-     *
296
-     * @param    string $field_name
297
-     * @param    mixed  $field_value
298
-     * @param bool      $use_default
299
-     * @throws \EE_Error
300
-     */
301
-    public function set($field_name, $field_value, $use_default = false)
302
-    {
303
-        // if not using default and nothing has changed, and object has already been setup (has ID),
304
-        // then don't do anything
305
-        if (
306
-            ! $use_default
307
-            && $this->_fields[$field_name] === $field_value
308
-            && $this->ID()
309
-        ) {
310
-            return;
311
-        }
312
-        $this->_has_changes = true;
313
-        $field_obj = $this->get_model()->field_settings_for($field_name);
314
-        if ($field_obj instanceof EE_Model_Field_Base) {
315
-            //			if ( method_exists( $field_obj, 'set_timezone' )) {
316
-            if ($field_obj instanceof EE_Datetime_Field) {
317
-                $field_obj->set_timezone($this->_timezone);
318
-                $field_obj->set_date_format($this->_dt_frmt);
319
-                $field_obj->set_time_format($this->_tm_frmt);
320
-            }
321
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
322
-            //should the value be null?
323
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
324
-                $this->_fields[$field_name] = $field_obj->get_default_value();
325
-                /**
326
-                 * To save having to refactor all the models, if a default value is used for a
327
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
328
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
329
-                 * object.
330
-                 *
331
-                 * @since 4.6.10+
332
-                 */
333
-                if (
334
-                    $field_obj instanceof EE_Datetime_Field
335
-                    && $this->_fields[$field_name] !== null
336
-                    && ! $this->_fields[$field_name] instanceof DateTime
337
-                ) {
338
-                    empty($this->_fields[$field_name])
339
-                        ? $this->set($field_name, time())
340
-                        : $this->set($field_name, $this->_fields[$field_name]);
341
-                }
342
-            } else {
343
-                $this->_fields[$field_name] = $holder_of_value;
344
-            }
345
-            //if we're not in the constructor...
346
-            //now check if what we set was a primary key
347
-            if (
348
-                //note: props_n_values_provided_in_constructor is only set at the END of the constructor
349
-                $this->_props_n_values_provided_in_constructor
350
-                && $field_value
351
-                && $field_name === self::_get_primary_key_name(get_class($this))
352
-            ) {
353
-                //if so, we want all this object's fields to be filled either with
354
-                //what we've explicitly set on this model
355
-                //or what we have in the db
356
-                // echo "setting primary key!";
357
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
358
-                $obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
359
-                foreach ($fields_on_model as $field_obj) {
360
-                    if ( ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
361
-                         && $field_obj->get_name() !== $field_name
362
-                    ) {
363
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
364
-                    }
365
-                }
366
-                //oh this model object has an ID? well make sure its in the entity mapper
367
-                $this->get_model()->add_to_entity_map($this);
368
-            }
369
-            //let's unset any cache for this field_name from the $_cached_properties property.
370
-            $this->_clear_cached_property($field_name);
371
-        } else {
372
-            throw new EE_Error(sprintf(__("A valid EE_Model_Field_Base could not be found for the given field name: %s",
373
-                "event_espresso"), $field_name));
374
-        }
375
-    }
376
-
377
-
378
-
379
-    /**
380
-     * This sets the field value on the db column if it exists for the given $column_name or
381
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
382
-     *
383
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
384
-     * @param string $field_name  Must be the exact column name.
385
-     * @param mixed  $field_value The value to set.
386
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
387
-     * @throws \EE_Error
388
-     */
389
-    public function set_field_or_extra_meta($field_name, $field_value)
390
-    {
391
-        if ($this->get_model()->has_field($field_name)) {
392
-            $this->set($field_name, $field_value);
393
-            return true;
394
-        } else {
395
-            //ensure this object is saved first so that extra meta can be properly related.
396
-            $this->save();
397
-            return $this->update_extra_meta($field_name, $field_value);
398
-        }
399
-    }
400
-
401
-
402
-
403
-    /**
404
-     * This retrieves the value of the db column set on this class or if that's not present
405
-     * it will attempt to retrieve from extra_meta if found.
406
-     * Example Usage:
407
-     * Via EE_Message child class:
408
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
409
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
410
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
411
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
412
-     * value for those extra fields dynamically via the EE_message object.
413
-     *
414
-     * @param  string $field_name expecting the fully qualified field name.
415
-     * @return mixed|null  value for the field if found.  null if not found.
416
-     * @throws \EE_Error
417
-     */
418
-    public function get_field_or_extra_meta($field_name)
419
-    {
420
-        if ($this->get_model()->has_field($field_name)) {
421
-            $column_value = $this->get($field_name);
422
-        } else {
423
-            //This isn't a column in the main table, let's see if it is in the extra meta.
424
-            $column_value = $this->get_extra_meta($field_name, true, null);
425
-        }
426
-        return $column_value;
427
-    }
428
-
429
-
430
-
431
-    /**
432
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
433
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
434
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
435
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
436
-     *
437
-     * @access public
438
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
439
-     * @return void
440
-     * @throws \EE_Error
441
-     */
442
-    public function set_timezone($timezone = '')
443
-    {
444
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
445
-        //make sure we clear all cached properties because they won't be relevant now
446
-        $this->_clear_cached_properties();
447
-        //make sure we update field settings and the date for all EE_Datetime_Fields
448
-        $model_fields = $this->get_model()->field_settings(false);
449
-        foreach ($model_fields as $field_name => $field_obj) {
450
-            if ($field_obj instanceof EE_Datetime_Field) {
451
-                $field_obj->set_timezone($this->_timezone);
452
-                if (isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime) {
453
-                    $this->_fields[$field_name]->setTimezone(new DateTimeZone($this->_timezone));
454
-                }
455
-            }
456
-        }
457
-    }
458
-
459
-
460
-
461
-    /**
462
-     * This just returns whatever is set for the current timezone.
463
-     *
464
-     * @access public
465
-     * @return string timezone string
466
-     */
467
-    public function get_timezone()
468
-    {
469
-        return $this->_timezone;
470
-    }
471
-
472
-
473
-
474
-    /**
475
-     * This sets the internal date format to what is sent in to be used as the new default for the class
476
-     * internally instead of wp set date format options
477
-     *
478
-     * @since 4.6
479
-     * @param string $format should be a format recognizable by PHP date() functions.
480
-     */
481
-    public function set_date_format($format)
482
-    {
483
-        $this->_dt_frmt = $format;
484
-        //clear cached_properties because they won't be relevant now.
485
-        $this->_clear_cached_properties();
486
-    }
487
-
488
-
489
-
490
-    /**
491
-     * This sets the internal time format string to what is sent in to be used as the new default for the
492
-     * class internally instead of wp set time format options.
493
-     *
494
-     * @since 4.6
495
-     * @param string $format should be a format recognizable by PHP date() functions.
496
-     */
497
-    public function set_time_format($format)
498
-    {
499
-        $this->_tm_frmt = $format;
500
-        //clear cached_properties because they won't be relevant now.
501
-        $this->_clear_cached_properties();
502
-    }
503
-
504
-
505
-
506
-    /**
507
-     * This returns the current internal set format for the date and time formats.
508
-     *
509
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
510
-     *                             where the first value is the date format and the second value is the time format.
511
-     * @return mixed string|array
512
-     */
513
-    public function get_format($full = true)
514
-    {
515
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
516
-    }
517
-
518
-
519
-
520
-    /**
521
-     * cache
522
-     * stores the passed model object on the current model object.
523
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
524
-     *
525
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
526
-     *                                       'Registration' associated with this model object
527
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
528
-     *                                       that could be a payment or a registration)
529
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
530
-     *                                       items which will be stored in an array on this object
531
-     * @throws EE_Error
532
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
533
-     *                  related thing, no array)
534
-     */
535
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
536
-    {
537
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
538
-        if ( ! $object_to_cache instanceof EE_Base_Class) {
539
-            return false;
540
-        }
541
-        // also get "how" the object is related, or throw an error
542
-        if ( ! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
543
-            throw new EE_Error(sprintf(__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
544
-                $relationName, get_class($this)));
545
-        }
546
-        // how many things are related ?
547
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
548
-            // if it's a "belongs to" relationship, then there's only one related model object  eg, if this is a registration, there's only 1 attendee for it
549
-            // so for these model objects just set it to be cached
550
-            $this->_model_relations[$relationName] = $object_to_cache;
551
-            $return = true;
552
-        } else {
553
-            // otherwise, this is the "many" side of a one to many relationship, so we'll add the object to the array of related objects for that type.
554
-            // eg: if this is an event, there are many registrations for that event, so we cache the registrations in an array
555
-            if ( ! is_array($this->_model_relations[$relationName])) {
556
-                // if for some reason, the cached item is a model object, then stick that in the array, otherwise start with an empty array
557
-                $this->_model_relations[$relationName] = $this->_model_relations[$relationName] instanceof EE_Base_Class
558
-                    ? array($this->_model_relations[$relationName]) : array();
559
-            }
560
-            // first check for a cache_id which is normally empty
561
-            if ( ! empty($cache_id)) {
562
-                // if the cache_id exists, then it means we are purposely trying to cache this with a known key that can then be used to retrieve the object later on
563
-                $this->_model_relations[$relationName][$cache_id] = $object_to_cache;
564
-                $return = $cache_id;
565
-            } elseif ($object_to_cache->ID()) {
566
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
567
-                $this->_model_relations[$relationName][$object_to_cache->ID()] = $object_to_cache;
568
-                $return = $object_to_cache->ID();
569
-            } else {
570
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
571
-                $this->_model_relations[$relationName][] = $object_to_cache;
572
-                // move the internal pointer to the end of the array
573
-                end($this->_model_relations[$relationName]);
574
-                // and grab the key so that we can return it
575
-                $return = key($this->_model_relations[$relationName]);
576
-            }
577
-        }
578
-        return $return;
579
-    }
580
-
581
-
582
-
583
-    /**
584
-     * For adding an item to the cached_properties property.
585
-     *
586
-     * @access protected
587
-     * @param string      $fieldname the property item the corresponding value is for.
588
-     * @param mixed       $value     The value we are caching.
589
-     * @param string|null $cache_type
590
-     * @return void
591
-     * @throws \EE_Error
592
-     */
593
-    protected function _set_cached_property($fieldname, $value, $cache_type = null)
594
-    {
595
-        //first make sure this property exists
596
-        $this->get_model()->field_settings_for($fieldname);
597
-        $cache_type = empty($cache_type) ? 'standard' : $cache_type;
598
-        $this->_cached_properties[$fieldname][$cache_type] = $value;
599
-    }
600
-
601
-
602
-
603
-    /**
604
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
605
-     * This also SETS the cache if we return the actual property!
606
-     *
607
-     * @param string $fieldname        the name of the property we're trying to retrieve
608
-     * @param bool   $pretty
609
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
610
-     *                                 (in cases where the same property may be used for different outputs
611
-     *                                 - i.e. datetime, money etc.)
612
-     *                                 It can also accept certain pre-defined "schema" strings
613
-     *                                 to define how to output the property.
614
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
615
-     * @return mixed                   whatever the value for the property is we're retrieving
616
-     * @throws \EE_Error
617
-     */
618
-    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
619
-    {
620
-        //verify the field exists
621
-        $this->get_model()->field_settings_for($fieldname);
622
-        $cache_type = $pretty ? 'pretty' : 'standard';
623
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
624
-        if (isset($this->_cached_properties[$fieldname][$cache_type])) {
625
-            return $this->_cached_properties[$fieldname][$cache_type];
626
-        }
627
-        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
628
-        $this->_set_cached_property($fieldname, $value, $cache_type);
629
-        return $value;
630
-    }
631
-
632
-
633
-
634
-    /**
635
-     * If the cache didn't fetch the needed item, this fetches it.
636
-     * @param string $fieldname
637
-     * @param bool $pretty
638
-     * @param string $extra_cache_ref
639
-     * @return mixed
640
-     */
641
-    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
642
-    {
643
-        $field_obj = $this->get_model()->field_settings_for($fieldname);
644
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
645
-        if ($field_obj instanceof EE_Datetime_Field) {
646
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
647
-        }
648
-        if ( ! isset($this->_fields[$fieldname])) {
649
-            $this->_fields[$fieldname] = null;
650
-        }
651
-        $value = $pretty
652
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[$fieldname], $extra_cache_ref)
653
-            : $field_obj->prepare_for_get($this->_fields[$fieldname]);
654
-        return $value;
655
-    }
656
-
657
-
658
-
659
-    /**
660
-     * set timezone, formats, and output for EE_Datetime_Field objects
661
-     *
662
-     * @param \EE_Datetime_Field $datetime_field
663
-     * @param bool               $pretty
664
-     * @param null $date_or_time
665
-     * @return void
666
-     * @throws \EE_Error
667
-     */
668
-    protected function _prepare_datetime_field(
669
-        EE_Datetime_Field $datetime_field,
670
-        $pretty = false,
671
-        $date_or_time = null
672
-    ) {
673
-        $datetime_field->set_timezone($this->_timezone);
674
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
675
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
676
-        //set the output returned
677
-        switch ($date_or_time) {
678
-            case 'D' :
679
-                $datetime_field->set_date_time_output('date');
680
-                break;
681
-            case 'T' :
682
-                $datetime_field->set_date_time_output('time');
683
-                break;
684
-            default :
685
-                $datetime_field->set_date_time_output();
686
-        }
687
-    }
688
-
689
-
690
-
691
-    /**
692
-     * This just takes care of clearing out the cached_properties
693
-     *
694
-     * @return void
695
-     */
696
-    protected function _clear_cached_properties()
697
-    {
698
-        $this->_cached_properties = array();
699
-    }
700
-
701
-
702
-
703
-    /**
704
-     * This just clears out ONE property if it exists in the cache
705
-     *
706
-     * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
707
-     * @return void
708
-     */
709
-    protected function _clear_cached_property($property_name)
710
-    {
711
-        if (isset($this->_cached_properties[$property_name])) {
712
-            unset($this->_cached_properties[$property_name]);
713
-        }
714
-    }
715
-
716
-
717
-
718
-    /**
719
-     * Ensures that this related thing is a model object.
720
-     *
721
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
722
-     * @param string $model_name   name of the related thing, eg 'Attendee',
723
-     * @return EE_Base_Class
724
-     * @throws \EE_Error
725
-     */
726
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
727
-    {
728
-        $other_model_instance = self::_get_model_instance_with_name(
729
-            self::_get_model_classname($model_name),
730
-            $this->_timezone
731
-        );
732
-        return $other_model_instance->ensure_is_obj($object_or_id);
733
-    }
734
-
735
-
736
-
737
-    /**
738
-     * Forgets the cached model of the given relation Name. So the next time we request it,
739
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
740
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
741
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
742
-     *
743
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
744
-     *                                                     Eg 'Registration'
745
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
746
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
747
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
748
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
749
-     *                                                     this is HasMany or HABTM.
750
-     * @throws EE_Error
751
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
752
-     *                       relation from all
753
-     */
754
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
755
-    {
756
-        $relationship_to_model = $this->get_model()->related_settings_for($relationName);
757
-        $index_in_cache = '';
758
-        if ( ! $relationship_to_model) {
759
-            throw new EE_Error(
760
-                sprintf(
761
-                    __("There is no relationship to %s on a %s. Cannot clear that cache", 'event_espresso'),
762
-                    $relationName,
763
-                    get_class($this)
764
-                )
765
-            );
766
-        }
767
-        if ($clear_all) {
768
-            $obj_removed = true;
769
-            $this->_model_relations[$relationName] = null;
770
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
771
-            $obj_removed = $this->_model_relations[$relationName];
772
-            $this->_model_relations[$relationName] = null;
773
-        } else {
774
-            if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
775
-                && $object_to_remove_or_index_into_array->ID()
776
-            ) {
777
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
778
-                if (is_array($this->_model_relations[$relationName])
779
-                    && ! isset($this->_model_relations[$relationName][$index_in_cache])
780
-                ) {
781
-                    $index_found_at = null;
782
-                    //find this object in the array even though it has a different key
783
-                    foreach ($this->_model_relations[$relationName] as $index => $obj) {
784
-                        if (
785
-                            $obj instanceof EE_Base_Class
786
-                            && (
787
-                                $obj == $object_to_remove_or_index_into_array
788
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
789
-                            )
790
-                        ) {
791
-                            $index_found_at = $index;
792
-                            break;
793
-                        }
794
-                    }
795
-                    if ($index_found_at) {
796
-                        $index_in_cache = $index_found_at;
797
-                    } else {
798
-                        //it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
799
-                        //if it wasn't in it to begin with. So we're done
800
-                        return $object_to_remove_or_index_into_array;
801
-                    }
802
-                }
803
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
804
-                //so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
805
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
806
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
807
-                        $index_in_cache = $index;
808
-                    }
809
-                }
810
-            } else {
811
-                $index_in_cache = $object_to_remove_or_index_into_array;
812
-            }
813
-            //supposedly we've found it. But it could just be that the client code
814
-            //provided a bad index/object
815
-            if (
816
-            isset(
817
-                $this->_model_relations[$relationName],
818
-                $this->_model_relations[$relationName][$index_in_cache]
819
-            )
820
-            ) {
821
-                $obj_removed = $this->_model_relations[$relationName][$index_in_cache];
822
-                unset($this->_model_relations[$relationName][$index_in_cache]);
823
-            } else {
824
-                //that thing was never cached anyways.
825
-                $obj_removed = null;
826
-            }
827
-        }
828
-        return $obj_removed;
829
-    }
830
-
831
-
832
-
833
-    /**
834
-     * update_cache_after_object_save
835
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
836
-     * obtained after being saved to the db
837
-     *
838
-     * @param string         $relationName       - the type of object that is cached
839
-     * @param \EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
840
-     * @param string         $current_cache_id   - the ID that was used when originally caching the object
841
-     * @return boolean TRUE on success, FALSE on fail
842
-     * @throws \EE_Error
843
-     */
844
-    public function update_cache_after_object_save(
845
-        $relationName,
846
-        EE_Base_Class $newly_saved_object,
847
-        $current_cache_id = ''
848
-    ) {
849
-        // verify that incoming object is of the correct type
850
-        $obj_class = 'EE_' . $relationName;
851
-        if ($newly_saved_object instanceof $obj_class) {
852
-            /* @type EE_Base_Class $newly_saved_object */
853
-            // now get the type of relation
854
-            $relationship_to_model = $this->get_model()->related_settings_for($relationName);
855
-            // if this is a 1:1 relationship
856
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
857
-                // then just replace the cached object with the newly saved object
858
-                $this->_model_relations[$relationName] = $newly_saved_object;
859
-                return true;
860
-                // or if it's some kind of sordid feral polyamorous relationship...
861
-            } elseif (is_array($this->_model_relations[$relationName])
862
-                      && isset($this->_model_relations[$relationName][$current_cache_id])
863
-            ) {
864
-                // then remove the current cached item
865
-                unset($this->_model_relations[$relationName][$current_cache_id]);
866
-                // and cache the newly saved object using it's new ID
867
-                $this->_model_relations[$relationName][$newly_saved_object->ID()] = $newly_saved_object;
868
-                return true;
869
-            }
870
-        }
871
-        return false;
872
-    }
873
-
874
-
875
-
876
-    /**
877
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
878
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
879
-     *
880
-     * @param string $relationName
881
-     * @return EE_Base_Class
882
-     */
883
-    public function get_one_from_cache($relationName)
884
-    {
885
-        $cached_array_or_object = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName]
886
-            : null;
887
-        if (is_array($cached_array_or_object)) {
888
-            return array_shift($cached_array_or_object);
889
-        } else {
890
-            return $cached_array_or_object;
891
-        }
892
-    }
893
-
894
-
895
-
896
-    /**
897
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
898
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
899
-     *
900
-     * @param string $relationName
901
-     * @throws \EE_Error
902
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
903
-     */
904
-    public function get_all_from_cache($relationName)
905
-    {
906
-        $objects = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName] : array();
907
-        // if the result is not an array, but exists, make it an array
908
-        $objects = is_array($objects) ? $objects : array($objects);
909
-        //bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
910
-        //basically, if this model object was stored in the session, and these cached model objects
911
-        //already have IDs, let's make sure they're in their model's entity mapper
912
-        //otherwise we will have duplicates next time we call
913
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
914
-        $model = EE_Registry::instance()->load_model($relationName);
915
-        foreach ($objects as $model_object) {
916
-            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
917
-                //ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
918
-                if ($model_object->ID()) {
919
-                    $model->add_to_entity_map($model_object);
920
-                }
921
-            } else {
922
-                throw new EE_Error(
923
-                    sprintf(
924
-                        __(
925
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
926
-                            'event_espresso'
927
-                        ),
928
-                        $relationName,
929
-                        gettype($model_object)
930
-                    )
931
-                );
932
-            }
933
-        }
934
-        return $objects;
935
-    }
936
-
937
-
938
-
939
-    /**
940
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
941
-     * matching the given query conditions.
942
-     *
943
-     * @param null  $field_to_order_by  What field is being used as the reference point.
944
-     * @param int   $limit              How many objects to return.
945
-     * @param array $query_params       Any additional conditions on the query.
946
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
947
-     *                                  you can indicate just the columns you want returned
948
-     * @return array|EE_Base_Class[]
949
-     * @throws \EE_Error
950
-     */
951
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
952
-    {
953
-        $field = empty($field_to_order_by) && $this->get_model()->has_primary_key_field()
954
-            ? $this->get_model()->get_primary_key_field()->get_name()
955
-            : $field_to_order_by;
956
-        $current_value = ! empty($field) ? $this->get($field) : null;
957
-        if (empty($field) || empty($current_value)) {
958
-            return array();
959
-        }
960
-        return $this->get_model()->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
961
-    }
962
-
963
-
964
-
965
-    /**
966
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
967
-     * matching the given query conditions.
968
-     *
969
-     * @param null  $field_to_order_by  What field is being used as the reference point.
970
-     * @param int   $limit              How many objects to return.
971
-     * @param array $query_params       Any additional conditions on the query.
972
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
973
-     *                                  you can indicate just the columns you want returned
974
-     * @return array|EE_Base_Class[]
975
-     * @throws \EE_Error
976
-     */
977
-    public function previous_x(
978
-        $field_to_order_by = null,
979
-        $limit = 1,
980
-        $query_params = array(),
981
-        $columns_to_select = null
982
-    ) {
983
-        $field = empty($field_to_order_by) && $this->get_model()->has_primary_key_field()
984
-            ? $this->get_model()->get_primary_key_field()->get_name()
985
-            : $field_to_order_by;
986
-        $current_value = ! empty($field) ? $this->get($field) : null;
987
-        if (empty($field) || empty($current_value)) {
988
-            return array();
989
-        }
990
-        return $this->get_model()->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
991
-    }
992
-
993
-
994
-
995
-    /**
996
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
997
-     * matching the given query conditions.
998
-     *
999
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1000
-     * @param array $query_params       Any additional conditions on the query.
1001
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1002
-     *                                  you can indicate just the columns you want returned
1003
-     * @return array|EE_Base_Class
1004
-     * @throws \EE_Error
1005
-     */
1006
-    public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1007
-    {
1008
-        $field = empty($field_to_order_by) && $this->get_model()->has_primary_key_field()
1009
-            ? $this->get_model()->get_primary_key_field()->get_name()
1010
-            : $field_to_order_by;
1011
-        $current_value = ! empty($field) ? $this->get($field) : null;
1012
-        if (empty($field) || empty($current_value)) {
1013
-            return array();
1014
-        }
1015
-        return $this->get_model()->next($current_value, $field, $query_params, $columns_to_select);
1016
-    }
1017
-
1018
-
1019
-
1020
-    /**
1021
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1022
-     * matching the given query conditions.
1023
-     *
1024
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1025
-     * @param array $query_params       Any additional conditions on the query.
1026
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1027
-     *                                  you can indicate just the column you want returned
1028
-     * @return array|EE_Base_Class
1029
-     * @throws \EE_Error
1030
-     */
1031
-    public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1032
-    {
1033
-        $field = empty($field_to_order_by) && $this->get_model()->has_primary_key_field()
1034
-            ? $this->get_model()->get_primary_key_field()->get_name()
1035
-            : $field_to_order_by;
1036
-        $current_value = ! empty($field) ? $this->get($field) : null;
1037
-        if (empty($field) || empty($current_value)) {
1038
-            return array();
1039
-        }
1040
-        return $this->get_model()->previous($current_value, $field, $query_params, $columns_to_select);
1041
-    }
1042
-
1043
-
1044
-
1045
-    /**
1046
-     * Overrides parent because parent expects old models.
1047
-     * This also doesn't do any validation, and won't work for serialized arrays
1048
-     *
1049
-     * @param string $field_name
1050
-     * @param mixed  $field_value_from_db
1051
-     * @throws \EE_Error
1052
-     */
1053
-    public function set_from_db($field_name, $field_value_from_db)
1054
-    {
1055
-        $field_obj = $this->get_model()->field_settings_for($field_name);
1056
-        if ($field_obj instanceof EE_Model_Field_Base) {
1057
-            //you would think the DB has no NULLs for non-null label fields right? wrong!
1058
-            //eg, a CPT model object could have an entry in the posts table, but no
1059
-            //entry in the meta table. Meaning that all its columns in the meta table
1060
-            //are null! yikes! so when we find one like that, use defaults for its meta columns
1061
-            if ($field_value_from_db === null) {
1062
-                if ($field_obj->is_nullable()) {
1063
-                    //if the field allows nulls, then let it be null
1064
-                    $field_value = null;
1065
-                } else {
1066
-                    $field_value = $field_obj->get_default_value();
1067
-                }
1068
-            } else {
1069
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1070
-            }
1071
-            $this->_fields[$field_name] = $field_value;
1072
-            $this->_clear_cached_property($field_name);
1073
-        }
1074
-    }
1075
-
1076
-
1077
-
1078
-    /**
1079
-     * verifies that the specified field is of the correct type
1080
-     *
1081
-     * @param string $field_name
1082
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1083
-     *                                (in cases where the same property may be used for different outputs
1084
-     *                                - i.e. datetime, money etc.)
1085
-     * @return mixed
1086
-     * @throws \EE_Error
1087
-     */
1088
-    public function get($field_name, $extra_cache_ref = null)
1089
-    {
1090
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1091
-    }
1092
-
1093
-
1094
-
1095
-    /**
1096
-     * This method simply returns the RAW unprocessed value for the given property in this class
1097
-     *
1098
-     * @param  string $field_name A valid fieldname
1099
-     * @return mixed              Whatever the raw value stored on the property is.
1100
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1101
-     */
1102
-    public function get_raw($field_name)
1103
-    {
1104
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1105
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
1106
-            ? $this->_fields[$field_name]->format('U')
1107
-            : $this->_fields[$field_name];
1108
-    }
1109
-
1110
-
1111
-
1112
-    /**
1113
-     * This is used to return the internal DateTime object used for a field that is a
1114
-     * EE_Datetime_Field.
1115
-     *
1116
-     * @param string $field_name               The field name retrieving the DateTime object.
1117
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1118
-     * @throws \EE_Error
1119
-     *                                         an error is set and false returned.  If the field IS an
1120
-     *                                         EE_Datetime_Field and but the field value is null, then
1121
-     *                                         just null is returned (because that indicates that likely
1122
-     *                                         this field is nullable).
1123
-     */
1124
-    public function get_DateTime_object($field_name)
1125
-    {
1126
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1127
-        if ( ! $field_settings instanceof EE_Datetime_Field) {
1128
-            EE_Error::add_error(
1129
-                sprintf(
1130
-                    __(
1131
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1132
-                        'event_espresso'
1133
-                    ),
1134
-                    $field_name
1135
-                ),
1136
-                __FILE__,
1137
-                __FUNCTION__,
1138
-                __LINE__
1139
-            );
1140
-            return false;
1141
-        }
1142
-        return $this->_fields[$field_name];
1143
-    }
1144
-
1145
-
1146
-
1147
-    /**
1148
-     * To be used in template to immediately echo out the value, and format it for output.
1149
-     * Eg, should call stripslashes and whatnot before echoing
1150
-     *
1151
-     * @param string $field_name      the name of the field as it appears in the DB
1152
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1153
-     *                                (in cases where the same property may be used for different outputs
1154
-     *                                - i.e. datetime, money etc.)
1155
-     * @return void
1156
-     * @throws \EE_Error
1157
-     */
1158
-    public function e($field_name, $extra_cache_ref = null)
1159
-    {
1160
-        echo $this->get_pretty($field_name, $extra_cache_ref);
1161
-    }
1162
-
1163
-
1164
-
1165
-    /**
1166
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1167
-     * can be easily used as the value of form input.
1168
-     *
1169
-     * @param string $field_name
1170
-     * @return void
1171
-     * @throws \EE_Error
1172
-     */
1173
-    public function f($field_name)
1174
-    {
1175
-        $this->e($field_name, 'form_input');
1176
-    }
1177
-
1178
-
1179
-
1180
-    /**
1181
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1182
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1183
-     * to see what options are available.
1184
-     * @param string $field_name
1185
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1186
-     *                                (in cases where the same property may be used for different outputs
1187
-     *                                - i.e. datetime, money etc.)
1188
-     * @return mixed
1189
-     * @throws \EE_Error
1190
-     */
1191
-    public function get_pretty($field_name, $extra_cache_ref = null)
1192
-    {
1193
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1194
-    }
1195
-
1196
-
1197
-
1198
-    /**
1199
-     * This simply returns the datetime for the given field name
1200
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1201
-     * (and the equivalent e_date, e_time, e_datetime).
1202
-     *
1203
-     * @access   protected
1204
-     * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1205
-     * @param string   $dt_frmt      valid datetime format used for date
1206
-     *                               (if '' then we just use the default on the field,
1207
-     *                               if NULL we use the last-used format)
1208
-     * @param string   $tm_frmt      Same as above except this is for time format
1209
-     * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1210
-     * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1211
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1212
-     *                               if field is not a valid dtt field, or void if echoing
1213
-     * @throws \EE_Error
1214
-     */
1215
-    protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1216
-    {
1217
-        // clear cached property
1218
-        $this->_clear_cached_property($field_name);
1219
-        //reset format properties because they are used in get()
1220
-        $this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1221
-        $this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1222
-        if ($echo) {
1223
-            $this->e($field_name, $date_or_time);
1224
-            return '';
1225
-        }
1226
-        return $this->get($field_name, $date_or_time);
1227
-    }
1228
-
1229
-
1230
-
1231
-    /**
1232
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1233
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1234
-     * other echoes the pretty value for dtt)
1235
-     *
1236
-     * @param  string $field_name name of model object datetime field holding the value
1237
-     * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1238
-     * @return string            datetime value formatted
1239
-     * @throws \EE_Error
1240
-     */
1241
-    public function get_date($field_name, $format = '')
1242
-    {
1243
-        return $this->_get_datetime($field_name, $format, null, 'D');
1244
-    }
1245
-
1246
-
1247
-
1248
-    /**
1249
-     * @param      $field_name
1250
-     * @param string $format
1251
-     * @throws \EE_Error
1252
-     */
1253
-    public function e_date($field_name, $format = '')
1254
-    {
1255
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1256
-    }
1257
-
1258
-
1259
-
1260
-    /**
1261
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1262
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1263
-     * other echoes the pretty value for dtt)
1264
-     *
1265
-     * @param  string $field_name name of model object datetime field holding the value
1266
-     * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1267
-     * @return string             datetime value formatted
1268
-     * @throws \EE_Error
1269
-     */
1270
-    public function get_time($field_name, $format = '')
1271
-    {
1272
-        return $this->_get_datetime($field_name, null, $format, 'T');
1273
-    }
1274
-
1275
-
1276
-
1277
-    /**
1278
-     * @param      $field_name
1279
-     * @param string $format
1280
-     * @throws \EE_Error
1281
-     */
1282
-    public function e_time($field_name, $format = '')
1283
-    {
1284
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1285
-    }
1286
-
1287
-
1288
-
1289
-    /**
1290
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1291
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1292
-     * other echoes the pretty value for dtt)
1293
-     *
1294
-     * @param  string $field_name name of model object datetime field holding the value
1295
-     * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1296
-     * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1297
-     * @return string             datetime value formatted
1298
-     * @throws \EE_Error
1299
-     */
1300
-    public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1301
-    {
1302
-        return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1303
-    }
1304
-
1305
-
1306
-
1307
-    /**
1308
-     * @param string $field_name
1309
-     * @param string $dt_frmt
1310
-     * @param string $tm_frmt
1311
-     * @throws \EE_Error
1312
-     */
1313
-    public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1314
-    {
1315
-        $this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1316
-    }
1317
-
1318
-
1319
-
1320
-    /**
1321
-     * Get the i8ln value for a date using the WordPress @see date_i18n function.
1322
-     *
1323
-     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1324
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1325
-     *                           on the object will be used.
1326
-     * @return string Date and time string in set locale or false if no field exists for the given
1327
-     * @throws \EE_Error
1328
-     *                           field name.
1329
-     */
1330
-    public function get_i18n_datetime($field_name, $format = '')
1331
-    {
1332
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1333
-        return date_i18n(
1334
-            $format,
1335
-            EEH_DTT_Helper::get_timestamp_with_offset($this->get_raw($field_name), $this->_timezone)
1336
-        );
1337
-    }
1338
-
1339
-
1340
-
1341
-    /**
1342
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1343
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1344
-     * thrown.
1345
-     *
1346
-     * @param  string $field_name The field name being checked
1347
-     * @throws EE_Error
1348
-     * @return EE_Datetime_Field
1349
-     */
1350
-    protected function _get_dtt_field_settings($field_name)
1351
-    {
1352
-        $field = $this->get_model()->field_settings_for($field_name);
1353
-        //check if field is dtt
1354
-        if ($field instanceof EE_Datetime_Field) {
1355
-            return $field;
1356
-        } else {
1357
-            throw new EE_Error(sprintf(__('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',
1358
-                'event_espresso'), $field_name, self::_get_model_classname(get_class($this))));
1359
-        }
1360
-    }
1361
-
1362
-
1363
-
1364
-
1365
-    /**
1366
-     * NOTE ABOUT BELOW:
1367
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1368
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1369
-     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1370
-     * method and make sure you send the entire datetime value for setting.
1371
-     */
1372
-    /**
1373
-     * sets the time on a datetime property
1374
-     *
1375
-     * @access protected
1376
-     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1377
-     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1378
-     * @throws \EE_Error
1379
-     */
1380
-    protected function _set_time_for($time, $fieldname)
1381
-    {
1382
-        $this->_set_date_time('T', $time, $fieldname);
1383
-    }
1384
-
1385
-
1386
-
1387
-    /**
1388
-     * sets the date on a datetime property
1389
-     *
1390
-     * @access protected
1391
-     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1392
-     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1393
-     * @throws \EE_Error
1394
-     */
1395
-    protected function _set_date_for($date, $fieldname)
1396
-    {
1397
-        $this->_set_date_time('D', $date, $fieldname);
1398
-    }
1399
-
1400
-
1401
-
1402
-    /**
1403
-     * This takes care of setting a date or time independently on a given model object property. This method also
1404
-     * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1405
-     *
1406
-     * @access protected
1407
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1408
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1409
-     * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1410
-     *                                        EE_Datetime_Field property)
1411
-     * @throws \EE_Error
1412
-     */
1413
-    protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1414
-    {
1415
-        $field = $this->_get_dtt_field_settings($fieldname);
1416
-        $field->set_timezone($this->_timezone);
1417
-        $field->set_date_format($this->_dt_frmt);
1418
-        $field->set_time_format($this->_tm_frmt);
1419
-        switch ($what) {
1420
-            case 'T' :
1421
-                $this->_fields[$fieldname] = $field->prepare_for_set_with_new_time(
1422
-                    $datetime_value,
1423
-                    $this->_fields[$fieldname]
1424
-                );
1425
-                break;
1426
-            case 'D' :
1427
-                $this->_fields[$fieldname] = $field->prepare_for_set_with_new_date(
1428
-                    $datetime_value,
1429
-                    $this->_fields[$fieldname]
1430
-                );
1431
-                break;
1432
-            case 'B' :
1433
-                $this->_fields[$fieldname] = $field->prepare_for_set($datetime_value);
1434
-                break;
1435
-        }
1436
-        $this->_clear_cached_property($fieldname);
1437
-    }
1438
-
1439
-
1440
-
1441
-    /**
1442
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1443
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1444
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1445
-     * that could lead to some unexpected results!
1446
-     *
1447
-     * @access public
1448
-     * @param string               $field_name This is the name of the field on the object that contains the date/time
1449
-     *                                         value being returned.
1450
-     * @param string               $callback   must match a valid method in this class (defaults to get_datetime)
1451
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1452
-     * @param string               $prepend    You can include something to prepend on the timestamp
1453
-     * @param string               $append     You can include something to append on the timestamp
1454
-     * @throws EE_Error
1455
-     * @return string timestamp
1456
-     */
1457
-    public function display_in_my_timezone(
1458
-        $field_name,
1459
-        $callback = 'get_datetime',
1460
-        $args = null,
1461
-        $prepend = '',
1462
-        $append = ''
1463
-    ) {
1464
-        $timezone = EEH_DTT_Helper::get_timezone();
1465
-        if ($timezone === $this->_timezone) {
1466
-            return '';
1467
-        }
1468
-        $original_timezone = $this->_timezone;
1469
-        $this->set_timezone($timezone);
1470
-        $fn = (array)$field_name;
1471
-        $args = array_merge($fn, (array)$args);
1472
-        if ( ! method_exists($this, $callback)) {
1473
-            throw new EE_Error(
1474
-                sprintf(
1475
-                    __(
1476
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1477
-                        'event_espresso'
1478
-                    ),
1479
-                    $callback
1480
-                )
1481
-            );
1482
-        }
1483
-        $args = (array)$args;
1484
-        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1485
-        $this->set_timezone($original_timezone);
1486
-        return $return;
1487
-    }
1488
-
1489
-
1490
-
1491
-    /**
1492
-     * Deletes this model object.
1493
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1494
-     * override
1495
-     * `EE_Base_Class::_delete` NOT this class.
1496
-     *
1497
-     * @return boolean | int
1498
-     * @throws \EE_Error
1499
-     */
1500
-    public function delete()
1501
-    {
1502
-        /**
1503
-         * Called just before the `EE_Base_Class::_delete` method call.
1504
-         * Note: `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1505
-         * should be aware that `_delete` may not always result in a permanent delete.  For example, `EE_Soft_Delete_Base_Class::_delete`
1506
-         * soft deletes (trash) the object and does not permanently delete it.
1507
-         *
1508
-         * @param EE_Base_Class $model_object about to be 'deleted'
1509
-         */
1510
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
1511
-        $result = $this->_delete();
1512
-        /**
1513
-         * Called just after the `EE_Base_Class::_delete` method call.
1514
-         * Note: `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1515
-         * should be aware that `_delete` may not always result in a permanent delete.  For example `EE_Soft_Base_Class::_delete`
1516
-         * soft deletes (trash) the object and does not permanently delete it.
1517
-         *
1518
-         * @param EE_Base_Class $model_object that was just 'deleted'
1519
-         * @param boolean       $result
1520
-         */
1521
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1522
-        return $result;
1523
-    }
1524
-
1525
-
1526
-
1527
-    /**
1528
-     * Calls the specific delete method for the instantiated class.
1529
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1530
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1531
-     * `EE_Base_Class::delete`
1532
-     *
1533
-     * @return bool|int
1534
-     * @throws \EE_Error
1535
-     */
1536
-    protected function _delete()
1537
-    {
1538
-        return $this->delete_permanently();
1539
-    }
1540
-
1541
-
1542
-
1543
-    /**
1544
-     * Deletes this model object permanently from db (but keep in mind related models my block the delete and return an
1545
-     * error)
1546
-     *
1547
-     * @return bool | int
1548
-     * @throws \EE_Error
1549
-     */
1550
-    public function delete_permanently()
1551
-    {
1552
-        /**
1553
-         * Called just before HARD deleting a model object
1554
-         *
1555
-         * @param EE_Base_Class $model_object about to be 'deleted'
1556
-         */
1557
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1558
-        $model = $this->get_model();
1559
-        $result = $model->delete_permanently_by_ID($this->ID());
1560
-        $this->refresh_cache_of_related_objects();
1561
-        /**
1562
-         * Called just after HARD deleting a model object
1563
-         *
1564
-         * @param EE_Base_Class $model_object that was just 'deleted'
1565
-         * @param boolean       $result
1566
-         */
1567
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1568
-        return $result;
1569
-    }
1570
-
1571
-
1572
-
1573
-    /**
1574
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1575
-     * related model objects
1576
-     *
1577
-     * @throws \EE_Error
1578
-     */
1579
-    public function refresh_cache_of_related_objects()
1580
-    {
1581
-        foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
1582
-            if ( ! empty($this->_model_relations[$relation_name])) {
1583
-                $related_objects = $this->_model_relations[$relation_name];
1584
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1585
-                    //this relation only stores a single model object, not an array
1586
-                    //but let's make it consistent
1587
-                    $related_objects = array($related_objects);
1588
-                }
1589
-                foreach ($related_objects as $related_object) {
1590
-                    //only refresh their cache if they're in memory
1591
-                    if ($related_object instanceof EE_Base_Class) {
1592
-                        $related_object->clear_cache($this->get_model()->get_this_model_name(), $this);
1593
-                    }
1594
-                }
1595
-            }
1596
-        }
1597
-    }
1598
-
1599
-
1600
-
1601
-    /**
1602
-     *        Saves this object to the database. An array may be supplied to set some values on this
1603
-     * object just before saving.
1604
-     *
1605
-     * @access public
1606
-     * @param array $set_cols_n_values keys are field names, values are their new values,
1607
-     *                                 if provided during the save() method (often client code will change the fields'
1608
-     *                                 values before calling save)
1609
-     * @throws \EE_Error
1610
-     * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1611
-     *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1612
-     */
1613
-    public function save($set_cols_n_values = array())
1614
-    {
1615
-        /**
1616
-         * Filters the fields we're about to save on the model object
1617
-         *
1618
-         * @param array         $set_cols_n_values
1619
-         * @param EE_Base_Class $model_object
1620
-         */
1621
-        $set_cols_n_values = (array)apply_filters('FHEE__EE_Base_Class__save__set_cols_n_values', $set_cols_n_values,
1622
-            $this);
1623
-        //set attributes as provided in $set_cols_n_values
1624
-        foreach ($set_cols_n_values as $column => $value) {
1625
-            $this->set($column, $value);
1626
-        }
1627
-        // no changes ? then don't do anything
1628
-        if (! $this->_has_changes && $this->ID() && $this->get_model()->get_primary_key_field()->is_auto_increment()) {
1629
-            return 0;
1630
-        }
1631
-        /**
1632
-         * Saving a model object.
1633
-         * Before we perform a save, this action is fired.
1634
-         *
1635
-         * @param EE_Base_Class $model_object the model object about to be saved.
1636
-         */
1637
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
1638
-        if ( ! $this->allow_persist()) {
1639
-            return 0;
1640
-        }
1641
-        //now get current attribute values
1642
-        $save_cols_n_values = $this->_fields;
1643
-        //if the object already has an ID, update it. Otherwise, insert it
1644
-        //also: change the assumption about values passed to the model NOT being prepare dby the model object. They have been
1645
-        $old_assumption_concerning_value_preparation = $this->get_model()
1646
-                                                            ->get_assumption_concerning_values_already_prepared_by_model_object();
1647
-        $this->get_model()->assume_values_already_prepared_by_model_object(true);
1648
-        //does this model have an autoincrement PK?
1649
-        if ($this->get_model()->has_primary_key_field()) {
1650
-            if ($this->get_model()->get_primary_key_field()->is_auto_increment()) {
1651
-                //ok check if it's set, if so: update; if not, insert
1652
-                if ( ! empty($save_cols_n_values[self::_get_primary_key_name(get_class($this))])) {
1653
-                    $results = $this->get_model()->update_by_ID($save_cols_n_values, $this->ID());
1654
-                } else {
1655
-                    unset($save_cols_n_values[self::_get_primary_key_name(get_class($this))]);
1656
-                    $results = $this->get_model()->insert($save_cols_n_values);
1657
-                    if ($results) {
1658
-                        //if successful, set the primary key
1659
-                        //but don't use the normal SET method, because it will check if
1660
-                        //an item with the same ID exists in the mapper & db, then
1661
-                        //will find it in the db (because we just added it) and THAT object
1662
-                        //will get added to the mapper before we can add this one!
1663
-                        //but if we just avoid using the SET method, all that headache can be avoided
1664
-                        $pk_field_name = self::_get_primary_key_name(get_class($this));
1665
-                        $this->_fields[$pk_field_name] = $results;
1666
-                        $this->_clear_cached_property($pk_field_name);
1667
-                        $this->get_model()->add_to_entity_map($this);
1668
-                        $this->_update_cached_related_model_objs_fks();
1669
-                    }
1670
-                }
1671
-            } else {//PK is NOT auto-increment
1672
-                //so check if one like it already exists in the db
1673
-                if ($this->get_model()->exists_by_ID($this->ID())) {
1674
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
1675
-                        throw new EE_Error(
1676
-                            sprintf(
1677
-                                __('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',
1678
-                                    'event_espresso'),
1679
-                                get_class($this),
1680
-                                get_class($this->get_model()) . '::instance()->add_to_entity_map()',
1681
-                                get_class($this->get_model()) . '::instance()->get_one_by_ID()',
1682
-                                '<br />'
1683
-                            )
1684
-                        );
1685
-                    }
1686
-                    $results = $this->get_model()->update_by_ID($save_cols_n_values, $this->ID());
1687
-                } else {
1688
-                    $results = $this->get_model()->insert($save_cols_n_values);
1689
-                    $this->_update_cached_related_model_objs_fks();
1690
-                }
1691
-            }
1692
-        } else {//there is NO primary key
1693
-            $already_in_db = false;
1694
-            foreach ($this->get_model()->unique_indexes() as $index) {
1695
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1696
-                if ($this->get_model()->exists(array($uniqueness_where_params))) {
1697
-                    $already_in_db = true;
1698
-                }
1699
-            }
1700
-            if ($already_in_db) {
1701
-                $combined_pk_fields_n_values = array_intersect_key($save_cols_n_values,
1702
-                    $this->get_model()->get_combined_primary_key_fields());
1703
-                $results = $this->get_model()->update($save_cols_n_values, $combined_pk_fields_n_values);
1704
-            } else {
1705
-                $results = $this->get_model()->insert($save_cols_n_values);
1706
-            }
1707
-        }
1708
-        //restore the old assumption about values being prepared by the model object
1709
-        $this->get_model()
1710
-             ->assume_values_already_prepared_by_model_object($old_assumption_concerning_value_preparation);
1711
-        /**
1712
-         * After saving the model object this action is called
1713
-         *
1714
-         * @param EE_Base_Class $model_object which was just saved
1715
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1716
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1717
-         */
1718
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1719
-        $this->_has_changes = false;
1720
-        return $results;
1721
-    }
1722
-
1723
-
1724
-
1725
-    /**
1726
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1727
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1728
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1729
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1730
-     * 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
1731
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1732
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1733
-     *
1734
-     * @return void
1735
-     * @throws \EE_Error
1736
-     */
1737
-    protected function _update_cached_related_model_objs_fks()
1738
-    {
1739
-        foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
1740
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1741
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1742
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1743
-                        $this->get_model()->get_this_model_name()
1744
-                    );
1745
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1746
-                    if ($related_model_obj_in_cache->ID()) {
1747
-                        $related_model_obj_in_cache->save();
1748
-                    }
1749
-                }
1750
-            }
1751
-        }
1752
-    }
1753
-
1754
-
1755
-
1756
-    /**
1757
-     * Saves this model object and its NEW cached relations to the database.
1758
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1759
-     * In order for that to work, we would need to mark model objects as dirty/clean...
1760
-     * because otherwise, there's a potential for infinite looping of saving
1761
-     * Saves the cached related model objects, and ensures the relation between them
1762
-     * and this object and properly setup
1763
-     *
1764
-     * @return int ID of new model object on save; 0 on failure+
1765
-     * @throws \EE_Error
1766
-     */
1767
-    public function save_new_cached_related_model_objs()
1768
-    {
1769
-        //make sure this has been saved
1770
-        if ( ! $this->ID()) {
1771
-            $id = $this->save();
1772
-        } else {
1773
-            $id = $this->ID();
1774
-        }
1775
-        //now save all the NEW cached model objects  (ie they don't exist in the DB)
1776
-        foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1777
-            if ($this->_model_relations[$relationName]) {
1778
-                //is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1779
-                //or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1780
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
1781
-                    //add a relation to that relation type (which saves the appropriate thing in the process)
1782
-                    //but ONLY if it DOES NOT exist in the DB
1783
-                    /* @var $related_model_obj EE_Base_Class */
1784
-                    $related_model_obj = $this->_model_relations[$relationName];
1785
-                    //					if( ! $related_model_obj->ID()){
1786
-                    $this->_add_relation_to($related_model_obj, $relationName);
1787
-                    $related_model_obj->save_new_cached_related_model_objs();
1788
-                    //					}
1789
-                } else {
1790
-                    foreach ($this->_model_relations[$relationName] as $related_model_obj) {
1791
-                        //add a relation to that relation type (which saves the appropriate thing in the process)
1792
-                        //but ONLY if it DOES NOT exist in the DB
1793
-                        //						if( ! $related_model_obj->ID()){
1794
-                        $this->_add_relation_to($related_model_obj, $relationName);
1795
-                        $related_model_obj->save_new_cached_related_model_objs();
1796
-                        //						}
1797
-                    }
1798
-                }
1799
-            }
1800
-        }
1801
-        return $id;
1802
-    }
1803
-
1804
-
1805
-
1806
-    /**
1807
-     * for getting a model while instantiated.
1808
-     *
1809
-     * @return \EEM_Base | \EEM_CPT_Base
1810
-     */
1811
-    public function get_model()
1812
-    {
1813
-        $modelName = self::_get_model_classname(get_class($this));
1814
-        return self::_get_model_instance_with_name($modelName, $this->_timezone);
1815
-    }
1816
-
1817
-
1818
-
1819
-    /**
1820
-     * @param $props_n_values
1821
-     * @param $classname
1822
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
1823
-     * @throws \EE_Error
1824
-     */
1825
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
1826
-    {
1827
-        //TODO: will not work for Term_Relationships because they have no PK!
1828
-        $primary_id_ref = self::_get_primary_key_name($classname);
1829
-        if (array_key_exists($primary_id_ref, $props_n_values) && ! empty($props_n_values[$primary_id_ref])) {
1830
-            $id = $props_n_values[$primary_id_ref];
1831
-            return self::_get_model($classname)->get_from_entity_map($id);
1832
-        }
1833
-        return false;
1834
-    }
1835
-
1836
-
1837
-
1838
-    /**
1839
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
1840
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
1841
-     * 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
1842
-     * we return false.
1843
-     *
1844
-     * @param  array  $props_n_values   incoming array of properties and their values
1845
-     * @param  string $classname        the classname of the child class
1846
-     * @param null    $timezone
1847
-     * @param array   $date_formats     incoming date_formats in an array where the first value is the
1848
-     *                                  date_format and the second value is the time format
1849
-     * @return mixed (EE_Base_Class|bool)
1850
-     * @throws \EE_Error
1851
-     */
1852
-    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
1853
-    {
1854
-        $existing = null;
1855
-        if (self::_get_model($classname)->has_primary_key_field()) {
1856
-            $primary_id_ref = self::_get_primary_key_name($classname);
1857
-            if (array_key_exists($primary_id_ref, $props_n_values)
1858
-                && ! empty($props_n_values[$primary_id_ref])
1859
-            ) {
1860
-                $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
1861
-                    $props_n_values[$primary_id_ref]
1862
-                );
1863
-            }
1864
-        } elseif (self::_get_model($classname, $timezone)->has_all_combined_primary_key_fields($props_n_values)) {
1865
-            //no primary key on this model, but there's still a matching item in the DB
1866
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
1867
-                self::_get_model($classname, $timezone)->get_index_primary_key_string($props_n_values)
1868
-            );
1869
-        }
1870
-        if ($existing) {
1871
-            //set date formats if present before setting values
1872
-            if ( ! empty($date_formats) && is_array($date_formats)) {
1873
-                $existing->set_date_format($date_formats[0]);
1874
-                $existing->set_time_format($date_formats[1]);
1875
-            } else {
1876
-                //set default formats for date and time
1877
-                $existing->set_date_format(get_option('date_format'));
1878
-                $existing->set_time_format(get_option('time_format'));
1879
-            }
1880
-            foreach ($props_n_values as $property => $field_value) {
1881
-                $existing->set($property, $field_value);
1882
-            }
1883
-            return $existing;
1884
-        } else {
1885
-            return false;
1886
-        }
1887
-    }
1888
-
1889
-
1890
-
1891
-    /**
1892
-     * Gets the EEM_*_Model for this class
1893
-     *
1894
-     * @access public now, as this is more convenient
1895
-     * @param      $classname
1896
-     * @param null $timezone
1897
-     * @throws EE_Error
1898
-     * @return EEM_Base
1899
-     */
1900
-    protected static function _get_model($classname, $timezone = null)
1901
-    {
1902
-        //find model for this class
1903
-        if ( ! $classname) {
1904
-            throw new EE_Error(
1905
-                sprintf(
1906
-                    __(
1907
-                        "What were you thinking calling _get_model(%s)?? You need to specify the class name",
1908
-                        "event_espresso"
1909
-                    ),
1910
-                    $classname
1911
-                )
1912
-            );
1913
-        }
1914
-        $modelName = self::_get_model_classname($classname);
1915
-        return self::_get_model_instance_with_name($modelName, $timezone);
1916
-    }
1917
-
1918
-
1919
-
1920
-    /**
1921
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
1922
-     *
1923
-     * @param string $model_classname
1924
-     * @param null   $timezone
1925
-     * @return EEM_Base
1926
-     */
1927
-    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
1928
-    {
1929
-        $model_classname = str_replace('EEM_', '', $model_classname);
1930
-        $model = EE_Registry::instance()->load_model($model_classname);
1931
-        $model->set_timezone($timezone);
1932
-        return $model;
1933
-    }
1934
-
1935
-
1936
-
1937
-    /**
1938
-     * If a model name is provided (eg Registration), gets the model classname for that model.
1939
-     * Also works if a model class's classname is provided (eg EE_Registration).
1940
-     *
1941
-     * @param null $model_name
1942
-     * @return string like EEM_Attendee
1943
-     */
1944
-    private static function _get_model_classname($model_name = null)
1945
-    {
1946
-        if (strpos($model_name, "EE_") === 0) {
1947
-            $model_classname = str_replace("EE_", "EEM_", $model_name);
1948
-        } else {
1949
-            $model_classname = "EEM_" . $model_name;
1950
-        }
1951
-        return $model_classname;
1952
-    }
1953
-
1954
-
1955
-
1956
-    /**
1957
-     * returns the name of the primary key attribute
1958
-     *
1959
-     * @param null $classname
1960
-     * @throws EE_Error
1961
-     * @return string
1962
-     */
1963
-    protected static function _get_primary_key_name($classname = null)
1964
-    {
1965
-        if ( ! $classname) {
1966
-            throw new EE_Error(
1967
-                sprintf(
1968
-                    __("What were you thinking calling _get_primary_key_name(%s)", "event_espresso"),
1969
-                    $classname
1970
-                )
1971
-            );
1972
-        }
1973
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
1974
-    }
1975
-
1976
-
1977
-
1978
-    /**
1979
-     * Gets the value of the primary key.
1980
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
1981
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
1982
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
1983
-     *
1984
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
1985
-     * @throws \EE_Error
1986
-     */
1987
-    public function ID()
1988
-    {
1989
-        //now that we know the name of the variable, use a variable variable to get its value and return its
1990
-        if ($this->get_model()->has_primary_key_field()) {
1991
-            return $this->_fields[self::_get_primary_key_name(get_class($this))];
1992
-        } else {
1993
-            return $this->get_model()->get_index_primary_key_string($this->_fields);
1994
-        }
1995
-    }
1996
-
1997
-
1998
-
1999
-    /**
2000
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2001
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2002
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2003
-     *
2004
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2005
-     * @param string $relationName                     eg 'Events','Question',etc.
2006
-     *                                                 an attendee to a group, you also want to specify which role they
2007
-     *                                                 will have in that group. So you would use this parameter to
2008
-     *                                                 specify array('role-column-name'=>'role-id')
2009
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2010
-     *                                                 allow you to further constrict the relation to being added.
2011
-     *                                                 However, keep in mind that the columns (keys) given must match a
2012
-     *                                                 column on the JOIN table and currently only the HABTM models
2013
-     *                                                 accept these additional conditions.  Also remember that if an
2014
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2015
-     *                                                 NEW row is created in the join table.
2016
-     * @param null   $cache_id
2017
-     * @throws EE_Error
2018
-     * @return EE_Base_Class the object the relation was added to
2019
-     */
2020
-    public function _add_relation_to(
2021
-        $otherObjectModelObjectOrID,
2022
-        $relationName,
2023
-        $extra_join_model_fields_n_values = array(),
2024
-        $cache_id = null
2025
-    ) {
2026
-        //if this thing exists in the DB, save the relation to the DB
2027
-        if ($this->ID()) {
2028
-            $otherObject = $this->get_model()
2029
-                                ->add_relationship_to($this, $otherObjectModelObjectOrID, $relationName,
2030
-                                    $extra_join_model_fields_n_values);
2031
-            //clear cache so future get_many_related and get_first_related() return new results.
2032
-            $this->clear_cache($relationName, $otherObject, true);
2033
-            if ($otherObject instanceof EE_Base_Class) {
2034
-                $otherObject->clear_cache($this->get_model()->get_this_model_name(), $this);
2035
-            }
2036
-        } else {
2037
-            //this thing doesn't exist in the DB,  so just cache it
2038
-            if ( ! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2039
-                throw new EE_Error(sprintf(
2040
-                    __('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',
2041
-                        'event_espresso'),
2042
-                    $otherObjectModelObjectOrID,
2043
-                    get_class($this)
2044
-                ));
2045
-            } else {
2046
-                $otherObject = $otherObjectModelObjectOrID;
2047
-            }
2048
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2049
-        }
2050
-        if ($otherObject instanceof EE_Base_Class) {
2051
-            //fix the reciprocal relation too
2052
-            if ($otherObject->ID()) {
2053
-                //its saved so assumed relations exist in the DB, so we can just
2054
-                //clear the cache so future queries use the updated info in the DB
2055
-                $otherObject->clear_cache($this->get_model()->get_this_model_name(), null, true);
2056
-            } else {
2057
-                //it's not saved, so it caches relations like this
2058
-                $otherObject->cache($this->get_model()->get_this_model_name(), $this);
2059
-            }
2060
-        }
2061
-        return $otherObject;
2062
-    }
2063
-
2064
-
2065
-
2066
-    /**
2067
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2068
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2069
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2070
-     * from the cache
2071
-     *
2072
-     * @param mixed  $otherObjectModelObjectOrID
2073
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2074
-     *                to the DB yet
2075
-     * @param string $relationName
2076
-     * @param array  $where_query
2077
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2078
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2079
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2080
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is
2081
-     *                created in the join table.
2082
-     * @return EE_Base_Class the relation was removed from
2083
-     * @throws \EE_Error
2084
-     */
2085
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2086
-    {
2087
-        if ($this->ID()) {
2088
-            //if this exists in the DB, save the relation change to the DB too
2089
-            $otherObject = $this->get_model()
2090
-                                ->remove_relationship_to($this, $otherObjectModelObjectOrID, $relationName,
2091
-                                    $where_query);
2092
-            $this->clear_cache($relationName, $otherObject);
2093
-        } else {
2094
-            //this doesn't exist in the DB, just remove it from the cache
2095
-            $otherObject = $this->clear_cache($relationName, $otherObjectModelObjectOrID);
2096
-        }
2097
-        if ($otherObject instanceof EE_Base_Class) {
2098
-            $otherObject->clear_cache($this->get_model()->get_this_model_name(), $this);
2099
-        }
2100
-        return $otherObject;
2101
-    }
2102
-
2103
-
2104
-
2105
-    /**
2106
-     * Removes ALL the related things for the $relationName.
2107
-     *
2108
-     * @param string $relationName
2109
-     * @param array  $where_query_params like EEM_Base::get_all's $query_params[0] (where conditions)
2110
-     * @return EE_Base_Class
2111
-     * @throws \EE_Error
2112
-     */
2113
-    public function _remove_relations($relationName, $where_query_params = array())
2114
-    {
2115
-        if ($this->ID()) {
2116
-            //if this exists in the DB, save the relation change to the DB too
2117
-            $otherObjects = $this->get_model()->remove_relations($this, $relationName, $where_query_params);
2118
-            $this->clear_cache($relationName, null, true);
2119
-        } else {
2120
-            //this doesn't exist in the DB, just remove it from the cache
2121
-            $otherObjects = $this->clear_cache($relationName, null, true);
2122
-        }
2123
-        if (is_array($otherObjects)) {
2124
-            foreach ($otherObjects as $otherObject) {
2125
-                $otherObject->clear_cache($this->get_model()->get_this_model_name(), $this);
2126
-            }
2127
-        }
2128
-        return $otherObjects;
2129
-    }
2130
-
2131
-
2132
-
2133
-    /**
2134
-     * Gets all the related model objects of the specified type. Eg, if the current class if
2135
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2136
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2137
-     * because we want to get even deleted items etc.
2138
-     *
2139
-     * @param string $relationName key in the model's _model_relations array
2140
-     * @param array  $query_params like EEM_Base::get_all
2141
-     * @return EE_Base_Class[] Results not necessarily indexed by IDs, because some results might not have primary keys
2142
-     * @throws \EE_Error
2143
-     *                             or might not be saved yet. Consider using EEM_Base::get_IDs() on these results if
2144
-     *                             you want IDs
2145
-     */
2146
-    public function get_many_related($relationName, $query_params = array())
2147
-    {
2148
-        if ($this->ID()) {
2149
-            //this exists in the DB, so get the related things from either the cache or the DB
2150
-            //if there are query parameters, forget about caching the related model objects.
2151
-            if ($query_params) {
2152
-                $related_model_objects = $this->get_model()->get_all_related($this, $relationName, $query_params);
2153
-            } else {
2154
-                //did we already cache the result of this query?
2155
-                $cached_results = $this->get_all_from_cache($relationName);
2156
-                if ( ! $cached_results) {
2157
-                    $related_model_objects = $this->get_model()->get_all_related($this, $relationName, $query_params);
2158
-                    //if no query parameters were passed, then we got all the related model objects
2159
-                    //for that relation. We can cache them then.
2160
-                    foreach ($related_model_objects as $related_model_object) {
2161
-                        $this->cache($relationName, $related_model_object);
2162
-                    }
2163
-                } else {
2164
-                    $related_model_objects = $cached_results;
2165
-                }
2166
-            }
2167
-        } else {
2168
-            //this doesn't exist in the DB, so just get the related things from the cache
2169
-            $related_model_objects = $this->get_all_from_cache($relationName);
2170
-        }
2171
-        return $related_model_objects;
2172
-    }
2173
-
2174
-
2175
-
2176
-    /**
2177
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2178
-     * unless otherwise specified in the $query_params
2179
-     *
2180
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2181
-     * @param array  $query_params   like EEM_Base::get_all's
2182
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2183
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2184
-     *                               that by the setting $distinct to TRUE;
2185
-     * @return int
2186
-     */
2187
-    public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2188
-    {
2189
-        return $this->get_model()->count_related($this, $relation_name, $query_params, $field_to_count, $distinct);
2190
-    }
2191
-
2192
-
2193
-
2194
-    /**
2195
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2196
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2197
-     *
2198
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2199
-     * @param array  $query_params  like EEM_Base::get_all's
2200
-     * @param string $field_to_sum  name of field to count by.
2201
-     *                              By default, uses primary key (which doesn't make much sense, so you should probably
2202
-     *                              change it)
2203
-     * @return int
2204
-     */
2205
-    public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2206
-    {
2207
-        return $this->get_model()->sum_related($this, $relation_name, $query_params, $field_to_sum);
2208
-    }
2209
-
2210
-
2211
-
2212
-    /**
2213
-     * Gets the first (ie, one) related model object of the specified type.
2214
-     *
2215
-     * @param string $relationName key in the model's _model_relations array
2216
-     * @param array  $query_params like EEM_Base::get_all
2217
-     * @return EE_Base_Class (not an array, a single object)
2218
-     * @throws \EE_Error
2219
-     */
2220
-    public function get_first_related($relationName, $query_params = array())
2221
-    {
2222
-        if ($this->ID()) {//this exists in the DB, get from the cache OR the DB
2223
-            //if they've provided some query parameters, don't bother trying to cache the result
2224
-            //also make sure we're not caching the result of get_first_related
2225
-            //on a relation which should have an array of objects (because the cache might have an array of objects)
2226
-            if ($query_params
2227
-                || ! $this->get_model()->related_settings_for($relationName)
2228
-                     instanceof
2229
-                     EE_Belongs_To_Relation
2230
-            ) {
2231
-                $related_model_object = $this->get_model()->get_first_related($this, $relationName, $query_params);
2232
-            } else {
2233
-                //first, check if we've already cached the result of this query
2234
-                $cached_result = $this->get_one_from_cache($relationName);
2235
-                if ( ! $cached_result) {
2236
-                    $related_model_object = $this->get_model()->get_first_related($this, $relationName, $query_params);
2237
-                    $this->cache($relationName, $related_model_object);
2238
-                } else {
2239
-                    $related_model_object = $cached_result;
2240
-                }
2241
-            }
2242
-        } else {
2243
-            $related_model_object = null;
2244
-            //this doesn't exist in the Db, but maybe the relation is of type belongs to, and so the related thing might
2245
-            if ($this->get_model()->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2246
-                $related_model_object = $this->get_model()->get_first_related($this, $relationName, $query_params);
2247
-            }
2248
-            //this doesn't exist in the DB and apparently the thing it belongs to doesn't either, just get what's cached on this object
2249
-            if ( ! $related_model_object) {
2250
-                $related_model_object = $this->get_one_from_cache($relationName);
2251
-            }
2252
-        }
2253
-        return $related_model_object;
2254
-    }
2255
-
2256
-
2257
-
2258
-    /**
2259
-     * Does a delete on all related objects of type $relationName and removes
2260
-     * the current model object's relation to them. If they can't be deleted (because
2261
-     * of blocking related model objects) does nothing. If the related model objects are
2262
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2263
-     * If this model object doesn't exist yet in the DB, just removes its related things
2264
-     *
2265
-     * @param string $relationName
2266
-     * @param array  $query_params like EEM_Base::get_all's
2267
-     * @return int how many deleted
2268
-     * @throws \EE_Error
2269
-     */
2270
-    public function delete_related($relationName, $query_params = array())
2271
-    {
2272
-        if ($this->ID()) {
2273
-            $count = $this->get_model()->delete_related($this, $relationName, $query_params);
2274
-        } else {
2275
-            $count = count($this->get_all_from_cache($relationName));
2276
-            $this->clear_cache($relationName, null, true);
2277
-        }
2278
-        return $count;
2279
-    }
2280
-
2281
-
2282
-
2283
-    /**
2284
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2285
-     * the current model object's relation to them. If they can't be deleted (because
2286
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2287
-     * If the related thing isn't a soft-deletable model object, this function is identical
2288
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2289
-     *
2290
-     * @param string $relationName
2291
-     * @param array  $query_params like EEM_Base::get_all's
2292
-     * @return int how many deleted (including those soft deleted)
2293
-     * @throws \EE_Error
2294
-     */
2295
-    public function delete_related_permanently($relationName, $query_params = array())
2296
-    {
2297
-        if ($this->ID()) {
2298
-            $count = $this->get_model()->delete_related_permanently($this, $relationName, $query_params);
2299
-        } else {
2300
-            $count = count($this->get_all_from_cache($relationName));
2301
-        }
2302
-        $this->clear_cache($relationName, null, true);
2303
-        return $count;
2304
-    }
2305
-
2306
-
2307
-
2308
-    /**
2309
-     * is_set
2310
-     * Just a simple utility function children can use for checking if property exists
2311
-     *
2312
-     * @access  public
2313
-     * @param  string $field_name property to check
2314
-     * @return bool                              TRUE if existing,FALSE if not.
2315
-     */
2316
-    public function is_set($field_name)
2317
-    {
2318
-        return isset($this->_fields[$field_name]);
2319
-    }
2320
-
2321
-
2322
-
2323
-    /**
2324
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2325
-     * EE_Error exception if they don't
2326
-     *
2327
-     * @param  mixed (string|array) $properties properties to check
2328
-     * @throws EE_Error
2329
-     * @return bool                              TRUE if existing, throw EE_Error if not.
2330
-     */
2331
-    protected function _property_exists($properties)
2332
-    {
2333
-        foreach ((array)$properties as $property_name) {
2334
-            //first make sure this property exists
2335
-            if ( ! $this->_fields[$property_name]) {
2336
-                throw new EE_Error(
2337
-                    sprintf(
2338
-                        __(
2339
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2340
-                            'event_espresso'
2341
-                        ),
2342
-                        $property_name
2343
-                    )
2344
-                );
2345
-            }
2346
-        }
2347
-        return true;
2348
-    }
2349
-
2350
-
2351
-
2352
-    /**
2353
-     * This simply returns an array of model fields for this object
2354
-     *
2355
-     * @return array
2356
-     * @throws \EE_Error
2357
-     */
2358
-    public function model_field_array()
2359
-    {
2360
-        $fields = $this->get_model()->field_settings(false);
2361
-        $properties = array();
2362
-        //remove prepended underscore
2363
-        foreach ($fields as $field_name => $settings) {
2364
-            $properties[$field_name] = $this->get($field_name);
2365
-        }
2366
-        return $properties;
2367
-    }
2368
-
2369
-
2370
-
2371
-    /**
2372
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2373
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2374
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
2375
-     * requiring a plugin to extend the EE_Base_Class (which works fine is there's only 1 plugin, but when will that
2376
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
2377
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
2378
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
2379
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
2380
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
2381
-     * my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2382
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2383
-     *        return $previousReturnValue.$returnString;
2384
-     * }
2385
-     * require('EE_Answer.class.php');
2386
-     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2387
-     * echo $answer->my_callback('monkeys',100);
2388
-     * //will output "you called my_callback! and passed args:monkeys,100"
2389
-     *
2390
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2391
-     * @param array  $args       array of original arguments passed to the function
2392
-     * @throws EE_Error
2393
-     * @return mixed whatever the plugin which calls add_filter decides
2394
-     */
2395
-    public function __call($methodName, $args)
2396
-    {
2397
-        $className = get_class($this);
2398
-        $tagName = "FHEE__{$className}__{$methodName}";
2399
-        if ( ! has_filter($tagName)) {
2400
-            throw new EE_Error(
2401
-                sprintf(
2402
-                    __(
2403
-                        "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;}",
2404
-                        "event_espresso"
2405
-                    ),
2406
-                    $methodName,
2407
-                    $className,
2408
-                    $tagName
2409
-                )
2410
-            );
2411
-        }
2412
-        return apply_filters($tagName, null, $this, $args);
2413
-    }
2414
-
2415
-
2416
-
2417
-    /**
2418
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2419
-     * A $previous_value can be specified in case there are many meta rows with the same key
2420
-     *
2421
-     * @param string $meta_key
2422
-     * @param mixed  $meta_value
2423
-     * @param mixed  $previous_value
2424
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2425
-     * @throws \EE_Error
2426
-     * NOTE: if the values haven't changed, returns 0
2427
-     */
2428
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2429
-    {
2430
-        $query_params = array(
2431
-            array(
2432
-                'EXM_key'  => $meta_key,
2433
-                'OBJ_ID'   => $this->ID(),
2434
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2435
-            ),
2436
-        );
2437
-        if ($previous_value !== null) {
2438
-            $query_params[0]['EXM_value'] = $meta_value;
2439
-        }
2440
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2441
-        if ( ! $existing_rows_like_that) {
2442
-            return $this->add_extra_meta($meta_key, $meta_value);
2443
-        }
2444
-        foreach ($existing_rows_like_that as $existing_row) {
2445
-            $existing_row->save(array('EXM_value' => $meta_value));
2446
-        }
2447
-        return count($existing_rows_like_that);
2448
-    }
2449
-
2450
-
2451
-
2452
-    /**
2453
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2454
-     * no other extra meta for this model object have the same key. Returns TRUE if the
2455
-     * extra meta row was entered, false if not
2456
-     *
2457
-     * @param string  $meta_key
2458
-     * @param string  $meta_value
2459
-     * @param boolean $unique
2460
-     * @return boolean
2461
-     * @throws \EE_Error
2462
-     */
2463
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2464
-    {
2465
-        if ($unique) {
2466
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2467
-                array(
2468
-                    array(
2469
-                        'EXM_key'  => $meta_key,
2470
-                        'OBJ_ID'   => $this->ID(),
2471
-                        'EXM_type' => $this->get_model()->get_this_model_name(),
2472
-                    ),
2473
-                )
2474
-            );
2475
-            if ($existing_extra_meta) {
2476
-                return false;
2477
-            }
2478
-        }
2479
-        $new_extra_meta = EE_Extra_Meta::new_instance(
2480
-            array(
2481
-                'EXM_key'   => $meta_key,
2482
-                'EXM_value' => $meta_value,
2483
-                'OBJ_ID'    => $this->ID(),
2484
-                'EXM_type'  => $this->get_model()->get_this_model_name(),
2485
-            )
2486
-        );
2487
-        $new_extra_meta->save();
2488
-        return true;
2489
-    }
2490
-
2491
-
2492
-
2493
-    /**
2494
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2495
-     * is specified, only deletes extra meta records with that value.
2496
-     *
2497
-     * @param string $meta_key
2498
-     * @param string $meta_value
2499
-     * @return int number of extra meta rows deleted
2500
-     * @throws \EE_Error
2501
-     */
2502
-    public function delete_extra_meta($meta_key, $meta_value = null)
2503
-    {
2504
-        $query_params = array(
2505
-            array(
2506
-                'EXM_key'  => $meta_key,
2507
-                'OBJ_ID'   => $this->ID(),
2508
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2509
-            ),
2510
-        );
2511
-        if ($meta_value !== null) {
2512
-            $query_params[0]['EXM_value'] = $meta_value;
2513
-        }
2514
-        return EEM_Extra_Meta::instance()->delete($query_params);
2515
-    }
2516
-
2517
-
2518
-
2519
-    /**
2520
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2521
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2522
-     * You can specify $default is case you haven't found the extra meta
2523
-     *
2524
-     * @param string  $meta_key
2525
-     * @param boolean $single
2526
-     * @param mixed   $default if we don't find anything, what should we return?
2527
-     * @return mixed single value if $single; array if ! $single
2528
-     * @throws \EE_Error
2529
-     */
2530
-    public function get_extra_meta($meta_key, $single = false, $default = null)
2531
-    {
2532
-        if ($single) {
2533
-            $result = $this->get_first_related('Extra_Meta', array(array('EXM_key' => $meta_key)));
2534
-            if ($result instanceof EE_Extra_Meta) {
2535
-                return $result->value();
2536
-            } else {
2537
-                return $default;
2538
-            }
2539
-        } else {
2540
-            $results = $this->get_many_related('Extra_Meta', array(array('EXM_key' => $meta_key)));
2541
-            if ($results) {
2542
-                $values = array();
2543
-                foreach ($results as $result) {
2544
-                    if ($result instanceof EE_Extra_Meta) {
2545
-                        $values[$result->ID()] = $result->value();
2546
-                    }
2547
-                }
2548
-                return $values;
2549
-            } else {
2550
-                return $default;
2551
-            }
2552
-        }
2553
-    }
2554
-
2555
-
2556
-
2557
-    /**
2558
-     * Returns a simple array of all the extra meta associated with this model object.
2559
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2560
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2561
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2562
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2563
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2564
-     * finally the extra meta's value as each sub-value. (eg
2565
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2566
-     *
2567
-     * @param boolean $one_of_each_key
2568
-     * @return array
2569
-     * @throws \EE_Error
2570
-     */
2571
-    public function all_extra_meta_array($one_of_each_key = true)
2572
-    {
2573
-        $return_array = array();
2574
-        if ($one_of_each_key) {
2575
-            $extra_meta_objs = $this->get_many_related('Extra_Meta', array('group_by' => 'EXM_key'));
2576
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2577
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2578
-                    $return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2579
-                }
2580
-            }
2581
-        } else {
2582
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2583
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2584
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2585
-                    if ( ! isset($return_array[$extra_meta_obj->key()])) {
2586
-                        $return_array[$extra_meta_obj->key()] = array();
2587
-                    }
2588
-                    $return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2589
-                }
2590
-            }
2591
-        }
2592
-        return $return_array;
2593
-    }
2594
-
2595
-
2596
-
2597
-    /**
2598
-     * Gets a pretty nice displayable nice for this model object. Often overridden
2599
-     *
2600
-     * @return string
2601
-     * @throws \EE_Error
2602
-     */
2603
-    public function name()
2604
-    {
2605
-        //find a field that's not a text field
2606
-        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2607
-        if ($field_we_can_use) {
2608
-            return $this->get($field_we_can_use->get_name());
2609
-        } else {
2610
-            $first_few_properties = $this->model_field_array();
2611
-            $first_few_properties = array_slice($first_few_properties, 0, 3);
2612
-            $name_parts = array();
2613
-            foreach ($first_few_properties as $name => $value) {
2614
-                $name_parts[] = "$name:$value";
2615
-            }
2616
-            return implode(",", $name_parts);
2617
-        }
2618
-    }
2619
-
2620
-
2621
-
2622
-    /**
2623
-     * in_entity_map
2624
-     * Checks if this model object has been proven to already be in the entity map
2625
-     *
2626
-     * @return boolean
2627
-     * @throws \EE_Error
2628
-     */
2629
-    public function in_entity_map()
2630
-    {
2631
-        if ($this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this) {
2632
-            //well, if we looked, did we find it in the entity map?
2633
-            return true;
2634
-        } else {
2635
-            return false;
2636
-        }
2637
-    }
2638
-
2639
-
2640
-
2641
-    /**
2642
-     * refresh_from_db
2643
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
2644
-     *
2645
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
2646
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
2647
-     */
2648
-    public function refresh_from_db()
2649
-    {
2650
-        if ($this->ID() && $this->in_entity_map()) {
2651
-            $this->get_model()->refresh_entity_map_from_db($this->ID());
2652
-        } else {
2653
-            //if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
2654
-            //if it has an ID but it's not in the map, and you're asking me to refresh it
2655
-            //that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
2656
-            //absolutely nothing in it for this ID
2657
-            if (WP_DEBUG) {
2658
-                throw new EE_Error(
2659
-                    sprintf(
2660
-                        __('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.',
2661
-                            'event_espresso'),
2662
-                        $this->ID(),
2663
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2664
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2665
-                    )
2666
-                );
2667
-            }
2668
-        }
2669
-    }
2670
-
2671
-
2672
-
2673
-    /**
2674
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
2675
-     * (probably a bad assumption they have made, oh well)
2676
-     *
2677
-     * @return string
2678
-     */
2679
-    public function __toString()
2680
-    {
2681
-        try {
2682
-            return sprintf('%s (%s)', $this->name(), $this->ID());
2683
-        } catch (Exception $e) {
2684
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
2685
-            return '';
2686
-        }
2687
-    }
2688
-
2689
-
2690
-
2691
-    /**
2692
-     * Clear related model objects if they're already in the DB, because otherwise when we
2693
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
2694
-     * This means if we have made changes to those related model objects, and want to unserialize
2695
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
2696
-     * Instead, those related model objects should be directly serialized and stored.
2697
-     * Eg, the following won't work:
2698
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
2699
-     * $att = $reg->attendee();
2700
-     * $att->set( 'ATT_fname', 'Dirk' );
2701
-     * update_option( 'my_option', serialize( $reg ) );
2702
-     * //END REQUEST
2703
-     * //START NEXT REQUEST
2704
-     * $reg = get_option( 'my_option' );
2705
-     * $reg->attendee()->save();
2706
-     * And would need to be replace with:
2707
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
2708
-     * $att = $reg->attendee();
2709
-     * $att->set( 'ATT_fname', 'Dirk' );
2710
-     * update_option( 'my_option', serialize( $reg ) );
2711
-     * //END REQUEST
2712
-     * //START NEXT REQUEST
2713
-     * $att = get_option( 'my_option' );
2714
-     * $att->save();
2715
-     *
2716
-     * @return array
2717
-     * @throws \EE_Error
2718
-     */
2719
-    public function __sleep()
2720
-    {
2721
-        foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
2722
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
2723
-                $classname = 'EE_' . $this->get_model()->get_this_model_name();
2724
-                if (
2725
-                    $this->get_one_from_cache($relation_name) instanceof $classname
2726
-                    && $this->get_one_from_cache($relation_name)->ID()
2727
-                ) {
2728
-                    $this->clear_cache($relation_name, $this->get_one_from_cache($relation_name)->ID());
2729
-                }
2730
-            }
2731
-        }
2732
-        $this->_props_n_values_provided_in_constructor = array();
2733
-        return array_keys(get_object_vars($this));
2734
-    }
2735
-
2736
-
2737
-
2738
-    /**
2739
-     * restore _props_n_values_provided_in_constructor
2740
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
2741
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
2742
-     * At best, you would only be able to detect if state change has occurred during THIS request.
2743
-     */
2744
-    public function __wakeup()
2745
-    {
2746
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
2747
-    }
28
+	/**
29
+	 * This is an array of the original properties and values provided during construction
30
+	 * of this model object. (keys are model field names, values are their values).
31
+	 * This list is important to remember so that when we are merging data from the db, we know
32
+	 * which values to override and which to not override.
33
+	 *
34
+	 * @var array
35
+	 */
36
+	protected $_props_n_values_provided_in_constructor;
37
+
38
+	/**
39
+	 * Timezone
40
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
41
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
42
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
43
+	 * access to it.
44
+	 *
45
+	 * @var string
46
+	 */
47
+	protected $_timezone;
48
+
49
+
50
+
51
+	/**
52
+	 * date format
53
+	 * pattern or format for displaying dates
54
+	 *
55
+	 * @var string $_dt_frmt
56
+	 */
57
+	protected $_dt_frmt;
58
+
59
+
60
+
61
+	/**
62
+	 * time format
63
+	 * pattern or format for displaying time
64
+	 *
65
+	 * @var string $_tm_frmt
66
+	 */
67
+	protected $_tm_frmt;
68
+
69
+
70
+
71
+	/**
72
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
73
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
74
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
75
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
76
+	 *
77
+	 * @var array
78
+	 */
79
+	protected $_cached_properties = array();
80
+
81
+	/**
82
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
83
+	 * single
84
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
85
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
86
+	 * all others have an array)
87
+	 *
88
+	 * @var array
89
+	 */
90
+	protected $_model_relations = array();
91
+
92
+	/**
93
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
94
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
95
+	 *
96
+	 * @var array
97
+	 */
98
+	protected $_fields = array();
99
+
100
+	/**
101
+	 * @var boolean indicating whether or not this model object is intended to ever be saved
102
+	 * For example, we might create model objects intended to only be used for the duration
103
+	 * of this request and to be thrown away, and if they were accidentally saved
104
+	 * it would be a bug.
105
+	 */
106
+	protected $_allow_persist = true;
107
+
108
+	/**
109
+	 * @var boolean indicating whether or not this model object's properties have changed since construction
110
+	 */
111
+	protected $_has_changes = false;
112
+
113
+
114
+
115
+	/**
116
+	 * @param array  $fieldValues
117
+	 * @param string $timezone
118
+	 * @param array  $date_formats
119
+	 * @param bool   $bydb
120
+	 * @return \EE_Base_Class
121
+	 * @throws \EE_Error
122
+	 */
123
+	public static function new_instance(
124
+		array $fieldValues = array(),
125
+		$timezone = '',
126
+		array $date_formats = array(),
127
+		$bydb = false
128
+	)
129
+	{
130
+		$className = get_called_class();
131
+		if ( ! $bydb) {
132
+			$cached_object = \EE_Base_Class::_check_for_object($fieldValues, $className, $timezone, $date_formats);
133
+			if ($cached_object) {
134
+				return $cached_object;
135
+			}
136
+		}
137
+		return new static($fieldValues, $bydb, $timezone, $date_formats);
138
+	}
139
+
140
+
141
+
142
+	/**
143
+	 * @deprecated
144
+	 * @param array  $fieldValues
145
+	 * @param string $timezone
146
+	 * @param array  $date_formats
147
+	 * @return \EE_Base_Class
148
+	 * @throws \EE_Error
149
+	 */
150
+	public static function new_instance_from_db(array $fieldValues = array(), $timezone = '', array $date_formats = array())
151
+	{
152
+		return static::new_instance($fieldValues, $timezone, $date_formats, true);
153
+	}
154
+
155
+
156
+	/**
157
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children play nice
158
+	 *
159
+	 * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
160
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
161
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
162
+	 * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
163
+	 *                                                         corresponding db model or not.
164
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
165
+	 *                                                         be in when instantiating a EE_Base_Class object.
166
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
167
+	 *                                                         value is the date_format and second value is the time
168
+	 *                                                         format.
169
+	 * @throws EE_Error
170
+	 */
171
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
172
+	{
173
+		$className = get_class($this);
174
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
175
+		$model = $this->get_model();
176
+		$model_fields = $model->field_settings(false);
177
+		// ensure $fieldValues is an array
178
+		$fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
179
+		// EEH_Debug_Tools::printr( $fieldValues, '$fieldValues  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
180
+		// verify client code has not passed any invalid field names
181
+		foreach ($fieldValues as $field_name => $field_value) {
182
+			if ( ! isset($model_fields[$field_name])) {
183
+				throw new EE_Error(sprintf(__("Invalid field (%s) passed to constructor of %s. Allowed fields are :%s",
184
+					"event_espresso"), $field_name, get_class($this), implode(", ", array_keys($model_fields))));
185
+			}
186
+		}
187
+		// EEH_Debug_Tools::printr( $model_fields, '$model_fields  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
188
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
189
+		if ( ! empty($date_formats) && is_array($date_formats)) {
190
+			list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
191
+		} else {
192
+			//set default formats for date and time
193
+			$this->_dt_frmt = (string)get_option('date_format', 'Y-m-d');
194
+			$this->_tm_frmt = (string)get_option('time_format', 'g:i a');
195
+		}
196
+		//if db model is instantiating
197
+		if ($bydb) {
198
+			//client code has indicated these field values are from the database
199
+			foreach ($model_fields as $fieldName => $field) {
200
+				$this->set_from_db($fieldName, isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null);
201
+			}
202
+		} else {
203
+			//we're constructing a brand
204
+			//new instance of the model object. Generally, this means we'll need to do more field validation
205
+			foreach ($model_fields as $fieldName => $field) {
206
+				$this->set($fieldName, isset($fieldValues[$fieldName]) ? $fieldValues[$fieldName] : null, true);
207
+			}
208
+		}
209
+		//remember what values were passed to this constructor
210
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
211
+		//remember in entity mapper
212
+		if ( ! $bydb && $model->has_primary_key_field() && $this->ID()) {
213
+			$model->add_to_entity_map($this);
214
+		}
215
+		//setup all the relations
216
+		foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
217
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
218
+				$this->_model_relations[$relation_name] = null;
219
+			} else {
220
+				$this->_model_relations[$relation_name] = array();
221
+			}
222
+		}
223
+		/**
224
+		 * Action done at the end of each model object construction
225
+		 *
226
+		 * @param EE_Base_Class $this the model object just created
227
+		 */
228
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
229
+	}
230
+
231
+
232
+
233
+	/**
234
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
235
+	 *
236
+	 * @return boolean
237
+	 */
238
+	public function allow_persist()
239
+	{
240
+		return $this->_allow_persist;
241
+	}
242
+
243
+
244
+
245
+	/**
246
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
247
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
248
+	 * you got new information that somehow made you change your mind.
249
+	 *
250
+	 * @param boolean $allow_persist
251
+	 * @return boolean
252
+	 */
253
+	public function set_allow_persist($allow_persist)
254
+	{
255
+		return $this->_allow_persist = $allow_persist;
256
+	}
257
+
258
+
259
+
260
+	/**
261
+	 * Gets the field's original value when this object was constructed during this request.
262
+	 * This can be helpful when determining if a model object has changed or not
263
+	 *
264
+	 * @param string $field_name
265
+	 * @return mixed|null
266
+	 * @throws \EE_Error
267
+	 */
268
+	public function get_original($field_name)
269
+	{
270
+		if (isset($this->_props_n_values_provided_in_constructor[$field_name])
271
+			&& $field_settings = $this->get_model()->field_settings_for($field_name)
272
+		) {
273
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[$field_name]);
274
+		} else {
275
+			return null;
276
+		}
277
+	}
278
+
279
+
280
+
281
+	/**
282
+	 * @param EE_Base_Class $obj
283
+	 * @return string
284
+	 */
285
+	public function get_class($obj)
286
+	{
287
+		return get_class($obj);
288
+	}
289
+
290
+
291
+
292
+	/**
293
+	 * Overrides parent because parent expects old models.
294
+	 * This also doesn't do any validation, and won't work for serialized arrays
295
+	 *
296
+	 * @param    string $field_name
297
+	 * @param    mixed  $field_value
298
+	 * @param bool      $use_default
299
+	 * @throws \EE_Error
300
+	 */
301
+	public function set($field_name, $field_value, $use_default = false)
302
+	{
303
+		// if not using default and nothing has changed, and object has already been setup (has ID),
304
+		// then don't do anything
305
+		if (
306
+			! $use_default
307
+			&& $this->_fields[$field_name] === $field_value
308
+			&& $this->ID()
309
+		) {
310
+			return;
311
+		}
312
+		$this->_has_changes = true;
313
+		$field_obj = $this->get_model()->field_settings_for($field_name);
314
+		if ($field_obj instanceof EE_Model_Field_Base) {
315
+			//			if ( method_exists( $field_obj, 'set_timezone' )) {
316
+			if ($field_obj instanceof EE_Datetime_Field) {
317
+				$field_obj->set_timezone($this->_timezone);
318
+				$field_obj->set_date_format($this->_dt_frmt);
319
+				$field_obj->set_time_format($this->_tm_frmt);
320
+			}
321
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
322
+			//should the value be null?
323
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
324
+				$this->_fields[$field_name] = $field_obj->get_default_value();
325
+				/**
326
+				 * To save having to refactor all the models, if a default value is used for a
327
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
328
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
329
+				 * object.
330
+				 *
331
+				 * @since 4.6.10+
332
+				 */
333
+				if (
334
+					$field_obj instanceof EE_Datetime_Field
335
+					&& $this->_fields[$field_name] !== null
336
+					&& ! $this->_fields[$field_name] instanceof DateTime
337
+				) {
338
+					empty($this->_fields[$field_name])
339
+						? $this->set($field_name, time())
340
+						: $this->set($field_name, $this->_fields[$field_name]);
341
+				}
342
+			} else {
343
+				$this->_fields[$field_name] = $holder_of_value;
344
+			}
345
+			//if we're not in the constructor...
346
+			//now check if what we set was a primary key
347
+			if (
348
+				//note: props_n_values_provided_in_constructor is only set at the END of the constructor
349
+				$this->_props_n_values_provided_in_constructor
350
+				&& $field_value
351
+				&& $field_name === self::_get_primary_key_name(get_class($this))
352
+			) {
353
+				//if so, we want all this object's fields to be filled either with
354
+				//what we've explicitly set on this model
355
+				//or what we have in the db
356
+				// echo "setting primary key!";
357
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
358
+				$obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
359
+				foreach ($fields_on_model as $field_obj) {
360
+					if ( ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
361
+						 && $field_obj->get_name() !== $field_name
362
+					) {
363
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
364
+					}
365
+				}
366
+				//oh this model object has an ID? well make sure its in the entity mapper
367
+				$this->get_model()->add_to_entity_map($this);
368
+			}
369
+			//let's unset any cache for this field_name from the $_cached_properties property.
370
+			$this->_clear_cached_property($field_name);
371
+		} else {
372
+			throw new EE_Error(sprintf(__("A valid EE_Model_Field_Base could not be found for the given field name: %s",
373
+				"event_espresso"), $field_name));
374
+		}
375
+	}
376
+
377
+
378
+
379
+	/**
380
+	 * This sets the field value on the db column if it exists for the given $column_name or
381
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
382
+	 *
383
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
384
+	 * @param string $field_name  Must be the exact column name.
385
+	 * @param mixed  $field_value The value to set.
386
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
387
+	 * @throws \EE_Error
388
+	 */
389
+	public function set_field_or_extra_meta($field_name, $field_value)
390
+	{
391
+		if ($this->get_model()->has_field($field_name)) {
392
+			$this->set($field_name, $field_value);
393
+			return true;
394
+		} else {
395
+			//ensure this object is saved first so that extra meta can be properly related.
396
+			$this->save();
397
+			return $this->update_extra_meta($field_name, $field_value);
398
+		}
399
+	}
400
+
401
+
402
+
403
+	/**
404
+	 * This retrieves the value of the db column set on this class or if that's not present
405
+	 * it will attempt to retrieve from extra_meta if found.
406
+	 * Example Usage:
407
+	 * Via EE_Message child class:
408
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
409
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
410
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
411
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
412
+	 * value for those extra fields dynamically via the EE_message object.
413
+	 *
414
+	 * @param  string $field_name expecting the fully qualified field name.
415
+	 * @return mixed|null  value for the field if found.  null if not found.
416
+	 * @throws \EE_Error
417
+	 */
418
+	public function get_field_or_extra_meta($field_name)
419
+	{
420
+		if ($this->get_model()->has_field($field_name)) {
421
+			$column_value = $this->get($field_name);
422
+		} else {
423
+			//This isn't a column in the main table, let's see if it is in the extra meta.
424
+			$column_value = $this->get_extra_meta($field_name, true, null);
425
+		}
426
+		return $column_value;
427
+	}
428
+
429
+
430
+
431
+	/**
432
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
433
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
434
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
435
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
436
+	 *
437
+	 * @access public
438
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
439
+	 * @return void
440
+	 * @throws \EE_Error
441
+	 */
442
+	public function set_timezone($timezone = '')
443
+	{
444
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
445
+		//make sure we clear all cached properties because they won't be relevant now
446
+		$this->_clear_cached_properties();
447
+		//make sure we update field settings and the date for all EE_Datetime_Fields
448
+		$model_fields = $this->get_model()->field_settings(false);
449
+		foreach ($model_fields as $field_name => $field_obj) {
450
+			if ($field_obj instanceof EE_Datetime_Field) {
451
+				$field_obj->set_timezone($this->_timezone);
452
+				if (isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime) {
453
+					$this->_fields[$field_name]->setTimezone(new DateTimeZone($this->_timezone));
454
+				}
455
+			}
456
+		}
457
+	}
458
+
459
+
460
+
461
+	/**
462
+	 * This just returns whatever is set for the current timezone.
463
+	 *
464
+	 * @access public
465
+	 * @return string timezone string
466
+	 */
467
+	public function get_timezone()
468
+	{
469
+		return $this->_timezone;
470
+	}
471
+
472
+
473
+
474
+	/**
475
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
476
+	 * internally instead of wp set date format options
477
+	 *
478
+	 * @since 4.6
479
+	 * @param string $format should be a format recognizable by PHP date() functions.
480
+	 */
481
+	public function set_date_format($format)
482
+	{
483
+		$this->_dt_frmt = $format;
484
+		//clear cached_properties because they won't be relevant now.
485
+		$this->_clear_cached_properties();
486
+	}
487
+
488
+
489
+
490
+	/**
491
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
492
+	 * class internally instead of wp set time format options.
493
+	 *
494
+	 * @since 4.6
495
+	 * @param string $format should be a format recognizable by PHP date() functions.
496
+	 */
497
+	public function set_time_format($format)
498
+	{
499
+		$this->_tm_frmt = $format;
500
+		//clear cached_properties because they won't be relevant now.
501
+		$this->_clear_cached_properties();
502
+	}
503
+
504
+
505
+
506
+	/**
507
+	 * This returns the current internal set format for the date and time formats.
508
+	 *
509
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
510
+	 *                             where the first value is the date format and the second value is the time format.
511
+	 * @return mixed string|array
512
+	 */
513
+	public function get_format($full = true)
514
+	{
515
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
516
+	}
517
+
518
+
519
+
520
+	/**
521
+	 * cache
522
+	 * stores the passed model object on the current model object.
523
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
524
+	 *
525
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
526
+	 *                                       'Registration' associated with this model object
527
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
528
+	 *                                       that could be a payment or a registration)
529
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
530
+	 *                                       items which will be stored in an array on this object
531
+	 * @throws EE_Error
532
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
533
+	 *                  related thing, no array)
534
+	 */
535
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
536
+	{
537
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
538
+		if ( ! $object_to_cache instanceof EE_Base_Class) {
539
+			return false;
540
+		}
541
+		// also get "how" the object is related, or throw an error
542
+		if ( ! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
543
+			throw new EE_Error(sprintf(__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
544
+				$relationName, get_class($this)));
545
+		}
546
+		// how many things are related ?
547
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
548
+			// if it's a "belongs to" relationship, then there's only one related model object  eg, if this is a registration, there's only 1 attendee for it
549
+			// so for these model objects just set it to be cached
550
+			$this->_model_relations[$relationName] = $object_to_cache;
551
+			$return = true;
552
+		} else {
553
+			// otherwise, this is the "many" side of a one to many relationship, so we'll add the object to the array of related objects for that type.
554
+			// eg: if this is an event, there are many registrations for that event, so we cache the registrations in an array
555
+			if ( ! is_array($this->_model_relations[$relationName])) {
556
+				// if for some reason, the cached item is a model object, then stick that in the array, otherwise start with an empty array
557
+				$this->_model_relations[$relationName] = $this->_model_relations[$relationName] instanceof EE_Base_Class
558
+					? array($this->_model_relations[$relationName]) : array();
559
+			}
560
+			// first check for a cache_id which is normally empty
561
+			if ( ! empty($cache_id)) {
562
+				// if the cache_id exists, then it means we are purposely trying to cache this with a known key that can then be used to retrieve the object later on
563
+				$this->_model_relations[$relationName][$cache_id] = $object_to_cache;
564
+				$return = $cache_id;
565
+			} elseif ($object_to_cache->ID()) {
566
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
567
+				$this->_model_relations[$relationName][$object_to_cache->ID()] = $object_to_cache;
568
+				$return = $object_to_cache->ID();
569
+			} else {
570
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
571
+				$this->_model_relations[$relationName][] = $object_to_cache;
572
+				// move the internal pointer to the end of the array
573
+				end($this->_model_relations[$relationName]);
574
+				// and grab the key so that we can return it
575
+				$return = key($this->_model_relations[$relationName]);
576
+			}
577
+		}
578
+		return $return;
579
+	}
580
+
581
+
582
+
583
+	/**
584
+	 * For adding an item to the cached_properties property.
585
+	 *
586
+	 * @access protected
587
+	 * @param string      $fieldname the property item the corresponding value is for.
588
+	 * @param mixed       $value     The value we are caching.
589
+	 * @param string|null $cache_type
590
+	 * @return void
591
+	 * @throws \EE_Error
592
+	 */
593
+	protected function _set_cached_property($fieldname, $value, $cache_type = null)
594
+	{
595
+		//first make sure this property exists
596
+		$this->get_model()->field_settings_for($fieldname);
597
+		$cache_type = empty($cache_type) ? 'standard' : $cache_type;
598
+		$this->_cached_properties[$fieldname][$cache_type] = $value;
599
+	}
600
+
601
+
602
+
603
+	/**
604
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
605
+	 * This also SETS the cache if we return the actual property!
606
+	 *
607
+	 * @param string $fieldname        the name of the property we're trying to retrieve
608
+	 * @param bool   $pretty
609
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
610
+	 *                                 (in cases where the same property may be used for different outputs
611
+	 *                                 - i.e. datetime, money etc.)
612
+	 *                                 It can also accept certain pre-defined "schema" strings
613
+	 *                                 to define how to output the property.
614
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
615
+	 * @return mixed                   whatever the value for the property is we're retrieving
616
+	 * @throws \EE_Error
617
+	 */
618
+	protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
619
+	{
620
+		//verify the field exists
621
+		$this->get_model()->field_settings_for($fieldname);
622
+		$cache_type = $pretty ? 'pretty' : 'standard';
623
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
624
+		if (isset($this->_cached_properties[$fieldname][$cache_type])) {
625
+			return $this->_cached_properties[$fieldname][$cache_type];
626
+		}
627
+		$value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
628
+		$this->_set_cached_property($fieldname, $value, $cache_type);
629
+		return $value;
630
+	}
631
+
632
+
633
+
634
+	/**
635
+	 * If the cache didn't fetch the needed item, this fetches it.
636
+	 * @param string $fieldname
637
+	 * @param bool $pretty
638
+	 * @param string $extra_cache_ref
639
+	 * @return mixed
640
+	 */
641
+	protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
642
+	{
643
+		$field_obj = $this->get_model()->field_settings_for($fieldname);
644
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
645
+		if ($field_obj instanceof EE_Datetime_Field) {
646
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
647
+		}
648
+		if ( ! isset($this->_fields[$fieldname])) {
649
+			$this->_fields[$fieldname] = null;
650
+		}
651
+		$value = $pretty
652
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[$fieldname], $extra_cache_ref)
653
+			: $field_obj->prepare_for_get($this->_fields[$fieldname]);
654
+		return $value;
655
+	}
656
+
657
+
658
+
659
+	/**
660
+	 * set timezone, formats, and output for EE_Datetime_Field objects
661
+	 *
662
+	 * @param \EE_Datetime_Field $datetime_field
663
+	 * @param bool               $pretty
664
+	 * @param null $date_or_time
665
+	 * @return void
666
+	 * @throws \EE_Error
667
+	 */
668
+	protected function _prepare_datetime_field(
669
+		EE_Datetime_Field $datetime_field,
670
+		$pretty = false,
671
+		$date_or_time = null
672
+	) {
673
+		$datetime_field->set_timezone($this->_timezone);
674
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
675
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
676
+		//set the output returned
677
+		switch ($date_or_time) {
678
+			case 'D' :
679
+				$datetime_field->set_date_time_output('date');
680
+				break;
681
+			case 'T' :
682
+				$datetime_field->set_date_time_output('time');
683
+				break;
684
+			default :
685
+				$datetime_field->set_date_time_output();
686
+		}
687
+	}
688
+
689
+
690
+
691
+	/**
692
+	 * This just takes care of clearing out the cached_properties
693
+	 *
694
+	 * @return void
695
+	 */
696
+	protected function _clear_cached_properties()
697
+	{
698
+		$this->_cached_properties = array();
699
+	}
700
+
701
+
702
+
703
+	/**
704
+	 * This just clears out ONE property if it exists in the cache
705
+	 *
706
+	 * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
707
+	 * @return void
708
+	 */
709
+	protected function _clear_cached_property($property_name)
710
+	{
711
+		if (isset($this->_cached_properties[$property_name])) {
712
+			unset($this->_cached_properties[$property_name]);
713
+		}
714
+	}
715
+
716
+
717
+
718
+	/**
719
+	 * Ensures that this related thing is a model object.
720
+	 *
721
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
722
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
723
+	 * @return EE_Base_Class
724
+	 * @throws \EE_Error
725
+	 */
726
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
727
+	{
728
+		$other_model_instance = self::_get_model_instance_with_name(
729
+			self::_get_model_classname($model_name),
730
+			$this->_timezone
731
+		);
732
+		return $other_model_instance->ensure_is_obj($object_or_id);
733
+	}
734
+
735
+
736
+
737
+	/**
738
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
739
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
740
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
741
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
742
+	 *
743
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
744
+	 *                                                     Eg 'Registration'
745
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
746
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
747
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
748
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
749
+	 *                                                     this is HasMany or HABTM.
750
+	 * @throws EE_Error
751
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
752
+	 *                       relation from all
753
+	 */
754
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
755
+	{
756
+		$relationship_to_model = $this->get_model()->related_settings_for($relationName);
757
+		$index_in_cache = '';
758
+		if ( ! $relationship_to_model) {
759
+			throw new EE_Error(
760
+				sprintf(
761
+					__("There is no relationship to %s on a %s. Cannot clear that cache", 'event_espresso'),
762
+					$relationName,
763
+					get_class($this)
764
+				)
765
+			);
766
+		}
767
+		if ($clear_all) {
768
+			$obj_removed = true;
769
+			$this->_model_relations[$relationName] = null;
770
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
771
+			$obj_removed = $this->_model_relations[$relationName];
772
+			$this->_model_relations[$relationName] = null;
773
+		} else {
774
+			if ($object_to_remove_or_index_into_array instanceof EE_Base_Class
775
+				&& $object_to_remove_or_index_into_array->ID()
776
+			) {
777
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
778
+				if (is_array($this->_model_relations[$relationName])
779
+					&& ! isset($this->_model_relations[$relationName][$index_in_cache])
780
+				) {
781
+					$index_found_at = null;
782
+					//find this object in the array even though it has a different key
783
+					foreach ($this->_model_relations[$relationName] as $index => $obj) {
784
+						if (
785
+							$obj instanceof EE_Base_Class
786
+							&& (
787
+								$obj == $object_to_remove_or_index_into_array
788
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
789
+							)
790
+						) {
791
+							$index_found_at = $index;
792
+							break;
793
+						}
794
+					}
795
+					if ($index_found_at) {
796
+						$index_in_cache = $index_found_at;
797
+					} else {
798
+						//it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
799
+						//if it wasn't in it to begin with. So we're done
800
+						return $object_to_remove_or_index_into_array;
801
+					}
802
+				}
803
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
804
+				//so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
805
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
806
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
807
+						$index_in_cache = $index;
808
+					}
809
+				}
810
+			} else {
811
+				$index_in_cache = $object_to_remove_or_index_into_array;
812
+			}
813
+			//supposedly we've found it. But it could just be that the client code
814
+			//provided a bad index/object
815
+			if (
816
+			isset(
817
+				$this->_model_relations[$relationName],
818
+				$this->_model_relations[$relationName][$index_in_cache]
819
+			)
820
+			) {
821
+				$obj_removed = $this->_model_relations[$relationName][$index_in_cache];
822
+				unset($this->_model_relations[$relationName][$index_in_cache]);
823
+			} else {
824
+				//that thing was never cached anyways.
825
+				$obj_removed = null;
826
+			}
827
+		}
828
+		return $obj_removed;
829
+	}
830
+
831
+
832
+
833
+	/**
834
+	 * update_cache_after_object_save
835
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
836
+	 * obtained after being saved to the db
837
+	 *
838
+	 * @param string         $relationName       - the type of object that is cached
839
+	 * @param \EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
840
+	 * @param string         $current_cache_id   - the ID that was used when originally caching the object
841
+	 * @return boolean TRUE on success, FALSE on fail
842
+	 * @throws \EE_Error
843
+	 */
844
+	public function update_cache_after_object_save(
845
+		$relationName,
846
+		EE_Base_Class $newly_saved_object,
847
+		$current_cache_id = ''
848
+	) {
849
+		// verify that incoming object is of the correct type
850
+		$obj_class = 'EE_' . $relationName;
851
+		if ($newly_saved_object instanceof $obj_class) {
852
+			/* @type EE_Base_Class $newly_saved_object */
853
+			// now get the type of relation
854
+			$relationship_to_model = $this->get_model()->related_settings_for($relationName);
855
+			// if this is a 1:1 relationship
856
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
857
+				// then just replace the cached object with the newly saved object
858
+				$this->_model_relations[$relationName] = $newly_saved_object;
859
+				return true;
860
+				// or if it's some kind of sordid feral polyamorous relationship...
861
+			} elseif (is_array($this->_model_relations[$relationName])
862
+					  && isset($this->_model_relations[$relationName][$current_cache_id])
863
+			) {
864
+				// then remove the current cached item
865
+				unset($this->_model_relations[$relationName][$current_cache_id]);
866
+				// and cache the newly saved object using it's new ID
867
+				$this->_model_relations[$relationName][$newly_saved_object->ID()] = $newly_saved_object;
868
+				return true;
869
+			}
870
+		}
871
+		return false;
872
+	}
873
+
874
+
875
+
876
+	/**
877
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
878
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
879
+	 *
880
+	 * @param string $relationName
881
+	 * @return EE_Base_Class
882
+	 */
883
+	public function get_one_from_cache($relationName)
884
+	{
885
+		$cached_array_or_object = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName]
886
+			: null;
887
+		if (is_array($cached_array_or_object)) {
888
+			return array_shift($cached_array_or_object);
889
+		} else {
890
+			return $cached_array_or_object;
891
+		}
892
+	}
893
+
894
+
895
+
896
+	/**
897
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
898
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
899
+	 *
900
+	 * @param string $relationName
901
+	 * @throws \EE_Error
902
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
903
+	 */
904
+	public function get_all_from_cache($relationName)
905
+	{
906
+		$objects = isset($this->_model_relations[$relationName]) ? $this->_model_relations[$relationName] : array();
907
+		// if the result is not an array, but exists, make it an array
908
+		$objects = is_array($objects) ? $objects : array($objects);
909
+		//bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
910
+		//basically, if this model object was stored in the session, and these cached model objects
911
+		//already have IDs, let's make sure they're in their model's entity mapper
912
+		//otherwise we will have duplicates next time we call
913
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
914
+		$model = EE_Registry::instance()->load_model($relationName);
915
+		foreach ($objects as $model_object) {
916
+			if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
917
+				//ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
918
+				if ($model_object->ID()) {
919
+					$model->add_to_entity_map($model_object);
920
+				}
921
+			} else {
922
+				throw new EE_Error(
923
+					sprintf(
924
+						__(
925
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
926
+							'event_espresso'
927
+						),
928
+						$relationName,
929
+						gettype($model_object)
930
+					)
931
+				);
932
+			}
933
+		}
934
+		return $objects;
935
+	}
936
+
937
+
938
+
939
+	/**
940
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
941
+	 * matching the given query conditions.
942
+	 *
943
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
944
+	 * @param int   $limit              How many objects to return.
945
+	 * @param array $query_params       Any additional conditions on the query.
946
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
947
+	 *                                  you can indicate just the columns you want returned
948
+	 * @return array|EE_Base_Class[]
949
+	 * @throws \EE_Error
950
+	 */
951
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
952
+	{
953
+		$field = empty($field_to_order_by) && $this->get_model()->has_primary_key_field()
954
+			? $this->get_model()->get_primary_key_field()->get_name()
955
+			: $field_to_order_by;
956
+		$current_value = ! empty($field) ? $this->get($field) : null;
957
+		if (empty($field) || empty($current_value)) {
958
+			return array();
959
+		}
960
+		return $this->get_model()->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
961
+	}
962
+
963
+
964
+
965
+	/**
966
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
967
+	 * matching the given query conditions.
968
+	 *
969
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
970
+	 * @param int   $limit              How many objects to return.
971
+	 * @param array $query_params       Any additional conditions on the query.
972
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
973
+	 *                                  you can indicate just the columns you want returned
974
+	 * @return array|EE_Base_Class[]
975
+	 * @throws \EE_Error
976
+	 */
977
+	public function previous_x(
978
+		$field_to_order_by = null,
979
+		$limit = 1,
980
+		$query_params = array(),
981
+		$columns_to_select = null
982
+	) {
983
+		$field = empty($field_to_order_by) && $this->get_model()->has_primary_key_field()
984
+			? $this->get_model()->get_primary_key_field()->get_name()
985
+			: $field_to_order_by;
986
+		$current_value = ! empty($field) ? $this->get($field) : null;
987
+		if (empty($field) || empty($current_value)) {
988
+			return array();
989
+		}
990
+		return $this->get_model()->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
991
+	}
992
+
993
+
994
+
995
+	/**
996
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
997
+	 * matching the given query conditions.
998
+	 *
999
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1000
+	 * @param array $query_params       Any additional conditions on the query.
1001
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1002
+	 *                                  you can indicate just the columns you want returned
1003
+	 * @return array|EE_Base_Class
1004
+	 * @throws \EE_Error
1005
+	 */
1006
+	public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1007
+	{
1008
+		$field = empty($field_to_order_by) && $this->get_model()->has_primary_key_field()
1009
+			? $this->get_model()->get_primary_key_field()->get_name()
1010
+			: $field_to_order_by;
1011
+		$current_value = ! empty($field) ? $this->get($field) : null;
1012
+		if (empty($field) || empty($current_value)) {
1013
+			return array();
1014
+		}
1015
+		return $this->get_model()->next($current_value, $field, $query_params, $columns_to_select);
1016
+	}
1017
+
1018
+
1019
+
1020
+	/**
1021
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1022
+	 * matching the given query conditions.
1023
+	 *
1024
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1025
+	 * @param array $query_params       Any additional conditions on the query.
1026
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1027
+	 *                                  you can indicate just the column you want returned
1028
+	 * @return array|EE_Base_Class
1029
+	 * @throws \EE_Error
1030
+	 */
1031
+	public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1032
+	{
1033
+		$field = empty($field_to_order_by) && $this->get_model()->has_primary_key_field()
1034
+			? $this->get_model()->get_primary_key_field()->get_name()
1035
+			: $field_to_order_by;
1036
+		$current_value = ! empty($field) ? $this->get($field) : null;
1037
+		if (empty($field) || empty($current_value)) {
1038
+			return array();
1039
+		}
1040
+		return $this->get_model()->previous($current_value, $field, $query_params, $columns_to_select);
1041
+	}
1042
+
1043
+
1044
+
1045
+	/**
1046
+	 * Overrides parent because parent expects old models.
1047
+	 * This also doesn't do any validation, and won't work for serialized arrays
1048
+	 *
1049
+	 * @param string $field_name
1050
+	 * @param mixed  $field_value_from_db
1051
+	 * @throws \EE_Error
1052
+	 */
1053
+	public function set_from_db($field_name, $field_value_from_db)
1054
+	{
1055
+		$field_obj = $this->get_model()->field_settings_for($field_name);
1056
+		if ($field_obj instanceof EE_Model_Field_Base) {
1057
+			//you would think the DB has no NULLs for non-null label fields right? wrong!
1058
+			//eg, a CPT model object could have an entry in the posts table, but no
1059
+			//entry in the meta table. Meaning that all its columns in the meta table
1060
+			//are null! yikes! so when we find one like that, use defaults for its meta columns
1061
+			if ($field_value_from_db === null) {
1062
+				if ($field_obj->is_nullable()) {
1063
+					//if the field allows nulls, then let it be null
1064
+					$field_value = null;
1065
+				} else {
1066
+					$field_value = $field_obj->get_default_value();
1067
+				}
1068
+			} else {
1069
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1070
+			}
1071
+			$this->_fields[$field_name] = $field_value;
1072
+			$this->_clear_cached_property($field_name);
1073
+		}
1074
+	}
1075
+
1076
+
1077
+
1078
+	/**
1079
+	 * verifies that the specified field is of the correct type
1080
+	 *
1081
+	 * @param string $field_name
1082
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1083
+	 *                                (in cases where the same property may be used for different outputs
1084
+	 *                                - i.e. datetime, money etc.)
1085
+	 * @return mixed
1086
+	 * @throws \EE_Error
1087
+	 */
1088
+	public function get($field_name, $extra_cache_ref = null)
1089
+	{
1090
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1091
+	}
1092
+
1093
+
1094
+
1095
+	/**
1096
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1097
+	 *
1098
+	 * @param  string $field_name A valid fieldname
1099
+	 * @return mixed              Whatever the raw value stored on the property is.
1100
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1101
+	 */
1102
+	public function get_raw($field_name)
1103
+	{
1104
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1105
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
1106
+			? $this->_fields[$field_name]->format('U')
1107
+			: $this->_fields[$field_name];
1108
+	}
1109
+
1110
+
1111
+
1112
+	/**
1113
+	 * This is used to return the internal DateTime object used for a field that is a
1114
+	 * EE_Datetime_Field.
1115
+	 *
1116
+	 * @param string $field_name               The field name retrieving the DateTime object.
1117
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1118
+	 * @throws \EE_Error
1119
+	 *                                         an error is set and false returned.  If the field IS an
1120
+	 *                                         EE_Datetime_Field and but the field value is null, then
1121
+	 *                                         just null is returned (because that indicates that likely
1122
+	 *                                         this field is nullable).
1123
+	 */
1124
+	public function get_DateTime_object($field_name)
1125
+	{
1126
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1127
+		if ( ! $field_settings instanceof EE_Datetime_Field) {
1128
+			EE_Error::add_error(
1129
+				sprintf(
1130
+					__(
1131
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1132
+						'event_espresso'
1133
+					),
1134
+					$field_name
1135
+				),
1136
+				__FILE__,
1137
+				__FUNCTION__,
1138
+				__LINE__
1139
+			);
1140
+			return false;
1141
+		}
1142
+		return $this->_fields[$field_name];
1143
+	}
1144
+
1145
+
1146
+
1147
+	/**
1148
+	 * To be used in template to immediately echo out the value, and format it for output.
1149
+	 * Eg, should call stripslashes and whatnot before echoing
1150
+	 *
1151
+	 * @param string $field_name      the name of the field as it appears in the DB
1152
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1153
+	 *                                (in cases where the same property may be used for different outputs
1154
+	 *                                - i.e. datetime, money etc.)
1155
+	 * @return void
1156
+	 * @throws \EE_Error
1157
+	 */
1158
+	public function e($field_name, $extra_cache_ref = null)
1159
+	{
1160
+		echo $this->get_pretty($field_name, $extra_cache_ref);
1161
+	}
1162
+
1163
+
1164
+
1165
+	/**
1166
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1167
+	 * can be easily used as the value of form input.
1168
+	 *
1169
+	 * @param string $field_name
1170
+	 * @return void
1171
+	 * @throws \EE_Error
1172
+	 */
1173
+	public function f($field_name)
1174
+	{
1175
+		$this->e($field_name, 'form_input');
1176
+	}
1177
+
1178
+
1179
+
1180
+	/**
1181
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1182
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1183
+	 * to see what options are available.
1184
+	 * @param string $field_name
1185
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1186
+	 *                                (in cases where the same property may be used for different outputs
1187
+	 *                                - i.e. datetime, money etc.)
1188
+	 * @return mixed
1189
+	 * @throws \EE_Error
1190
+	 */
1191
+	public function get_pretty($field_name, $extra_cache_ref = null)
1192
+	{
1193
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1194
+	}
1195
+
1196
+
1197
+
1198
+	/**
1199
+	 * This simply returns the datetime for the given field name
1200
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1201
+	 * (and the equivalent e_date, e_time, e_datetime).
1202
+	 *
1203
+	 * @access   protected
1204
+	 * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1205
+	 * @param string   $dt_frmt      valid datetime format used for date
1206
+	 *                               (if '' then we just use the default on the field,
1207
+	 *                               if NULL we use the last-used format)
1208
+	 * @param string   $tm_frmt      Same as above except this is for time format
1209
+	 * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1210
+	 * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1211
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1212
+	 *                               if field is not a valid dtt field, or void if echoing
1213
+	 * @throws \EE_Error
1214
+	 */
1215
+	protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1216
+	{
1217
+		// clear cached property
1218
+		$this->_clear_cached_property($field_name);
1219
+		//reset format properties because they are used in get()
1220
+		$this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1221
+		$this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1222
+		if ($echo) {
1223
+			$this->e($field_name, $date_or_time);
1224
+			return '';
1225
+		}
1226
+		return $this->get($field_name, $date_or_time);
1227
+	}
1228
+
1229
+
1230
+
1231
+	/**
1232
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1233
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1234
+	 * other echoes the pretty value for dtt)
1235
+	 *
1236
+	 * @param  string $field_name name of model object datetime field holding the value
1237
+	 * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1238
+	 * @return string            datetime value formatted
1239
+	 * @throws \EE_Error
1240
+	 */
1241
+	public function get_date($field_name, $format = '')
1242
+	{
1243
+		return $this->_get_datetime($field_name, $format, null, 'D');
1244
+	}
1245
+
1246
+
1247
+
1248
+	/**
1249
+	 * @param      $field_name
1250
+	 * @param string $format
1251
+	 * @throws \EE_Error
1252
+	 */
1253
+	public function e_date($field_name, $format = '')
1254
+	{
1255
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1256
+	}
1257
+
1258
+
1259
+
1260
+	/**
1261
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1262
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1263
+	 * other echoes the pretty value for dtt)
1264
+	 *
1265
+	 * @param  string $field_name name of model object datetime field holding the value
1266
+	 * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1267
+	 * @return string             datetime value formatted
1268
+	 * @throws \EE_Error
1269
+	 */
1270
+	public function get_time($field_name, $format = '')
1271
+	{
1272
+		return $this->_get_datetime($field_name, null, $format, 'T');
1273
+	}
1274
+
1275
+
1276
+
1277
+	/**
1278
+	 * @param      $field_name
1279
+	 * @param string $format
1280
+	 * @throws \EE_Error
1281
+	 */
1282
+	public function e_time($field_name, $format = '')
1283
+	{
1284
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1285
+	}
1286
+
1287
+
1288
+
1289
+	/**
1290
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1291
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1292
+	 * other echoes the pretty value for dtt)
1293
+	 *
1294
+	 * @param  string $field_name name of model object datetime field holding the value
1295
+	 * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1296
+	 * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1297
+	 * @return string             datetime value formatted
1298
+	 * @throws \EE_Error
1299
+	 */
1300
+	public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1301
+	{
1302
+		return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1303
+	}
1304
+
1305
+
1306
+
1307
+	/**
1308
+	 * @param string $field_name
1309
+	 * @param string $dt_frmt
1310
+	 * @param string $tm_frmt
1311
+	 * @throws \EE_Error
1312
+	 */
1313
+	public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1314
+	{
1315
+		$this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1316
+	}
1317
+
1318
+
1319
+
1320
+	/**
1321
+	 * Get the i8ln value for a date using the WordPress @see date_i18n function.
1322
+	 *
1323
+	 * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1324
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1325
+	 *                           on the object will be used.
1326
+	 * @return string Date and time string in set locale or false if no field exists for the given
1327
+	 * @throws \EE_Error
1328
+	 *                           field name.
1329
+	 */
1330
+	public function get_i18n_datetime($field_name, $format = '')
1331
+	{
1332
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1333
+		return date_i18n(
1334
+			$format,
1335
+			EEH_DTT_Helper::get_timestamp_with_offset($this->get_raw($field_name), $this->_timezone)
1336
+		);
1337
+	}
1338
+
1339
+
1340
+
1341
+	/**
1342
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
1343
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1344
+	 * thrown.
1345
+	 *
1346
+	 * @param  string $field_name The field name being checked
1347
+	 * @throws EE_Error
1348
+	 * @return EE_Datetime_Field
1349
+	 */
1350
+	protected function _get_dtt_field_settings($field_name)
1351
+	{
1352
+		$field = $this->get_model()->field_settings_for($field_name);
1353
+		//check if field is dtt
1354
+		if ($field instanceof EE_Datetime_Field) {
1355
+			return $field;
1356
+		} else {
1357
+			throw new EE_Error(sprintf(__('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',
1358
+				'event_espresso'), $field_name, self::_get_model_classname(get_class($this))));
1359
+		}
1360
+	}
1361
+
1362
+
1363
+
1364
+
1365
+	/**
1366
+	 * NOTE ABOUT BELOW:
1367
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1368
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1369
+	 * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1370
+	 * method and make sure you send the entire datetime value for setting.
1371
+	 */
1372
+	/**
1373
+	 * sets the time on a datetime property
1374
+	 *
1375
+	 * @access protected
1376
+	 * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1377
+	 * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1378
+	 * @throws \EE_Error
1379
+	 */
1380
+	protected function _set_time_for($time, $fieldname)
1381
+	{
1382
+		$this->_set_date_time('T', $time, $fieldname);
1383
+	}
1384
+
1385
+
1386
+
1387
+	/**
1388
+	 * sets the date on a datetime property
1389
+	 *
1390
+	 * @access protected
1391
+	 * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1392
+	 * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1393
+	 * @throws \EE_Error
1394
+	 */
1395
+	protected function _set_date_for($date, $fieldname)
1396
+	{
1397
+		$this->_set_date_time('D', $date, $fieldname);
1398
+	}
1399
+
1400
+
1401
+
1402
+	/**
1403
+	 * This takes care of setting a date or time independently on a given model object property. This method also
1404
+	 * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1405
+	 *
1406
+	 * @access protected
1407
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1408
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1409
+	 * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1410
+	 *                                        EE_Datetime_Field property)
1411
+	 * @throws \EE_Error
1412
+	 */
1413
+	protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1414
+	{
1415
+		$field = $this->_get_dtt_field_settings($fieldname);
1416
+		$field->set_timezone($this->_timezone);
1417
+		$field->set_date_format($this->_dt_frmt);
1418
+		$field->set_time_format($this->_tm_frmt);
1419
+		switch ($what) {
1420
+			case 'T' :
1421
+				$this->_fields[$fieldname] = $field->prepare_for_set_with_new_time(
1422
+					$datetime_value,
1423
+					$this->_fields[$fieldname]
1424
+				);
1425
+				break;
1426
+			case 'D' :
1427
+				$this->_fields[$fieldname] = $field->prepare_for_set_with_new_date(
1428
+					$datetime_value,
1429
+					$this->_fields[$fieldname]
1430
+				);
1431
+				break;
1432
+			case 'B' :
1433
+				$this->_fields[$fieldname] = $field->prepare_for_set($datetime_value);
1434
+				break;
1435
+		}
1436
+		$this->_clear_cached_property($fieldname);
1437
+	}
1438
+
1439
+
1440
+
1441
+	/**
1442
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1443
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1444
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1445
+	 * that could lead to some unexpected results!
1446
+	 *
1447
+	 * @access public
1448
+	 * @param string               $field_name This is the name of the field on the object that contains the date/time
1449
+	 *                                         value being returned.
1450
+	 * @param string               $callback   must match a valid method in this class (defaults to get_datetime)
1451
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1452
+	 * @param string               $prepend    You can include something to prepend on the timestamp
1453
+	 * @param string               $append     You can include something to append on the timestamp
1454
+	 * @throws EE_Error
1455
+	 * @return string timestamp
1456
+	 */
1457
+	public function display_in_my_timezone(
1458
+		$field_name,
1459
+		$callback = 'get_datetime',
1460
+		$args = null,
1461
+		$prepend = '',
1462
+		$append = ''
1463
+	) {
1464
+		$timezone = EEH_DTT_Helper::get_timezone();
1465
+		if ($timezone === $this->_timezone) {
1466
+			return '';
1467
+		}
1468
+		$original_timezone = $this->_timezone;
1469
+		$this->set_timezone($timezone);
1470
+		$fn = (array)$field_name;
1471
+		$args = array_merge($fn, (array)$args);
1472
+		if ( ! method_exists($this, $callback)) {
1473
+			throw new EE_Error(
1474
+				sprintf(
1475
+					__(
1476
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1477
+						'event_espresso'
1478
+					),
1479
+					$callback
1480
+				)
1481
+			);
1482
+		}
1483
+		$args = (array)$args;
1484
+		$return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1485
+		$this->set_timezone($original_timezone);
1486
+		return $return;
1487
+	}
1488
+
1489
+
1490
+
1491
+	/**
1492
+	 * Deletes this model object.
1493
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1494
+	 * override
1495
+	 * `EE_Base_Class::_delete` NOT this class.
1496
+	 *
1497
+	 * @return boolean | int
1498
+	 * @throws \EE_Error
1499
+	 */
1500
+	public function delete()
1501
+	{
1502
+		/**
1503
+		 * Called just before the `EE_Base_Class::_delete` method call.
1504
+		 * Note: `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1505
+		 * should be aware that `_delete` may not always result in a permanent delete.  For example, `EE_Soft_Delete_Base_Class::_delete`
1506
+		 * soft deletes (trash) the object and does not permanently delete it.
1507
+		 *
1508
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1509
+		 */
1510
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
1511
+		$result = $this->_delete();
1512
+		/**
1513
+		 * Called just after the `EE_Base_Class::_delete` method call.
1514
+		 * Note: `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1515
+		 * should be aware that `_delete` may not always result in a permanent delete.  For example `EE_Soft_Base_Class::_delete`
1516
+		 * soft deletes (trash) the object and does not permanently delete it.
1517
+		 *
1518
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1519
+		 * @param boolean       $result
1520
+		 */
1521
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1522
+		return $result;
1523
+	}
1524
+
1525
+
1526
+
1527
+	/**
1528
+	 * Calls the specific delete method for the instantiated class.
1529
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1530
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1531
+	 * `EE_Base_Class::delete`
1532
+	 *
1533
+	 * @return bool|int
1534
+	 * @throws \EE_Error
1535
+	 */
1536
+	protected function _delete()
1537
+	{
1538
+		return $this->delete_permanently();
1539
+	}
1540
+
1541
+
1542
+
1543
+	/**
1544
+	 * Deletes this model object permanently from db (but keep in mind related models my block the delete and return an
1545
+	 * error)
1546
+	 *
1547
+	 * @return bool | int
1548
+	 * @throws \EE_Error
1549
+	 */
1550
+	public function delete_permanently()
1551
+	{
1552
+		/**
1553
+		 * Called just before HARD deleting a model object
1554
+		 *
1555
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1556
+		 */
1557
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1558
+		$model = $this->get_model();
1559
+		$result = $model->delete_permanently_by_ID($this->ID());
1560
+		$this->refresh_cache_of_related_objects();
1561
+		/**
1562
+		 * Called just after HARD deleting a model object
1563
+		 *
1564
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1565
+		 * @param boolean       $result
1566
+		 */
1567
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1568
+		return $result;
1569
+	}
1570
+
1571
+
1572
+
1573
+	/**
1574
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1575
+	 * related model objects
1576
+	 *
1577
+	 * @throws \EE_Error
1578
+	 */
1579
+	public function refresh_cache_of_related_objects()
1580
+	{
1581
+		foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
1582
+			if ( ! empty($this->_model_relations[$relation_name])) {
1583
+				$related_objects = $this->_model_relations[$relation_name];
1584
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
1585
+					//this relation only stores a single model object, not an array
1586
+					//but let's make it consistent
1587
+					$related_objects = array($related_objects);
1588
+				}
1589
+				foreach ($related_objects as $related_object) {
1590
+					//only refresh their cache if they're in memory
1591
+					if ($related_object instanceof EE_Base_Class) {
1592
+						$related_object->clear_cache($this->get_model()->get_this_model_name(), $this);
1593
+					}
1594
+				}
1595
+			}
1596
+		}
1597
+	}
1598
+
1599
+
1600
+
1601
+	/**
1602
+	 *        Saves this object to the database. An array may be supplied to set some values on this
1603
+	 * object just before saving.
1604
+	 *
1605
+	 * @access public
1606
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
1607
+	 *                                 if provided during the save() method (often client code will change the fields'
1608
+	 *                                 values before calling save)
1609
+	 * @throws \EE_Error
1610
+	 * @return int , 1 on a successful update, the ID of the new entry on insert; 0 on failure or if the model object
1611
+	 *                                 isn't allowed to persist (as determined by EE_Base_Class::allow_persist())
1612
+	 */
1613
+	public function save($set_cols_n_values = array())
1614
+	{
1615
+		/**
1616
+		 * Filters the fields we're about to save on the model object
1617
+		 *
1618
+		 * @param array         $set_cols_n_values
1619
+		 * @param EE_Base_Class $model_object
1620
+		 */
1621
+		$set_cols_n_values = (array)apply_filters('FHEE__EE_Base_Class__save__set_cols_n_values', $set_cols_n_values,
1622
+			$this);
1623
+		//set attributes as provided in $set_cols_n_values
1624
+		foreach ($set_cols_n_values as $column => $value) {
1625
+			$this->set($column, $value);
1626
+		}
1627
+		// no changes ? then don't do anything
1628
+		if (! $this->_has_changes && $this->ID() && $this->get_model()->get_primary_key_field()->is_auto_increment()) {
1629
+			return 0;
1630
+		}
1631
+		/**
1632
+		 * Saving a model object.
1633
+		 * Before we perform a save, this action is fired.
1634
+		 *
1635
+		 * @param EE_Base_Class $model_object the model object about to be saved.
1636
+		 */
1637
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
1638
+		if ( ! $this->allow_persist()) {
1639
+			return 0;
1640
+		}
1641
+		//now get current attribute values
1642
+		$save_cols_n_values = $this->_fields;
1643
+		//if the object already has an ID, update it. Otherwise, insert it
1644
+		//also: change the assumption about values passed to the model NOT being prepare dby the model object. They have been
1645
+		$old_assumption_concerning_value_preparation = $this->get_model()
1646
+															->get_assumption_concerning_values_already_prepared_by_model_object();
1647
+		$this->get_model()->assume_values_already_prepared_by_model_object(true);
1648
+		//does this model have an autoincrement PK?
1649
+		if ($this->get_model()->has_primary_key_field()) {
1650
+			if ($this->get_model()->get_primary_key_field()->is_auto_increment()) {
1651
+				//ok check if it's set, if so: update; if not, insert
1652
+				if ( ! empty($save_cols_n_values[self::_get_primary_key_name(get_class($this))])) {
1653
+					$results = $this->get_model()->update_by_ID($save_cols_n_values, $this->ID());
1654
+				} else {
1655
+					unset($save_cols_n_values[self::_get_primary_key_name(get_class($this))]);
1656
+					$results = $this->get_model()->insert($save_cols_n_values);
1657
+					if ($results) {
1658
+						//if successful, set the primary key
1659
+						//but don't use the normal SET method, because it will check if
1660
+						//an item with the same ID exists in the mapper & db, then
1661
+						//will find it in the db (because we just added it) and THAT object
1662
+						//will get added to the mapper before we can add this one!
1663
+						//but if we just avoid using the SET method, all that headache can be avoided
1664
+						$pk_field_name = self::_get_primary_key_name(get_class($this));
1665
+						$this->_fields[$pk_field_name] = $results;
1666
+						$this->_clear_cached_property($pk_field_name);
1667
+						$this->get_model()->add_to_entity_map($this);
1668
+						$this->_update_cached_related_model_objs_fks();
1669
+					}
1670
+				}
1671
+			} else {//PK is NOT auto-increment
1672
+				//so check if one like it already exists in the db
1673
+				if ($this->get_model()->exists_by_ID($this->ID())) {
1674
+					if (WP_DEBUG && ! $this->in_entity_map()) {
1675
+						throw new EE_Error(
1676
+							sprintf(
1677
+								__('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',
1678
+									'event_espresso'),
1679
+								get_class($this),
1680
+								get_class($this->get_model()) . '::instance()->add_to_entity_map()',
1681
+								get_class($this->get_model()) . '::instance()->get_one_by_ID()',
1682
+								'<br />'
1683
+							)
1684
+						);
1685
+					}
1686
+					$results = $this->get_model()->update_by_ID($save_cols_n_values, $this->ID());
1687
+				} else {
1688
+					$results = $this->get_model()->insert($save_cols_n_values);
1689
+					$this->_update_cached_related_model_objs_fks();
1690
+				}
1691
+			}
1692
+		} else {//there is NO primary key
1693
+			$already_in_db = false;
1694
+			foreach ($this->get_model()->unique_indexes() as $index) {
1695
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1696
+				if ($this->get_model()->exists(array($uniqueness_where_params))) {
1697
+					$already_in_db = true;
1698
+				}
1699
+			}
1700
+			if ($already_in_db) {
1701
+				$combined_pk_fields_n_values = array_intersect_key($save_cols_n_values,
1702
+					$this->get_model()->get_combined_primary_key_fields());
1703
+				$results = $this->get_model()->update($save_cols_n_values, $combined_pk_fields_n_values);
1704
+			} else {
1705
+				$results = $this->get_model()->insert($save_cols_n_values);
1706
+			}
1707
+		}
1708
+		//restore the old assumption about values being prepared by the model object
1709
+		$this->get_model()
1710
+			 ->assume_values_already_prepared_by_model_object($old_assumption_concerning_value_preparation);
1711
+		/**
1712
+		 * After saving the model object this action is called
1713
+		 *
1714
+		 * @param EE_Base_Class $model_object which was just saved
1715
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1716
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1717
+		 */
1718
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1719
+		$this->_has_changes = false;
1720
+		return $results;
1721
+	}
1722
+
1723
+
1724
+
1725
+	/**
1726
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1727
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1728
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1729
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1730
+	 * 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
1731
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1732
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1733
+	 *
1734
+	 * @return void
1735
+	 * @throws \EE_Error
1736
+	 */
1737
+	protected function _update_cached_related_model_objs_fks()
1738
+	{
1739
+		foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
1740
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1741
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1742
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1743
+						$this->get_model()->get_this_model_name()
1744
+					);
1745
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1746
+					if ($related_model_obj_in_cache->ID()) {
1747
+						$related_model_obj_in_cache->save();
1748
+					}
1749
+				}
1750
+			}
1751
+		}
1752
+	}
1753
+
1754
+
1755
+
1756
+	/**
1757
+	 * Saves this model object and its NEW cached relations to the database.
1758
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1759
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
1760
+	 * because otherwise, there's a potential for infinite looping of saving
1761
+	 * Saves the cached related model objects, and ensures the relation between them
1762
+	 * and this object and properly setup
1763
+	 *
1764
+	 * @return int ID of new model object on save; 0 on failure+
1765
+	 * @throws \EE_Error
1766
+	 */
1767
+	public function save_new_cached_related_model_objs()
1768
+	{
1769
+		//make sure this has been saved
1770
+		if ( ! $this->ID()) {
1771
+			$id = $this->save();
1772
+		} else {
1773
+			$id = $this->ID();
1774
+		}
1775
+		//now save all the NEW cached model objects  (ie they don't exist in the DB)
1776
+		foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1777
+			if ($this->_model_relations[$relationName]) {
1778
+				//is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1779
+				//or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1780
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
1781
+					//add a relation to that relation type (which saves the appropriate thing in the process)
1782
+					//but ONLY if it DOES NOT exist in the DB
1783
+					/* @var $related_model_obj EE_Base_Class */
1784
+					$related_model_obj = $this->_model_relations[$relationName];
1785
+					//					if( ! $related_model_obj->ID()){
1786
+					$this->_add_relation_to($related_model_obj, $relationName);
1787
+					$related_model_obj->save_new_cached_related_model_objs();
1788
+					//					}
1789
+				} else {
1790
+					foreach ($this->_model_relations[$relationName] as $related_model_obj) {
1791
+						//add a relation to that relation type (which saves the appropriate thing in the process)
1792
+						//but ONLY if it DOES NOT exist in the DB
1793
+						//						if( ! $related_model_obj->ID()){
1794
+						$this->_add_relation_to($related_model_obj, $relationName);
1795
+						$related_model_obj->save_new_cached_related_model_objs();
1796
+						//						}
1797
+					}
1798
+				}
1799
+			}
1800
+		}
1801
+		return $id;
1802
+	}
1803
+
1804
+
1805
+
1806
+	/**
1807
+	 * for getting a model while instantiated.
1808
+	 *
1809
+	 * @return \EEM_Base | \EEM_CPT_Base
1810
+	 */
1811
+	public function get_model()
1812
+	{
1813
+		$modelName = self::_get_model_classname(get_class($this));
1814
+		return self::_get_model_instance_with_name($modelName, $this->_timezone);
1815
+	}
1816
+
1817
+
1818
+
1819
+	/**
1820
+	 * @param $props_n_values
1821
+	 * @param $classname
1822
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
1823
+	 * @throws \EE_Error
1824
+	 */
1825
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
1826
+	{
1827
+		//TODO: will not work for Term_Relationships because they have no PK!
1828
+		$primary_id_ref = self::_get_primary_key_name($classname);
1829
+		if (array_key_exists($primary_id_ref, $props_n_values) && ! empty($props_n_values[$primary_id_ref])) {
1830
+			$id = $props_n_values[$primary_id_ref];
1831
+			return self::_get_model($classname)->get_from_entity_map($id);
1832
+		}
1833
+		return false;
1834
+	}
1835
+
1836
+
1837
+
1838
+	/**
1839
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
1840
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
1841
+	 * 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
1842
+	 * we return false.
1843
+	 *
1844
+	 * @param  array  $props_n_values   incoming array of properties and their values
1845
+	 * @param  string $classname        the classname of the child class
1846
+	 * @param null    $timezone
1847
+	 * @param array   $date_formats     incoming date_formats in an array where the first value is the
1848
+	 *                                  date_format and the second value is the time format
1849
+	 * @return mixed (EE_Base_Class|bool)
1850
+	 * @throws \EE_Error
1851
+	 */
1852
+	protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
1853
+	{
1854
+		$existing = null;
1855
+		if (self::_get_model($classname)->has_primary_key_field()) {
1856
+			$primary_id_ref = self::_get_primary_key_name($classname);
1857
+			if (array_key_exists($primary_id_ref, $props_n_values)
1858
+				&& ! empty($props_n_values[$primary_id_ref])
1859
+			) {
1860
+				$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
1861
+					$props_n_values[$primary_id_ref]
1862
+				);
1863
+			}
1864
+		} elseif (self::_get_model($classname, $timezone)->has_all_combined_primary_key_fields($props_n_values)) {
1865
+			//no primary key on this model, but there's still a matching item in the DB
1866
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
1867
+				self::_get_model($classname, $timezone)->get_index_primary_key_string($props_n_values)
1868
+			);
1869
+		}
1870
+		if ($existing) {
1871
+			//set date formats if present before setting values
1872
+			if ( ! empty($date_formats) && is_array($date_formats)) {
1873
+				$existing->set_date_format($date_formats[0]);
1874
+				$existing->set_time_format($date_formats[1]);
1875
+			} else {
1876
+				//set default formats for date and time
1877
+				$existing->set_date_format(get_option('date_format'));
1878
+				$existing->set_time_format(get_option('time_format'));
1879
+			}
1880
+			foreach ($props_n_values as $property => $field_value) {
1881
+				$existing->set($property, $field_value);
1882
+			}
1883
+			return $existing;
1884
+		} else {
1885
+			return false;
1886
+		}
1887
+	}
1888
+
1889
+
1890
+
1891
+	/**
1892
+	 * Gets the EEM_*_Model for this class
1893
+	 *
1894
+	 * @access public now, as this is more convenient
1895
+	 * @param      $classname
1896
+	 * @param null $timezone
1897
+	 * @throws EE_Error
1898
+	 * @return EEM_Base
1899
+	 */
1900
+	protected static function _get_model($classname, $timezone = null)
1901
+	{
1902
+		//find model for this class
1903
+		if ( ! $classname) {
1904
+			throw new EE_Error(
1905
+				sprintf(
1906
+					__(
1907
+						"What were you thinking calling _get_model(%s)?? You need to specify the class name",
1908
+						"event_espresso"
1909
+					),
1910
+					$classname
1911
+				)
1912
+			);
1913
+		}
1914
+		$modelName = self::_get_model_classname($classname);
1915
+		return self::_get_model_instance_with_name($modelName, $timezone);
1916
+	}
1917
+
1918
+
1919
+
1920
+	/**
1921
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
1922
+	 *
1923
+	 * @param string $model_classname
1924
+	 * @param null   $timezone
1925
+	 * @return EEM_Base
1926
+	 */
1927
+	protected static function _get_model_instance_with_name($model_classname, $timezone = null)
1928
+	{
1929
+		$model_classname = str_replace('EEM_', '', $model_classname);
1930
+		$model = EE_Registry::instance()->load_model($model_classname);
1931
+		$model->set_timezone($timezone);
1932
+		return $model;
1933
+	}
1934
+
1935
+
1936
+
1937
+	/**
1938
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
1939
+	 * Also works if a model class's classname is provided (eg EE_Registration).
1940
+	 *
1941
+	 * @param null $model_name
1942
+	 * @return string like EEM_Attendee
1943
+	 */
1944
+	private static function _get_model_classname($model_name = null)
1945
+	{
1946
+		if (strpos($model_name, "EE_") === 0) {
1947
+			$model_classname = str_replace("EE_", "EEM_", $model_name);
1948
+		} else {
1949
+			$model_classname = "EEM_" . $model_name;
1950
+		}
1951
+		return $model_classname;
1952
+	}
1953
+
1954
+
1955
+
1956
+	/**
1957
+	 * returns the name of the primary key attribute
1958
+	 *
1959
+	 * @param null $classname
1960
+	 * @throws EE_Error
1961
+	 * @return string
1962
+	 */
1963
+	protected static function _get_primary_key_name($classname = null)
1964
+	{
1965
+		if ( ! $classname) {
1966
+			throw new EE_Error(
1967
+				sprintf(
1968
+					__("What were you thinking calling _get_primary_key_name(%s)", "event_espresso"),
1969
+					$classname
1970
+				)
1971
+			);
1972
+		}
1973
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
1974
+	}
1975
+
1976
+
1977
+
1978
+	/**
1979
+	 * Gets the value of the primary key.
1980
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
1981
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
1982
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
1983
+	 *
1984
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
1985
+	 * @throws \EE_Error
1986
+	 */
1987
+	public function ID()
1988
+	{
1989
+		//now that we know the name of the variable, use a variable variable to get its value and return its
1990
+		if ($this->get_model()->has_primary_key_field()) {
1991
+			return $this->_fields[self::_get_primary_key_name(get_class($this))];
1992
+		} else {
1993
+			return $this->get_model()->get_index_primary_key_string($this->_fields);
1994
+		}
1995
+	}
1996
+
1997
+
1998
+
1999
+	/**
2000
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2001
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2002
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2003
+	 *
2004
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2005
+	 * @param string $relationName                     eg 'Events','Question',etc.
2006
+	 *                                                 an attendee to a group, you also want to specify which role they
2007
+	 *                                                 will have in that group. So you would use this parameter to
2008
+	 *                                                 specify array('role-column-name'=>'role-id')
2009
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2010
+	 *                                                 allow you to further constrict the relation to being added.
2011
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2012
+	 *                                                 column on the JOIN table and currently only the HABTM models
2013
+	 *                                                 accept these additional conditions.  Also remember that if an
2014
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2015
+	 *                                                 NEW row is created in the join table.
2016
+	 * @param null   $cache_id
2017
+	 * @throws EE_Error
2018
+	 * @return EE_Base_Class the object the relation was added to
2019
+	 */
2020
+	public function _add_relation_to(
2021
+		$otherObjectModelObjectOrID,
2022
+		$relationName,
2023
+		$extra_join_model_fields_n_values = array(),
2024
+		$cache_id = null
2025
+	) {
2026
+		//if this thing exists in the DB, save the relation to the DB
2027
+		if ($this->ID()) {
2028
+			$otherObject = $this->get_model()
2029
+								->add_relationship_to($this, $otherObjectModelObjectOrID, $relationName,
2030
+									$extra_join_model_fields_n_values);
2031
+			//clear cache so future get_many_related and get_first_related() return new results.
2032
+			$this->clear_cache($relationName, $otherObject, true);
2033
+			if ($otherObject instanceof EE_Base_Class) {
2034
+				$otherObject->clear_cache($this->get_model()->get_this_model_name(), $this);
2035
+			}
2036
+		} else {
2037
+			//this thing doesn't exist in the DB,  so just cache it
2038
+			if ( ! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2039
+				throw new EE_Error(sprintf(
2040
+					__('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',
2041
+						'event_espresso'),
2042
+					$otherObjectModelObjectOrID,
2043
+					get_class($this)
2044
+				));
2045
+			} else {
2046
+				$otherObject = $otherObjectModelObjectOrID;
2047
+			}
2048
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2049
+		}
2050
+		if ($otherObject instanceof EE_Base_Class) {
2051
+			//fix the reciprocal relation too
2052
+			if ($otherObject->ID()) {
2053
+				//its saved so assumed relations exist in the DB, so we can just
2054
+				//clear the cache so future queries use the updated info in the DB
2055
+				$otherObject->clear_cache($this->get_model()->get_this_model_name(), null, true);
2056
+			} else {
2057
+				//it's not saved, so it caches relations like this
2058
+				$otherObject->cache($this->get_model()->get_this_model_name(), $this);
2059
+			}
2060
+		}
2061
+		return $otherObject;
2062
+	}
2063
+
2064
+
2065
+
2066
+	/**
2067
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2068
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2069
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2070
+	 * from the cache
2071
+	 *
2072
+	 * @param mixed  $otherObjectModelObjectOrID
2073
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2074
+	 *                to the DB yet
2075
+	 * @param string $relationName
2076
+	 * @param array  $where_query
2077
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2078
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2079
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2080
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then a NEW row is
2081
+	 *                created in the join table.
2082
+	 * @return EE_Base_Class the relation was removed from
2083
+	 * @throws \EE_Error
2084
+	 */
2085
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2086
+	{
2087
+		if ($this->ID()) {
2088
+			//if this exists in the DB, save the relation change to the DB too
2089
+			$otherObject = $this->get_model()
2090
+								->remove_relationship_to($this, $otherObjectModelObjectOrID, $relationName,
2091
+									$where_query);
2092
+			$this->clear_cache($relationName, $otherObject);
2093
+		} else {
2094
+			//this doesn't exist in the DB, just remove it from the cache
2095
+			$otherObject = $this->clear_cache($relationName, $otherObjectModelObjectOrID);
2096
+		}
2097
+		if ($otherObject instanceof EE_Base_Class) {
2098
+			$otherObject->clear_cache($this->get_model()->get_this_model_name(), $this);
2099
+		}
2100
+		return $otherObject;
2101
+	}
2102
+
2103
+
2104
+
2105
+	/**
2106
+	 * Removes ALL the related things for the $relationName.
2107
+	 *
2108
+	 * @param string $relationName
2109
+	 * @param array  $where_query_params like EEM_Base::get_all's $query_params[0] (where conditions)
2110
+	 * @return EE_Base_Class
2111
+	 * @throws \EE_Error
2112
+	 */
2113
+	public function _remove_relations($relationName, $where_query_params = array())
2114
+	{
2115
+		if ($this->ID()) {
2116
+			//if this exists in the DB, save the relation change to the DB too
2117
+			$otherObjects = $this->get_model()->remove_relations($this, $relationName, $where_query_params);
2118
+			$this->clear_cache($relationName, null, true);
2119
+		} else {
2120
+			//this doesn't exist in the DB, just remove it from the cache
2121
+			$otherObjects = $this->clear_cache($relationName, null, true);
2122
+		}
2123
+		if (is_array($otherObjects)) {
2124
+			foreach ($otherObjects as $otherObject) {
2125
+				$otherObject->clear_cache($this->get_model()->get_this_model_name(), $this);
2126
+			}
2127
+		}
2128
+		return $otherObjects;
2129
+	}
2130
+
2131
+
2132
+
2133
+	/**
2134
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
2135
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2136
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2137
+	 * because we want to get even deleted items etc.
2138
+	 *
2139
+	 * @param string $relationName key in the model's _model_relations array
2140
+	 * @param array  $query_params like EEM_Base::get_all
2141
+	 * @return EE_Base_Class[] Results not necessarily indexed by IDs, because some results might not have primary keys
2142
+	 * @throws \EE_Error
2143
+	 *                             or might not be saved yet. Consider using EEM_Base::get_IDs() on these results if
2144
+	 *                             you want IDs
2145
+	 */
2146
+	public function get_many_related($relationName, $query_params = array())
2147
+	{
2148
+		if ($this->ID()) {
2149
+			//this exists in the DB, so get the related things from either the cache or the DB
2150
+			//if there are query parameters, forget about caching the related model objects.
2151
+			if ($query_params) {
2152
+				$related_model_objects = $this->get_model()->get_all_related($this, $relationName, $query_params);
2153
+			} else {
2154
+				//did we already cache the result of this query?
2155
+				$cached_results = $this->get_all_from_cache($relationName);
2156
+				if ( ! $cached_results) {
2157
+					$related_model_objects = $this->get_model()->get_all_related($this, $relationName, $query_params);
2158
+					//if no query parameters were passed, then we got all the related model objects
2159
+					//for that relation. We can cache them then.
2160
+					foreach ($related_model_objects as $related_model_object) {
2161
+						$this->cache($relationName, $related_model_object);
2162
+					}
2163
+				} else {
2164
+					$related_model_objects = $cached_results;
2165
+				}
2166
+			}
2167
+		} else {
2168
+			//this doesn't exist in the DB, so just get the related things from the cache
2169
+			$related_model_objects = $this->get_all_from_cache($relationName);
2170
+		}
2171
+		return $related_model_objects;
2172
+	}
2173
+
2174
+
2175
+
2176
+	/**
2177
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2178
+	 * unless otherwise specified in the $query_params
2179
+	 *
2180
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2181
+	 * @param array  $query_params   like EEM_Base::get_all's
2182
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2183
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2184
+	 *                               that by the setting $distinct to TRUE;
2185
+	 * @return int
2186
+	 */
2187
+	public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2188
+	{
2189
+		return $this->get_model()->count_related($this, $relation_name, $query_params, $field_to_count, $distinct);
2190
+	}
2191
+
2192
+
2193
+
2194
+	/**
2195
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2196
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2197
+	 *
2198
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2199
+	 * @param array  $query_params  like EEM_Base::get_all's
2200
+	 * @param string $field_to_sum  name of field to count by.
2201
+	 *                              By default, uses primary key (which doesn't make much sense, so you should probably
2202
+	 *                              change it)
2203
+	 * @return int
2204
+	 */
2205
+	public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2206
+	{
2207
+		return $this->get_model()->sum_related($this, $relation_name, $query_params, $field_to_sum);
2208
+	}
2209
+
2210
+
2211
+
2212
+	/**
2213
+	 * Gets the first (ie, one) related model object of the specified type.
2214
+	 *
2215
+	 * @param string $relationName key in the model's _model_relations array
2216
+	 * @param array  $query_params like EEM_Base::get_all
2217
+	 * @return EE_Base_Class (not an array, a single object)
2218
+	 * @throws \EE_Error
2219
+	 */
2220
+	public function get_first_related($relationName, $query_params = array())
2221
+	{
2222
+		if ($this->ID()) {//this exists in the DB, get from the cache OR the DB
2223
+			//if they've provided some query parameters, don't bother trying to cache the result
2224
+			//also make sure we're not caching the result of get_first_related
2225
+			//on a relation which should have an array of objects (because the cache might have an array of objects)
2226
+			if ($query_params
2227
+				|| ! $this->get_model()->related_settings_for($relationName)
2228
+					 instanceof
2229
+					 EE_Belongs_To_Relation
2230
+			) {
2231
+				$related_model_object = $this->get_model()->get_first_related($this, $relationName, $query_params);
2232
+			} else {
2233
+				//first, check if we've already cached the result of this query
2234
+				$cached_result = $this->get_one_from_cache($relationName);
2235
+				if ( ! $cached_result) {
2236
+					$related_model_object = $this->get_model()->get_first_related($this, $relationName, $query_params);
2237
+					$this->cache($relationName, $related_model_object);
2238
+				} else {
2239
+					$related_model_object = $cached_result;
2240
+				}
2241
+			}
2242
+		} else {
2243
+			$related_model_object = null;
2244
+			//this doesn't exist in the Db, but maybe the relation is of type belongs to, and so the related thing might
2245
+			if ($this->get_model()->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2246
+				$related_model_object = $this->get_model()->get_first_related($this, $relationName, $query_params);
2247
+			}
2248
+			//this doesn't exist in the DB and apparently the thing it belongs to doesn't either, just get what's cached on this object
2249
+			if ( ! $related_model_object) {
2250
+				$related_model_object = $this->get_one_from_cache($relationName);
2251
+			}
2252
+		}
2253
+		return $related_model_object;
2254
+	}
2255
+
2256
+
2257
+
2258
+	/**
2259
+	 * Does a delete on all related objects of type $relationName and removes
2260
+	 * the current model object's relation to them. If they can't be deleted (because
2261
+	 * of blocking related model objects) does nothing. If the related model objects are
2262
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2263
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2264
+	 *
2265
+	 * @param string $relationName
2266
+	 * @param array  $query_params like EEM_Base::get_all's
2267
+	 * @return int how many deleted
2268
+	 * @throws \EE_Error
2269
+	 */
2270
+	public function delete_related($relationName, $query_params = array())
2271
+	{
2272
+		if ($this->ID()) {
2273
+			$count = $this->get_model()->delete_related($this, $relationName, $query_params);
2274
+		} else {
2275
+			$count = count($this->get_all_from_cache($relationName));
2276
+			$this->clear_cache($relationName, null, true);
2277
+		}
2278
+		return $count;
2279
+	}
2280
+
2281
+
2282
+
2283
+	/**
2284
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2285
+	 * the current model object's relation to them. If they can't be deleted (because
2286
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2287
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2288
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2289
+	 *
2290
+	 * @param string $relationName
2291
+	 * @param array  $query_params like EEM_Base::get_all's
2292
+	 * @return int how many deleted (including those soft deleted)
2293
+	 * @throws \EE_Error
2294
+	 */
2295
+	public function delete_related_permanently($relationName, $query_params = array())
2296
+	{
2297
+		if ($this->ID()) {
2298
+			$count = $this->get_model()->delete_related_permanently($this, $relationName, $query_params);
2299
+		} else {
2300
+			$count = count($this->get_all_from_cache($relationName));
2301
+		}
2302
+		$this->clear_cache($relationName, null, true);
2303
+		return $count;
2304
+	}
2305
+
2306
+
2307
+
2308
+	/**
2309
+	 * is_set
2310
+	 * Just a simple utility function children can use for checking if property exists
2311
+	 *
2312
+	 * @access  public
2313
+	 * @param  string $field_name property to check
2314
+	 * @return bool                              TRUE if existing,FALSE if not.
2315
+	 */
2316
+	public function is_set($field_name)
2317
+	{
2318
+		return isset($this->_fields[$field_name]);
2319
+	}
2320
+
2321
+
2322
+
2323
+	/**
2324
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2325
+	 * EE_Error exception if they don't
2326
+	 *
2327
+	 * @param  mixed (string|array) $properties properties to check
2328
+	 * @throws EE_Error
2329
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
2330
+	 */
2331
+	protected function _property_exists($properties)
2332
+	{
2333
+		foreach ((array)$properties as $property_name) {
2334
+			//first make sure this property exists
2335
+			if ( ! $this->_fields[$property_name]) {
2336
+				throw new EE_Error(
2337
+					sprintf(
2338
+						__(
2339
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2340
+							'event_espresso'
2341
+						),
2342
+						$property_name
2343
+					)
2344
+				);
2345
+			}
2346
+		}
2347
+		return true;
2348
+	}
2349
+
2350
+
2351
+
2352
+	/**
2353
+	 * This simply returns an array of model fields for this object
2354
+	 *
2355
+	 * @return array
2356
+	 * @throws \EE_Error
2357
+	 */
2358
+	public function model_field_array()
2359
+	{
2360
+		$fields = $this->get_model()->field_settings(false);
2361
+		$properties = array();
2362
+		//remove prepended underscore
2363
+		foreach ($fields as $field_name => $settings) {
2364
+			$properties[$field_name] = $this->get($field_name);
2365
+		}
2366
+		return $properties;
2367
+	}
2368
+
2369
+
2370
+
2371
+	/**
2372
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2373
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2374
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
2375
+	 * requiring a plugin to extend the EE_Base_Class (which works fine is there's only 1 plugin, but when will that
2376
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
2377
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
2378
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
2379
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
2380
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
2381
+	 * my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2382
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2383
+	 *        return $previousReturnValue.$returnString;
2384
+	 * }
2385
+	 * require('EE_Answer.class.php');
2386
+	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2387
+	 * echo $answer->my_callback('monkeys',100);
2388
+	 * //will output "you called my_callback! and passed args:monkeys,100"
2389
+	 *
2390
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2391
+	 * @param array  $args       array of original arguments passed to the function
2392
+	 * @throws EE_Error
2393
+	 * @return mixed whatever the plugin which calls add_filter decides
2394
+	 */
2395
+	public function __call($methodName, $args)
2396
+	{
2397
+		$className = get_class($this);
2398
+		$tagName = "FHEE__{$className}__{$methodName}";
2399
+		if ( ! has_filter($tagName)) {
2400
+			throw new EE_Error(
2401
+				sprintf(
2402
+					__(
2403
+						"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;}",
2404
+						"event_espresso"
2405
+					),
2406
+					$methodName,
2407
+					$className,
2408
+					$tagName
2409
+				)
2410
+			);
2411
+		}
2412
+		return apply_filters($tagName, null, $this, $args);
2413
+	}
2414
+
2415
+
2416
+
2417
+	/**
2418
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2419
+	 * A $previous_value can be specified in case there are many meta rows with the same key
2420
+	 *
2421
+	 * @param string $meta_key
2422
+	 * @param mixed  $meta_value
2423
+	 * @param mixed  $previous_value
2424
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2425
+	 * @throws \EE_Error
2426
+	 * NOTE: if the values haven't changed, returns 0
2427
+	 */
2428
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2429
+	{
2430
+		$query_params = array(
2431
+			array(
2432
+				'EXM_key'  => $meta_key,
2433
+				'OBJ_ID'   => $this->ID(),
2434
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2435
+			),
2436
+		);
2437
+		if ($previous_value !== null) {
2438
+			$query_params[0]['EXM_value'] = $meta_value;
2439
+		}
2440
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2441
+		if ( ! $existing_rows_like_that) {
2442
+			return $this->add_extra_meta($meta_key, $meta_value);
2443
+		}
2444
+		foreach ($existing_rows_like_that as $existing_row) {
2445
+			$existing_row->save(array('EXM_value' => $meta_value));
2446
+		}
2447
+		return count($existing_rows_like_that);
2448
+	}
2449
+
2450
+
2451
+
2452
+	/**
2453
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2454
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
2455
+	 * extra meta row was entered, false if not
2456
+	 *
2457
+	 * @param string  $meta_key
2458
+	 * @param string  $meta_value
2459
+	 * @param boolean $unique
2460
+	 * @return boolean
2461
+	 * @throws \EE_Error
2462
+	 */
2463
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
2464
+	{
2465
+		if ($unique) {
2466
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2467
+				array(
2468
+					array(
2469
+						'EXM_key'  => $meta_key,
2470
+						'OBJ_ID'   => $this->ID(),
2471
+						'EXM_type' => $this->get_model()->get_this_model_name(),
2472
+					),
2473
+				)
2474
+			);
2475
+			if ($existing_extra_meta) {
2476
+				return false;
2477
+			}
2478
+		}
2479
+		$new_extra_meta = EE_Extra_Meta::new_instance(
2480
+			array(
2481
+				'EXM_key'   => $meta_key,
2482
+				'EXM_value' => $meta_value,
2483
+				'OBJ_ID'    => $this->ID(),
2484
+				'EXM_type'  => $this->get_model()->get_this_model_name(),
2485
+			)
2486
+		);
2487
+		$new_extra_meta->save();
2488
+		return true;
2489
+	}
2490
+
2491
+
2492
+
2493
+	/**
2494
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2495
+	 * is specified, only deletes extra meta records with that value.
2496
+	 *
2497
+	 * @param string $meta_key
2498
+	 * @param string $meta_value
2499
+	 * @return int number of extra meta rows deleted
2500
+	 * @throws \EE_Error
2501
+	 */
2502
+	public function delete_extra_meta($meta_key, $meta_value = null)
2503
+	{
2504
+		$query_params = array(
2505
+			array(
2506
+				'EXM_key'  => $meta_key,
2507
+				'OBJ_ID'   => $this->ID(),
2508
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2509
+			),
2510
+		);
2511
+		if ($meta_value !== null) {
2512
+			$query_params[0]['EXM_value'] = $meta_value;
2513
+		}
2514
+		return EEM_Extra_Meta::instance()->delete($query_params);
2515
+	}
2516
+
2517
+
2518
+
2519
+	/**
2520
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2521
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2522
+	 * You can specify $default is case you haven't found the extra meta
2523
+	 *
2524
+	 * @param string  $meta_key
2525
+	 * @param boolean $single
2526
+	 * @param mixed   $default if we don't find anything, what should we return?
2527
+	 * @return mixed single value if $single; array if ! $single
2528
+	 * @throws \EE_Error
2529
+	 */
2530
+	public function get_extra_meta($meta_key, $single = false, $default = null)
2531
+	{
2532
+		if ($single) {
2533
+			$result = $this->get_first_related('Extra_Meta', array(array('EXM_key' => $meta_key)));
2534
+			if ($result instanceof EE_Extra_Meta) {
2535
+				return $result->value();
2536
+			} else {
2537
+				return $default;
2538
+			}
2539
+		} else {
2540
+			$results = $this->get_many_related('Extra_Meta', array(array('EXM_key' => $meta_key)));
2541
+			if ($results) {
2542
+				$values = array();
2543
+				foreach ($results as $result) {
2544
+					if ($result instanceof EE_Extra_Meta) {
2545
+						$values[$result->ID()] = $result->value();
2546
+					}
2547
+				}
2548
+				return $values;
2549
+			} else {
2550
+				return $default;
2551
+			}
2552
+		}
2553
+	}
2554
+
2555
+
2556
+
2557
+	/**
2558
+	 * Returns a simple array of all the extra meta associated with this model object.
2559
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2560
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2561
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2562
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2563
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2564
+	 * finally the extra meta's value as each sub-value. (eg
2565
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2566
+	 *
2567
+	 * @param boolean $one_of_each_key
2568
+	 * @return array
2569
+	 * @throws \EE_Error
2570
+	 */
2571
+	public function all_extra_meta_array($one_of_each_key = true)
2572
+	{
2573
+		$return_array = array();
2574
+		if ($one_of_each_key) {
2575
+			$extra_meta_objs = $this->get_many_related('Extra_Meta', array('group_by' => 'EXM_key'));
2576
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2577
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2578
+					$return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2579
+				}
2580
+			}
2581
+		} else {
2582
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2583
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2584
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2585
+					if ( ! isset($return_array[$extra_meta_obj->key()])) {
2586
+						$return_array[$extra_meta_obj->key()] = array();
2587
+					}
2588
+					$return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2589
+				}
2590
+			}
2591
+		}
2592
+		return $return_array;
2593
+	}
2594
+
2595
+
2596
+
2597
+	/**
2598
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
2599
+	 *
2600
+	 * @return string
2601
+	 * @throws \EE_Error
2602
+	 */
2603
+	public function name()
2604
+	{
2605
+		//find a field that's not a text field
2606
+		$field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2607
+		if ($field_we_can_use) {
2608
+			return $this->get($field_we_can_use->get_name());
2609
+		} else {
2610
+			$first_few_properties = $this->model_field_array();
2611
+			$first_few_properties = array_slice($first_few_properties, 0, 3);
2612
+			$name_parts = array();
2613
+			foreach ($first_few_properties as $name => $value) {
2614
+				$name_parts[] = "$name:$value";
2615
+			}
2616
+			return implode(",", $name_parts);
2617
+		}
2618
+	}
2619
+
2620
+
2621
+
2622
+	/**
2623
+	 * in_entity_map
2624
+	 * Checks if this model object has been proven to already be in the entity map
2625
+	 *
2626
+	 * @return boolean
2627
+	 * @throws \EE_Error
2628
+	 */
2629
+	public function in_entity_map()
2630
+	{
2631
+		if ($this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this) {
2632
+			//well, if we looked, did we find it in the entity map?
2633
+			return true;
2634
+		} else {
2635
+			return false;
2636
+		}
2637
+	}
2638
+
2639
+
2640
+
2641
+	/**
2642
+	 * refresh_from_db
2643
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
2644
+	 *
2645
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
2646
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
2647
+	 */
2648
+	public function refresh_from_db()
2649
+	{
2650
+		if ($this->ID() && $this->in_entity_map()) {
2651
+			$this->get_model()->refresh_entity_map_from_db($this->ID());
2652
+		} else {
2653
+			//if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
2654
+			//if it has an ID but it's not in the map, and you're asking me to refresh it
2655
+			//that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
2656
+			//absolutely nothing in it for this ID
2657
+			if (WP_DEBUG) {
2658
+				throw new EE_Error(
2659
+					sprintf(
2660
+						__('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.',
2661
+							'event_espresso'),
2662
+						$this->ID(),
2663
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
2664
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
2665
+					)
2666
+				);
2667
+			}
2668
+		}
2669
+	}
2670
+
2671
+
2672
+
2673
+	/**
2674
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
2675
+	 * (probably a bad assumption they have made, oh well)
2676
+	 *
2677
+	 * @return string
2678
+	 */
2679
+	public function __toString()
2680
+	{
2681
+		try {
2682
+			return sprintf('%s (%s)', $this->name(), $this->ID());
2683
+		} catch (Exception $e) {
2684
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
2685
+			return '';
2686
+		}
2687
+	}
2688
+
2689
+
2690
+
2691
+	/**
2692
+	 * Clear related model objects if they're already in the DB, because otherwise when we
2693
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
2694
+	 * This means if we have made changes to those related model objects, and want to unserialize
2695
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
2696
+	 * Instead, those related model objects should be directly serialized and stored.
2697
+	 * Eg, the following won't work:
2698
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
2699
+	 * $att = $reg->attendee();
2700
+	 * $att->set( 'ATT_fname', 'Dirk' );
2701
+	 * update_option( 'my_option', serialize( $reg ) );
2702
+	 * //END REQUEST
2703
+	 * //START NEXT REQUEST
2704
+	 * $reg = get_option( 'my_option' );
2705
+	 * $reg->attendee()->save();
2706
+	 * And would need to be replace with:
2707
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
2708
+	 * $att = $reg->attendee();
2709
+	 * $att->set( 'ATT_fname', 'Dirk' );
2710
+	 * update_option( 'my_option', serialize( $reg ) );
2711
+	 * //END REQUEST
2712
+	 * //START NEXT REQUEST
2713
+	 * $att = get_option( 'my_option' );
2714
+	 * $att->save();
2715
+	 *
2716
+	 * @return array
2717
+	 * @throws \EE_Error
2718
+	 */
2719
+	public function __sleep()
2720
+	{
2721
+		foreach ($this->get_model()->relation_settings() as $relation_name => $relation_obj) {
2722
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
2723
+				$classname = 'EE_' . $this->get_model()->get_this_model_name();
2724
+				if (
2725
+					$this->get_one_from_cache($relation_name) instanceof $classname
2726
+					&& $this->get_one_from_cache($relation_name)->ID()
2727
+				) {
2728
+					$this->clear_cache($relation_name, $this->get_one_from_cache($relation_name)->ID());
2729
+				}
2730
+			}
2731
+		}
2732
+		$this->_props_n_values_provided_in_constructor = array();
2733
+		return array_keys(get_object_vars($this));
2734
+	}
2735
+
2736
+
2737
+
2738
+	/**
2739
+	 * restore _props_n_values_provided_in_constructor
2740
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
2741
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
2742
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
2743
+	 */
2744
+	public function __wakeup()
2745
+	{
2746
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
2747
+	}
2748 2748
 
2749 2749
 
2750 2750
 
Please login to merge, or discard this patch.
core/db_models/fields/EE_Post_Content_Field.php 2 patches
Indentation   +116 added lines, -116 removed lines patch added patch discarded remove patch
@@ -8,133 +8,133 @@
 block discarded – undo
8 8
 class EE_Post_Content_Field extends EE_Text_Field_Base
9 9
 {
10 10
 
11
-    /**
12
-     * @param string $table_column
13
-     * @param string $nicename
14
-     * @param bool   $nullable
15
-     * @param null   $default_value
16
-     */
17
-    public function __construct($table_column, $nicename, $nullable, $default_value = null)
18
-    {
19
-        parent::__construct($table_column, $nicename, $nullable, $default_value);
20
-        $this->setSchemaType('object');
21
-    }
11
+	/**
12
+	 * @param string $table_column
13
+	 * @param string $nicename
14
+	 * @param bool   $nullable
15
+	 * @param null   $default_value
16
+	 */
17
+	public function __construct($table_column, $nicename, $nullable, $default_value = null)
18
+	{
19
+		parent::__construct($table_column, $nicename, $nullable, $default_value);
20
+		$this->setSchemaType('object');
21
+	}
22 22
 
23 23
 
24
-    /**
25
-     * removes all tags which a WP Post wouldn't allow in its content normally
26
-     *
27
-     * @param string $value
28
-     * @return string
29
-     */
30
-    function prepare_for_set($value)
31
-    {
32
-        if (! current_user_can('unfiltered_html')) {
33
-            $value = wp_kses("$value", wp_kses_allowed_html('post'));
34
-        }
35
-        return parent::prepare_for_set($value);
36
-    }
24
+	/**
25
+	 * removes all tags which a WP Post wouldn't allow in its content normally
26
+	 *
27
+	 * @param string $value
28
+	 * @return string
29
+	 */
30
+	function prepare_for_set($value)
31
+	{
32
+		if (! current_user_can('unfiltered_html')) {
33
+			$value = wp_kses("$value", wp_kses_allowed_html('post'));
34
+		}
35
+		return parent::prepare_for_set($value);
36
+	}
37 37
 
38
-    function prepare_for_set_from_db($value_found_in_db_for_model_object)
39
-    {
40
-        return $value_found_in_db_for_model_object;
41
-    }
38
+	function prepare_for_set_from_db($value_found_in_db_for_model_object)
39
+	{
40
+		return $value_found_in_db_for_model_object;
41
+	}
42 42
 
43 43
 
44 44
 
45
-    /**
46
-     * Runs the content through `the_content`, or if prepares the content for placing in a form input
47
-     * @param string $value_on_field_to_be_outputted
48
-     * @param string   $schema possible values: 'form_input' or null (if null, will run through 'the_content')
49
-     * @return string
50
-     * @throws EE_Error when WP_DEBUG is on and recursive calling is detected
51
-     */
52
-    public function prepare_for_pretty_echoing($value_on_field_to_be_outputted, $schema = null)
53
-    {
54
-        switch($schema){
55
-            case 'form_input':
56
-                return parent::prepare_for_pretty_echoing($value_on_field_to_be_outputted, $schema);
57
-            case 'the_content':
45
+	/**
46
+	 * Runs the content through `the_content`, or if prepares the content for placing in a form input
47
+	 * @param string $value_on_field_to_be_outputted
48
+	 * @param string   $schema possible values: 'form_input' or null (if null, will run through 'the_content')
49
+	 * @return string
50
+	 * @throws EE_Error when WP_DEBUG is on and recursive calling is detected
51
+	 */
52
+	public function prepare_for_pretty_echoing($value_on_field_to_be_outputted, $schema = null)
53
+	{
54
+		switch($schema){
55
+			case 'form_input':
56
+				return parent::prepare_for_pretty_echoing($value_on_field_to_be_outputted, $schema);
57
+			case 'the_content':
58 58
 
59
-                if(doing_filter( 'the_content')){
60
-                    if( defined('WP_DEBUG') && WP_DEBUG){
61
-                        throw new EE_Error(
62
-                            sprintf(
63
-                                esc_html__('You have recursively called "%1$s" with %2$s set to %3$s which uses "%2$s" filter. You should use it with %2$s "%3$s" instead here.', 'event_espresso'),
64
-                                'EE_Post_Content_Field::prepare_for_pretty_echoing',
65
-                                '$schema',
66
-                                'the_content',
67
-                                'the_content_wp_core_only'
68
-                            )
69
-                        );
70
-                    } else {
71
-                        return $this->prepare_for_pretty_echoing($value_on_field_to_be_outputted, 'the_content_wp_core_only');
72
-                    }
73
-                }
74
-                return apply_filters(
75
-                    'the_content',
76
-                    parent::prepare_for_pretty_echoing(
77
-                        $value_on_field_to_be_outputted,
78
-                        $schema
79
-                    )
80
-                );
81
-            case 'the_content_wp_core_only':
82
-            default:
83
-                self::_setup_the_content_wp_core_only_filters();
84
-                $return_value = apply_filters(
85
-                    'the_content_wp_core_only',
86
-                    parent::prepare_for_pretty_echoing(
87
-                        $value_on_field_to_be_outputted,
88
-                        $schema
89
-                    )
90
-                );
91
-                //ya know what? adding these filters is super fast. Let's just
92
-                //avoid needing to maintain global state and set this up as-needed
93
-                remove_all_filters('the_content_wp_core_only');
94
-                do_action( 'AHEE__EE_Post_Content_Field__prepare_for_pretty_echoing__the_content_wp_core_only__done');
95
-                return $return_value;
96
-        }
97
-    }
59
+				if(doing_filter( 'the_content')){
60
+					if( defined('WP_DEBUG') && WP_DEBUG){
61
+						throw new EE_Error(
62
+							sprintf(
63
+								esc_html__('You have recursively called "%1$s" with %2$s set to %3$s which uses "%2$s" filter. You should use it with %2$s "%3$s" instead here.', 'event_espresso'),
64
+								'EE_Post_Content_Field::prepare_for_pretty_echoing',
65
+								'$schema',
66
+								'the_content',
67
+								'the_content_wp_core_only'
68
+							)
69
+						);
70
+					} else {
71
+						return $this->prepare_for_pretty_echoing($value_on_field_to_be_outputted, 'the_content_wp_core_only');
72
+					}
73
+				}
74
+				return apply_filters(
75
+					'the_content',
76
+					parent::prepare_for_pretty_echoing(
77
+						$value_on_field_to_be_outputted,
78
+						$schema
79
+					)
80
+				);
81
+			case 'the_content_wp_core_only':
82
+			default:
83
+				self::_setup_the_content_wp_core_only_filters();
84
+				$return_value = apply_filters(
85
+					'the_content_wp_core_only',
86
+					parent::prepare_for_pretty_echoing(
87
+						$value_on_field_to_be_outputted,
88
+						$schema
89
+					)
90
+				);
91
+				//ya know what? adding these filters is super fast. Let's just
92
+				//avoid needing to maintain global state and set this up as-needed
93
+				remove_all_filters('the_content_wp_core_only');
94
+				do_action( 'AHEE__EE_Post_Content_Field__prepare_for_pretty_echoing__the_content_wp_core_only__done');
95
+				return $return_value;
96
+		}
97
+	}
98 98
 
99 99
 
100 100
 
101
-    /**
102
-     * Verifies we've setup the standard WP core filters on  'the_content_wp_core_only' filter
103
-     */
104
-    protected static function _setup_the_content_wp_core_only_filters()
105
-    {
106
-        add_filter('the_content_wp_core_only', array( $GLOBALS['wp_embed'], 'run_shortcode'), 8);
107
-        add_filter('the_content_wp_core_only', array( $GLOBALS['wp_embed'], 'autoembed'), 8);
108
-        add_filter('the_content_wp_core_only', 'wptexturize', 10);
109
-        add_filter('the_content_wp_core_only', 'wpautop', 10);
110
-        add_filter('the_content_wp_core_only', 'shortcode_unautop', 10);
111
-        add_filter('the_content_wp_core_only', 'prepend_attachment', 10);
112
-        if(function_exists('wp_make_content_images_responsive')) {
113
-            add_filter('the_content_wp_core_only', 'wp_make_content_images_responsive', 10);
114
-        }
115
-        add_filter('the_content_wp_core_only', 'do_shortcode', 11);
116
-        add_filter('the_content_wp_core_only', 'convert_smilies', 20);
117
-    }
101
+	/**
102
+	 * Verifies we've setup the standard WP core filters on  'the_content_wp_core_only' filter
103
+	 */
104
+	protected static function _setup_the_content_wp_core_only_filters()
105
+	{
106
+		add_filter('the_content_wp_core_only', array( $GLOBALS['wp_embed'], 'run_shortcode'), 8);
107
+		add_filter('the_content_wp_core_only', array( $GLOBALS['wp_embed'], 'autoembed'), 8);
108
+		add_filter('the_content_wp_core_only', 'wptexturize', 10);
109
+		add_filter('the_content_wp_core_only', 'wpautop', 10);
110
+		add_filter('the_content_wp_core_only', 'shortcode_unautop', 10);
111
+		add_filter('the_content_wp_core_only', 'prepend_attachment', 10);
112
+		if(function_exists('wp_make_content_images_responsive')) {
113
+			add_filter('the_content_wp_core_only', 'wp_make_content_images_responsive', 10);
114
+		}
115
+		add_filter('the_content_wp_core_only', 'do_shortcode', 11);
116
+		add_filter('the_content_wp_core_only', 'convert_smilies', 20);
117
+	}
118 118
 
119 119
 
120 120
 
121
-    public function getSchemaProperties()
122
-    {
123
-        return array(
124
-            'raw' => array(
125
-                'description' =>  sprintf(
126
-                    __('%s - the content as it exists in the database.', 'event_espresso'),
127
-                    $this->get_nicename()
128
-                ),
129
-                'type' => 'string'
130
-            ),
131
-            'rendered' => array(
132
-                'description' =>  sprintf(
133
-                    __('%s - the content rendered for display.', 'event_espresso'),
134
-                    $this->get_nicename()
135
-                ),
136
-                'type' => 'string'
137
-            )
138
-        );
139
-    }
121
+	public function getSchemaProperties()
122
+	{
123
+		return array(
124
+			'raw' => array(
125
+				'description' =>  sprintf(
126
+					__('%s - the content as it exists in the database.', 'event_espresso'),
127
+					$this->get_nicename()
128
+				),
129
+				'type' => 'string'
130
+			),
131
+			'rendered' => array(
132
+				'description' =>  sprintf(
133
+					__('%s - the content rendered for display.', 'event_espresso'),
134
+					$this->get_nicename()
135
+				),
136
+				'type' => 'string'
137
+			)
138
+		);
139
+	}
140 140
 }
141 141
\ No newline at end of file
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -29,7 +29,7 @@  discard block
 block discarded – undo
29 29
      */
30 30
     function prepare_for_set($value)
31 31
     {
32
-        if (! current_user_can('unfiltered_html')) {
32
+        if ( ! current_user_can('unfiltered_html')) {
33 33
             $value = wp_kses("$value", wp_kses_allowed_html('post'));
34 34
         }
35 35
         return parent::prepare_for_set($value);
@@ -51,13 +51,13 @@  discard block
 block discarded – undo
51 51
      */
52 52
     public function prepare_for_pretty_echoing($value_on_field_to_be_outputted, $schema = null)
53 53
     {
54
-        switch($schema){
54
+        switch ($schema) {
55 55
             case 'form_input':
56 56
                 return parent::prepare_for_pretty_echoing($value_on_field_to_be_outputted, $schema);
57 57
             case 'the_content':
58 58
 
59
-                if(doing_filter( 'the_content')){
60
-                    if( defined('WP_DEBUG') && WP_DEBUG){
59
+                if (doing_filter('the_content')) {
60
+                    if (defined('WP_DEBUG') && WP_DEBUG) {
61 61
                         throw new EE_Error(
62 62
                             sprintf(
63 63
                                 esc_html__('You have recursively called "%1$s" with %2$s set to %3$s which uses "%2$s" filter. You should use it with %2$s "%3$s" instead here.', 'event_espresso'),
@@ -91,7 +91,7 @@  discard block
 block discarded – undo
91 91
                 //ya know what? adding these filters is super fast. Let's just
92 92
                 //avoid needing to maintain global state and set this up as-needed
93 93
                 remove_all_filters('the_content_wp_core_only');
94
-                do_action( 'AHEE__EE_Post_Content_Field__prepare_for_pretty_echoing__the_content_wp_core_only__done');
94
+                do_action('AHEE__EE_Post_Content_Field__prepare_for_pretty_echoing__the_content_wp_core_only__done');
95 95
                 return $return_value;
96 96
         }
97 97
     }
@@ -103,13 +103,13 @@  discard block
 block discarded – undo
103 103
      */
104 104
     protected static function _setup_the_content_wp_core_only_filters()
105 105
     {
106
-        add_filter('the_content_wp_core_only', array( $GLOBALS['wp_embed'], 'run_shortcode'), 8);
107
-        add_filter('the_content_wp_core_only', array( $GLOBALS['wp_embed'], 'autoembed'), 8);
106
+        add_filter('the_content_wp_core_only', array($GLOBALS['wp_embed'], 'run_shortcode'), 8);
107
+        add_filter('the_content_wp_core_only', array($GLOBALS['wp_embed'], 'autoembed'), 8);
108 108
         add_filter('the_content_wp_core_only', 'wptexturize', 10);
109 109
         add_filter('the_content_wp_core_only', 'wpautop', 10);
110 110
         add_filter('the_content_wp_core_only', 'shortcode_unautop', 10);
111 111
         add_filter('the_content_wp_core_only', 'prepend_attachment', 10);
112
-        if(function_exists('wp_make_content_images_responsive')) {
112
+        if (function_exists('wp_make_content_images_responsive')) {
113 113
             add_filter('the_content_wp_core_only', 'wp_make_content_images_responsive', 10);
114 114
         }
115 115
         add_filter('the_content_wp_core_only', 'do_shortcode', 11);
Please login to merge, or discard this patch.
core/db_classes/EE_CPT_Base.class.php 2 patches
Indentation   +82 added lines, -82 removed lines patch added patch discarded remove patch
@@ -36,9 +36,9 @@  discard block
 block discarded – undo
36 36
 	 */
37 37
 	protected $_feature_image = array();
38 38
 
39
-    /**
40
-     * @var WP_Post the WP_Post that corresponds with this CPT model object
41
-     */
39
+	/**
40
+	 * @var WP_Post the WP_Post that corresponds with this CPT model object
41
+	 */
42 42
 	protected $_wp_post;
43 43
 
44 44
 
@@ -46,75 +46,75 @@  discard block
 block discarded – undo
46 46
 
47 47
 
48 48
 
49
-    /**
50
-     * Returns the WP post associated with this CPT model object. If this CPT is saved, fetches it
51
-     * from the DB. Otherwise, create an unsaved WP_POst object. Caches the post internally.
52
-     * @return WP_Post
53
-     */
54
-    public function wp_post(){
55
-        global $wpdb;
56
-        if (! $this->_wp_post instanceof WP_Post) {
57
-            if ($this->ID()) {
58
-                $this->_wp_post = get_post($this->ID());
59
-            } else {
60
-                $simulated_db_result = new stdClass();
61
-                foreach($this->get_model()->field_settings(true) as $field_name => $field_obj){
62
-                    if ($this->get_model()->get_table_obj_by_alias($field_obj->get_table_alias())->get_table_name() === $wpdb->posts){
63
-                        $column = $field_obj->get_table_column();
64
-
65
-                        if($field_obj instanceof EE_Datetime_Field){
66
-                            $value_on_model_obj = $this->get_DateTime_object($field_name);
67
-                        } elseif( $field_obj->is_db_only_field()){
68
-                            $value_on_model_obj = $field_obj->get_default_value();
69
-                        } else {
70
-                            $value_on_model_obj = $this->get_raw($field_name);
71
-                        }
72
-                        $simulated_db_result->{$column} = $field_obj->prepare_for_use_in_db($value_on_model_obj);
73
-                    }
74
-                }
75
-                $this->_wp_post = new WP_Post($simulated_db_result);
76
-            }
77
-            //and let's make retrieving the EE CPT object easy too
78
-            $classname = get_class($this);
79
-            if (! isset($this->_wp_post->{$classname})) {
80
-                $this->_wp_post->{$classname} = $this;
81
-            }
82
-        }
83
-        return $this->_wp_post;
84
-    }
85
-
86
-    /**
87
-     * When fetching a new value for a post field that uses the global $post for rendering,
88
-     * set the global $post temporarily to be this model object; and afterwards restore it
89
-     * @param string $fieldname
90
-     * @param bool $pretty
91
-     * @param string $extra_cache_ref
92
-     * @return mixed
93
-     */
94
-    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
95
-    {
96
-        global $post;
97
-
98
-        if ( $pretty
99
-            && (
100
-                ! (
101
-                       $post instanceof WP_Post
102
-                       && $post->ID
103
-                   )
104
-                || (int)$post->ID !== $this->ID()
105
-             )
106
-            && $this->get_model()->field_settings_for($fieldname) instanceof EE_Post_Content_Field ) {
107
-            $old_post = $post;
108
-            $post = $this->wp_post();
109
-            $return_value = parent::_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
110
-            $post = $old_post;
111
-        } else {
112
-            $return_value = parent::_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
113
-        }
114
-        return $return_value;
115
-    }
116
-
117
-    /**
49
+	/**
50
+	 * Returns the WP post associated with this CPT model object. If this CPT is saved, fetches it
51
+	 * from the DB. Otherwise, create an unsaved WP_POst object. Caches the post internally.
52
+	 * @return WP_Post
53
+	 */
54
+	public function wp_post(){
55
+		global $wpdb;
56
+		if (! $this->_wp_post instanceof WP_Post) {
57
+			if ($this->ID()) {
58
+				$this->_wp_post = get_post($this->ID());
59
+			} else {
60
+				$simulated_db_result = new stdClass();
61
+				foreach($this->get_model()->field_settings(true) as $field_name => $field_obj){
62
+					if ($this->get_model()->get_table_obj_by_alias($field_obj->get_table_alias())->get_table_name() === $wpdb->posts){
63
+						$column = $field_obj->get_table_column();
64
+
65
+						if($field_obj instanceof EE_Datetime_Field){
66
+							$value_on_model_obj = $this->get_DateTime_object($field_name);
67
+						} elseif( $field_obj->is_db_only_field()){
68
+							$value_on_model_obj = $field_obj->get_default_value();
69
+						} else {
70
+							$value_on_model_obj = $this->get_raw($field_name);
71
+						}
72
+						$simulated_db_result->{$column} = $field_obj->prepare_for_use_in_db($value_on_model_obj);
73
+					}
74
+				}
75
+				$this->_wp_post = new WP_Post($simulated_db_result);
76
+			}
77
+			//and let's make retrieving the EE CPT object easy too
78
+			$classname = get_class($this);
79
+			if (! isset($this->_wp_post->{$classname})) {
80
+				$this->_wp_post->{$classname} = $this;
81
+			}
82
+		}
83
+		return $this->_wp_post;
84
+	}
85
+
86
+	/**
87
+	 * When fetching a new value for a post field that uses the global $post for rendering,
88
+	 * set the global $post temporarily to be this model object; and afterwards restore it
89
+	 * @param string $fieldname
90
+	 * @param bool $pretty
91
+	 * @param string $extra_cache_ref
92
+	 * @return mixed
93
+	 */
94
+	protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
95
+	{
96
+		global $post;
97
+
98
+		if ( $pretty
99
+			&& (
100
+				! (
101
+					   $post instanceof WP_Post
102
+					   && $post->ID
103
+				   )
104
+				|| (int)$post->ID !== $this->ID()
105
+			 )
106
+			&& $this->get_model()->field_settings_for($fieldname) instanceof EE_Post_Content_Field ) {
107
+			$old_post = $post;
108
+			$post = $this->wp_post();
109
+			$return_value = parent::_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
110
+			$post = $old_post;
111
+		} else {
112
+			$return_value = parent::_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
113
+		}
114
+		return $return_value;
115
+	}
116
+
117
+	/**
118 118
 	 * Adds to the specified event category. If it category doesn't exist, creates it.
119 119
 	 * @param string $category_name
120 120
 	 * @param string $category_description    optional
@@ -399,14 +399,14 @@  discard block
 block discarded – undo
399 399
 
400 400
 
401 401
 
402
-    /**
403
-     * Don't serialize the WP Post. That's just duplicate data and we want to avoid recursion
404
-     * @return array
405
-     */
406
-    public function __sleep()
407
-    {
408
-        $properties_to_serialize = parent::__sleep();
409
-        $properties_to_serialize = array_diff( $properties_to_serialize, array('_wp_post'));
410
-        return $properties_to_serialize;
411
-    }
402
+	/**
403
+	 * Don't serialize the WP Post. That's just duplicate data and we want to avoid recursion
404
+	 * @return array
405
+	 */
406
+	public function __sleep()
407
+	{
408
+		$properties_to_serialize = parent::__sleep();
409
+		$properties_to_serialize = array_diff( $properties_to_serialize, array('_wp_post'));
410
+		return $properties_to_serialize;
411
+	}
412 412
 }
Please login to merge, or discard this patch.
Spacing   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -1,5 +1,5 @@  discard block
 block discarded – undo
1
-<?php if ( !defined( 'EVENT_ESPRESSO_VERSION' ) ) {
2
-	exit( 'No direct script access allowed' );
1
+<?php if ( ! defined('EVENT_ESPRESSO_VERSION')) {
2
+	exit('No direct script access allowed');
3 3
 }
4 4
 /**
5 5
  * Event Espresso
@@ -51,20 +51,20 @@  discard block
 block discarded – undo
51 51
      * from the DB. Otherwise, create an unsaved WP_POst object. Caches the post internally.
52 52
      * @return WP_Post
53 53
      */
54
-    public function wp_post(){
54
+    public function wp_post() {
55 55
         global $wpdb;
56
-        if (! $this->_wp_post instanceof WP_Post) {
56
+        if ( ! $this->_wp_post instanceof WP_Post) {
57 57
             if ($this->ID()) {
58 58
                 $this->_wp_post = get_post($this->ID());
59 59
             } else {
60 60
                 $simulated_db_result = new stdClass();
61
-                foreach($this->get_model()->field_settings(true) as $field_name => $field_obj){
62
-                    if ($this->get_model()->get_table_obj_by_alias($field_obj->get_table_alias())->get_table_name() === $wpdb->posts){
61
+                foreach ($this->get_model()->field_settings(true) as $field_name => $field_obj) {
62
+                    if ($this->get_model()->get_table_obj_by_alias($field_obj->get_table_alias())->get_table_name() === $wpdb->posts) {
63 63
                         $column = $field_obj->get_table_column();
64 64
 
65
-                        if($field_obj instanceof EE_Datetime_Field){
65
+                        if ($field_obj instanceof EE_Datetime_Field) {
66 66
                             $value_on_model_obj = $this->get_DateTime_object($field_name);
67
-                        } elseif( $field_obj->is_db_only_field()){
67
+                        } elseif ($field_obj->is_db_only_field()) {
68 68
                             $value_on_model_obj = $field_obj->get_default_value();
69 69
                         } else {
70 70
                             $value_on_model_obj = $this->get_raw($field_name);
@@ -76,7 +76,7 @@  discard block
 block discarded – undo
76 76
             }
77 77
             //and let's make retrieving the EE CPT object easy too
78 78
             $classname = get_class($this);
79
-            if (! isset($this->_wp_post->{$classname})) {
79
+            if ( ! isset($this->_wp_post->{$classname})) {
80 80
                 $this->_wp_post->{$classname} = $this;
81 81
             }
82 82
         }
@@ -95,15 +95,15 @@  discard block
 block discarded – undo
95 95
     {
96 96
         global $post;
97 97
 
98
-        if ( $pretty
98
+        if ($pretty
99 99
             && (
100 100
                 ! (
101 101
                        $post instanceof WP_Post
102 102
                        && $post->ID
103 103
                    )
104
-                || (int)$post->ID !== $this->ID()
104
+                || (int) $post->ID !== $this->ID()
105 105
              )
106
-            && $this->get_model()->field_settings_for($fieldname) instanceof EE_Post_Content_Field ) {
106
+            && $this->get_model()->field_settings_for($fieldname) instanceof EE_Post_Content_Field) {
107 107
             $old_post = $post;
108 108
             $post = $this->wp_post();
109 109
             $return_value = parent::_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
@@ -121,8 +121,8 @@  discard block
 block discarded – undo
121 121
 	 * @param int    $parent_term_taxonomy_id optional
122 122
 	 * @return EE_Term_Taxonomy
123 123
 	 */
124
-	function add_event_category( $category_name, $category_description = NULL, $parent_term_taxonomy_id = NULL ) {
125
-		return $this->get_model()->add_event_category( $this, $category_name, $category_description, $parent_term_taxonomy_id );
124
+	function add_event_category($category_name, $category_description = NULL, $parent_term_taxonomy_id = NULL) {
125
+		return $this->get_model()->add_event_category($this, $category_name, $category_description, $parent_term_taxonomy_id);
126 126
 	}
127 127
 
128 128
 
@@ -132,8 +132,8 @@  discard block
 block discarded – undo
132 132
 	 * @param string $category_name
133 133
 	 * @return bool
134 134
 	 */
135
-	function remove_event_category( $category_name ) {
136
-		return $this->get_model()->remove_event_category( $this, $category_name );
135
+	function remove_event_category($category_name) {
136
+		return $this->get_model()->remove_event_category($this, $category_name);
137 137
 	}
138 138
 
139 139
 
@@ -144,14 +144,14 @@  discard block
 block discarded – undo
144 144
 	 * @param EE_Term_Taxonomy $term_taxonomy
145 145
 	 * @return EE_Base_Class the relation was removed from
146 146
 	 */
147
-	function remove_relation_to_term_taxonomy( $term_taxonomy ) {
148
-		if ( !$term_taxonomy ) {
149
-			EE_Error::add_error( sprintf( __( "No Term_Taxonomy provided which to remove from model object of type %s and id %d", "event_espresso" ), get_class( $this ), $this->ID() ), __FILE__, __FUNCTION__, __LINE__ );
147
+	function remove_relation_to_term_taxonomy($term_taxonomy) {
148
+		if ( ! $term_taxonomy) {
149
+			EE_Error::add_error(sprintf(__("No Term_Taxonomy provided which to remove from model object of type %s and id %d", "event_espresso"), get_class($this), $this->ID()), __FILE__, __FUNCTION__, __LINE__);
150 150
 			return NULL;
151 151
 		}
152
-		$term_taxonomy->set_count( $term_taxonomy->count() - 1 );
152
+		$term_taxonomy->set_count($term_taxonomy->count() - 1);
153 153
 		$term_taxonomy->save();
154
-		return $this->_remove_relation_to( $term_taxonomy, 'Term_Taxonomy' );
154
+		return $this->_remove_relation_to($term_taxonomy, 'Term_Taxonomy');
155 155
 	}
156 156
 
157 157
 
@@ -175,7 +175,7 @@  discard block
 block discarded – undo
175 175
 	 * @return int
176 176
 	 */
177 177
 	public function parent() {
178
-		return $this->get( 'parent' );
178
+		return $this->get('parent');
179 179
 	}
180 180
 
181 181
 
@@ -185,7 +185,7 @@  discard block
 block discarded – undo
185 185
 	 * @return string
186 186
 	 */
187 187
 	public function status() {
188
-		return $this->get( 'status' );
188
+		return $this->get('status');
189 189
 	}
190 190
 
191 191
 
@@ -193,8 +193,8 @@  discard block
 block discarded – undo
193 193
 	/**
194 194
 	 * @param string $status
195 195
 	 */
196
-	public function set_status( $status ) {
197
-		$this->set( 'status', $status );
196
+	public function set_status($status) {
197
+		$this->set('status', $status);
198 198
 	}
199 199
 
200 200
 
@@ -208,12 +208,12 @@  discard block
 block discarded – undo
208 208
 	 * @param string|array $attr Optional. Query string or array of attributes.
209 209
 	 * @return string HTML image element
210 210
 	 */
211
-	protected function _get_feature_image( $size, $attr ) {
211
+	protected function _get_feature_image($size, $attr) {
212 212
 		//first let's see if we already have the _feature_image property set AND if it has a cached element on it FOR the given size
213
-		$attr_key = is_array( $attr ) ? implode( '_', $attr ) : $attr;
214
-		$cache_key = is_array( $size ) ? implode( '_', $size ) . $attr_key : $size . $attr_key;
215
-		$this->_feature_image[ $cache_key ] = isset( $this->_feature_image[ $cache_key ] ) ? $this->_feature_image[ $cache_key ] : $this->get_model()->get_feature_image( $this->ID(), $size, $attr );
216
-		return $this->_feature_image[ $cache_key ];
213
+		$attr_key = is_array($attr) ? implode('_', $attr) : $attr;
214
+		$cache_key = is_array($size) ? implode('_', $size).$attr_key : $size.$attr_key;
215
+		$this->_feature_image[$cache_key] = isset($this->_feature_image[$cache_key]) ? $this->_feature_image[$cache_key] : $this->get_model()->get_feature_image($this->ID(), $size, $attr);
216
+		return $this->_feature_image[$cache_key];
217 217
 	}
218 218
 
219 219
 
@@ -224,8 +224,8 @@  discard block
 block discarded – undo
224 224
 	 * @param string|array $attr
225 225
 	 * @return string of html
226 226
 	 */
227
-	public function feature_image( $size = 'thumbnail', $attr = '' ) {
228
-		return $this->_get_feature_image( $size, $attr );
227
+	public function feature_image($size = 'thumbnail', $attr = '') {
228
+		return $this->_get_feature_image($size, $attr);
229 229
 	}
230 230
 
231 231
 
@@ -235,9 +235,9 @@  discard block
 block discarded – undo
235 235
 	 * @param  string|array $size can either be a string: 'thumbnail', 'medium', 'large', 'full' OR 2-item array representing width and height in pixels eg. array(32,32).
236 236
 	 * @return string|boolean          the url of the image or false if not found
237 237
 	 */
238
-	public function feature_image_url( $size = 'thumbnail' ) {
239
-		$attachment = wp_get_attachment_image_src( get_post_thumbnail_id( $this->ID() ), $size );
240
-		return !empty( $attachment ) ? $attachment[ 0 ] : FALSE;
238
+	public function feature_image_url($size = 'thumbnail') {
239
+		$attachment = wp_get_attachment_image_src(get_post_thumbnail_id($this->ID()), $size);
240
+		return ! empty($attachment) ? $attachment[0] : FALSE;
241 241
 	}
242 242
 
243 243
 
@@ -259,36 +259,36 @@  discard block
 block discarded – undo
259 259
 	 *                                 This array is INDEXED by RELATED OBJ NAME (so it corresponds with the obj_names sent);
260 260
 	 * @return void
261 261
 	 */
262
-	public function restore_revision( $revision_id, $related_obj_names = array(), $where_query = array() ) {
262
+	public function restore_revision($revision_id, $related_obj_names = array(), $where_query = array()) {
263 263
 		//get revision object
264
-		$revision_obj = $this->get_model()->get_one_by_ID( $revision_id );
265
-		if ( $revision_obj instanceof EE_CPT_Base ) {
264
+		$revision_obj = $this->get_model()->get_one_by_ID($revision_id);
265
+		if ($revision_obj instanceof EE_CPT_Base) {
266 266
 			//no related_obj_name so we assume we're saving a revision on this object.
267
-			if ( empty( $related_obj_names ) ) {
267
+			if (empty($related_obj_names)) {
268 268
 				$fields = $this->get_model()->get_meta_table_fields();
269
-				foreach ( $fields as $field ) {
270
-					$this->set( $field, $revision_obj->get( $field ) );
269
+				foreach ($fields as $field) {
270
+					$this->set($field, $revision_obj->get($field));
271 271
 				}
272 272
 				$this->save();
273 273
 			}
274
-			$related_obj_names = (array)$related_obj_names;
275
-			foreach ( $related_obj_names as $related_name ) {
274
+			$related_obj_names = (array) $related_obj_names;
275
+			foreach ($related_obj_names as $related_name) {
276 276
 				//related_obj_name so we're saving a revision on an object related to this object
277 277
 				//do we have $where_query params for this related object?  If we do then we include that.
278
-				$cols_n_values = isset( $where_query[ $related_name ] ) ? $where_query[ $related_name ] : array();
279
-				$where_params = !empty( $cols_n_values ) ? array( $cols_n_values ) : array();
280
-				$related_objs = $this->get_many_related( $related_name, $where_params );
281
-				$revision_related_objs = $revision_obj->get_many_related( $related_name, $where_params );
278
+				$cols_n_values = isset($where_query[$related_name]) ? $where_query[$related_name] : array();
279
+				$where_params = ! empty($cols_n_values) ? array($cols_n_values) : array();
280
+				$related_objs = $this->get_many_related($related_name, $where_params);
281
+				$revision_related_objs = $revision_obj->get_many_related($related_name, $where_params);
282 282
 				//load helper
283 283
 				//remove related objs from this object that are not in revision
284 284
 				//array_diff *should* work cause I think objects are indexed by ID?
285
-				$related_to_remove = EEH_Array::object_array_diff( $related_objs, $revision_related_objs );
286
-				foreach ( $related_to_remove as $rr ) {
287
-					$this->_remove_relation_to( $rr, $related_name, $cols_n_values );
285
+				$related_to_remove = EEH_Array::object_array_diff($related_objs, $revision_related_objs);
286
+				foreach ($related_to_remove as $rr) {
287
+					$this->_remove_relation_to($rr, $related_name, $cols_n_values);
288 288
 				}
289 289
 				//add all related objs attached to revision to this object
290
-				foreach ( $revision_related_objs as $r_obj ) {
291
-					$this->_add_relation_to( $r_obj, $related_name, $cols_n_values );
290
+				foreach ($revision_related_objs as $r_obj) {
291
+					$this->_add_relation_to($r_obj, $related_name, $cols_n_values);
292 292
 				}
293 293
 			}
294 294
 		}
@@ -304,8 +304,8 @@  discard block
 block discarded – undo
304 304
 	 * <li>If $single is set to false, or left blank, the function returns an array containing all values of the specified key.</li>
305 305
 	 * <li>If $single is set to true, the function returns the first value of the specified key (not in an array</li></ul>
306 306
 	 */
307
-	public function get_post_meta( $meta_key = NULL, $single = FALSE ) {
308
-		return get_post_meta( $this->ID(), $meta_key, $single );
307
+	public function get_post_meta($meta_key = NULL, $single = FALSE) {
308
+		return get_post_meta($this->ID(), $meta_key, $single);
309 309
 	}
310 310
 
311 311
 
@@ -317,11 +317,11 @@  discard block
 block discarded – undo
317 317
 	 * @param mixed  $prev_value
318 318
 	 * @return mixed Returns meta_id if the meta doesn't exist, otherwise returns true on success and false on failure. NOTE: If the meta_value passed to this function is the same as the value that is already in the database, this function returns false.
319 319
 	 */
320
-	public function update_post_meta( $meta_key, $meta_value, $prev_value = NULL ) {
321
-		if ( ! $this->ID() ) {
320
+	public function update_post_meta($meta_key, $meta_value, $prev_value = NULL) {
321
+		if ( ! $this->ID()) {
322 322
 			$this->save();
323 323
 		}
324
-		return update_post_meta( $this->ID(), $meta_key, $meta_value, $prev_value );
324
+		return update_post_meta($this->ID(), $meta_key, $meta_value, $prev_value);
325 325
 	}
326 326
 
327 327
 
@@ -333,11 +333,11 @@  discard block
 block discarded – undo
333 333
 	 * @param bool  $unique . If postmeta for this $meta_key already exists, whether to add an additional item or not
334 334
 	 * @return boolean Boolean true, except if the $unique argument was set to true and a custom field with the given key already exists, in which case false is returned.
335 335
 	 */
336
-	public function add_post_meta( $meta_key, $meta_value, $unique = FALSE ) {
337
-		if ( $this->ID() ) {
336
+	public function add_post_meta($meta_key, $meta_value, $unique = FALSE) {
337
+		if ($this->ID()) {
338 338
 			$this->save();
339 339
 		}
340
-		return add_post_meta( $this->ID(), $meta_key, $meta_value, $unique );
340
+		return add_post_meta($this->ID(), $meta_key, $meta_value, $unique);
341 341
 	}
342 342
 
343 343
 
@@ -349,13 +349,13 @@  discard block
 block discarded – undo
349 349
 	 * @param mixed $meta_value
350 350
 	 * @return boolean False for failure. True for success.
351 351
 	 */
352
-	public function delete_post_meta( $meta_key, $meta_value = '' ) {
353
-		if ( ! $this->ID() ) {
352
+	public function delete_post_meta($meta_key, $meta_value = '') {
353
+		if ( ! $this->ID()) {
354 354
 			//there are obviously no postmetas for this if it's not saved
355 355
 			//so let's just report this as a success
356 356
 			return true;
357 357
 		}
358
-		return delete_post_meta( $this->ID(), $meta_key, $meta_value );
358
+		return delete_post_meta($this->ID(), $meta_key, $meta_value);
359 359
 	}
360 360
 
361 361
 
@@ -365,7 +365,7 @@  discard block
 block discarded – undo
365 365
 	 * @return string
366 366
 	 */
367 367
 	public function get_permalink() {
368
-		return get_permalink( $this->ID() );
368
+		return get_permalink($this->ID());
369 369
 	}
370 370
 
371 371
 
@@ -375,8 +375,8 @@  discard block
 block discarded – undo
375 375
 	 * @param array $query_params
376 376
 	 * @return EE_Term_Taxonomy
377 377
 	 */
378
-	public function term_taxonomies( $query_params = array() ) {
379
-		return $this->get_many_related( 'Term_Taxonomy', $query_params );
378
+	public function term_taxonomies($query_params = array()) {
379
+		return $this->get_many_related('Term_Taxonomy', $query_params);
380 380
 	}
381 381
 
382 382
 
@@ -406,7 +406,7 @@  discard block
 block discarded – undo
406 406
     public function __sleep()
407 407
     {
408 408
         $properties_to_serialize = parent::__sleep();
409
-        $properties_to_serialize = array_diff( $properties_to_serialize, array('_wp_post'));
409
+        $properties_to_serialize = array_diff($properties_to_serialize, array('_wp_post'));
410 410
         return $properties_to_serialize;
411 411
     }
412 412
 }
Please login to merge, or discard this patch.
core/libraries/rest_api/controllers/model/Read.php 2 patches
Indentation   +1272 added lines, -1272 removed lines patch added patch discarded remove patch
@@ -9,7 +9,7 @@  discard block
 block discarded – undo
9 9
 use EE_Datetime_Field;
10 10
 
11 11
 if (! defined('EVENT_ESPRESSO_VERSION')) {
12
-    exit('No direct script access allowed');
12
+	exit('No direct script access allowed');
13 13
 }
14 14
 
15 15
 
@@ -27,1277 +27,1277 @@  discard block
 block discarded – undo
27 27
 
28 28
 
29 29
 
30
-    /**
31
-     * @var Calculated_Model_Fields
32
-     */
33
-    protected $_fields_calculator;
34
-
35
-
36
-
37
-    /**
38
-     * Read constructor.
39
-     */
40
-    public function __construct()
41
-    {
42
-        parent::__construct();
43
-        $this->_fields_calculator = new Calculated_Model_Fields();
44
-    }
45
-
46
-
47
-
48
-    /**
49
-     * Handles requests to get all (or a filtered subset) of entities for a particular model
50
-     *
51
-     * @param \WP_REST_Request $request
52
-     * @return \WP_REST_Response|\WP_Error
53
-     */
54
-    public static function handle_request_get_all(\WP_REST_Request $request)
55
-    {
56
-        $controller = new Read();
57
-        try {
58
-            $matches = $controller->parse_route(
59
-                $request->get_route(),
60
-                '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)~',
61
-                array('version', 'model')
62
-            );
63
-            $controller->set_requested_version($matches['version']);
64
-            $model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
65
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name_singular)) {
66
-                return $controller->send_response(
67
-                    new \WP_Error(
68
-                        'endpoint_parsing_error',
69
-                        sprintf(
70
-                            __('There is no model for endpoint %s. Please contact event espresso support',
71
-                                'event_espresso'),
72
-                            $model_name_singular
73
-                        )
74
-                    )
75
-                );
76
-            }
77
-            return $controller->send_response(
78
-                $controller->get_entities_from_model(
79
-                    $controller->get_model_version_info()->load_model($model_name_singular),
80
-                    $request
81
-                )
82
-            );
83
-        } catch (\Exception $e) {
84
-            return $controller->send_response($e);
85
-        }
86
-    }
87
-
88
-
89
-
90
-    /**
91
-     * Prepares and returns schema for any OPTIONS request.
92
-     *
93
-     * @param string $model_name Something like `Event` or `Registration`
94
-     * @param string $version    The API endpoint version being used.
95
-     * @return array
96
-     */
97
-    public static function handle_schema_request($model_name, $version)
98
-    {
99
-        $controller = new Read();
100
-        try {
101
-            $controller->set_requested_version($version);
102
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name)) {
103
-                return array();
104
-            }
105
-            //get the model for this version
106
-            $model = $controller->get_model_version_info()->load_model($model_name);
107
-            $model_schema = new JsonModelSchema($model);
108
-            return $model_schema->getModelSchemaForRelations(
109
-                $controller->get_model_version_info()->relation_settings($model),
110
-                $controller->_customize_schema_for_rest_response(
111
-                    $model,
112
-                    $model_schema->getModelSchemaForFields(
113
-                        $controller->get_model_version_info()->fields_on_model_in_this_version($model),
114
-                        $model_schema->getInitialSchemaStructure()
115
-                    )
116
-                )
117
-            );
118
-        } catch (\Exception $e) {
119
-            return array();
120
-        }
121
-    }
122
-
123
-
124
-
125
-    /**
126
-     * This loops through each field in the given schema for the model and does the following:
127
-     * - add any extra fields that are REST API specific and related to existing fields.
128
-     * - transform default values into the correct format for a REST API response.
129
-     *
130
-     * @param \EEM_Base $model
131
-     * @param array     $schema
132
-     * @return array  The final schema.
133
-     */
134
-    protected function _customize_schema_for_rest_response(\EEM_Base $model, array $schema)
135
-    {
136
-        foreach ($this->get_model_version_info()->fields_on_model_in_this_version($model) as $field_name => $field) {
137
-            $schema = $this->_translate_defaults_for_rest_response(
138
-                $field_name,
139
-                $field,
140
-                $this->_maybe_add_extra_fields_to_schema($field_name, $field, $schema)
141
-            );
142
-        }
143
-        return $schema;
144
-    }
145
-
146
-
147
-
148
-    /**
149
-     * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
150
-     * response.
151
-     *
152
-     * @param                      $field_name
153
-     * @param \EE_Model_Field_Base $field
154
-     * @param array                $schema
155
-     * @return array
156
-     */
157
-    protected function _translate_defaults_for_rest_response($field_name, \EE_Model_Field_Base $field, array $schema)
158
-    {
159
-        if (isset($schema['properties'][$field_name]['default'])) {
160
-            if (is_array($schema['properties'][$field_name]['default'])) {
161
-                foreach ($schema['properties'][$field_name]['default'] as $default_key => $default_value) {
162
-                    if ($default_key === 'raw') {
163
-                        $schema['properties'][$field_name]['default'][$default_key] = Model_Data_Translator::prepare_field_value_for_json(
164
-                            $field,
165
-                            $default_value,
166
-                            $this->get_model_version_info()->requested_version()
167
-                        );
168
-                    }
169
-                }
170
-            } else {
171
-                $schema['properties'][$field_name]['default'] = Model_Data_Translator::prepare_field_value_for_json(
172
-                    $field,
173
-                    $schema['properties'][$field_name]['default'],
174
-                    $this->get_model_version_info()->requested_version()
175
-                );
176
-            }
177
-        }
178
-        return $schema;
179
-    }
180
-
181
-
182
-
183
-    /**
184
-     * Adds additional fields to the schema
185
-     * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
186
-     * needs to be added to the schema.
187
-     *
188
-     * @param                      $field_name
189
-     * @param \EE_Model_Field_Base $field
190
-     * @param array                $schema
191
-     * @return array
192
-     */
193
-    protected function _maybe_add_extra_fields_to_schema($field_name, \EE_Model_Field_Base $field, array $schema)
194
-    {
195
-        if ($field instanceof EE_Datetime_Field) {
196
-            $schema['properties'][$field_name . '_gmt'] = $field->getSchema();
197
-            //modify the description
198
-            $schema['properties'][$field_name . '_gmt']['description'] = sprintf(
199
-                esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
200
-                $field->get_nicename()
201
-            );
202
-        }
203
-        return $schema;
204
-    }
205
-
206
-
207
-
208
-    /**
209
-     * Used to figure out the route from the request when a `WP_REST_Request` object is not available
210
-     *
211
-     * @return string
212
-     */
213
-    protected function get_route_from_request()
214
-    {
215
-        if (isset($GLOBALS['wp'])
216
-            && $GLOBALS['wp'] instanceof \WP
217
-            && isset($GLOBALS['wp']->query_vars['rest_route'])
218
-        ) {
219
-            return $GLOBALS['wp']->query_vars['rest_route'];
220
-        } else {
221
-            return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
222
-        }
223
-    }
224
-
225
-
226
-
227
-    /**
228
-     * Gets a single entity related to the model indicated in the path and its id
229
-     *
230
-     * @param \WP_REST_Request $request
231
-     * @return \WP_REST_Response|\WP_Error
232
-     */
233
-    public static function handle_request_get_one(\WP_REST_Request $request)
234
-    {
235
-        $controller = new Read();
236
-        try {
237
-            $matches = $controller->parse_route(
238
-                $request->get_route(),
239
-                '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)/(.*)~',
240
-                array('version', 'model', 'id'));
241
-            $controller->set_requested_version($matches['version']);
242
-            $model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
243
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name_singular)) {
244
-                return $controller->send_response(
245
-                    new \WP_Error(
246
-                        'endpoint_parsing_error',
247
-                        sprintf(
248
-                            __('There is no model for endpoint %s. Please contact event espresso support',
249
-                                'event_espresso'),
250
-                            $model_name_singular
251
-                        )
252
-                    )
253
-                );
254
-            }
255
-            return $controller->send_response(
256
-                $controller->get_entity_from_model(
257
-                    $controller->get_model_version_info()->load_model($model_name_singular),
258
-                    $request
259
-                )
260
-            );
261
-        } catch (\Exception $e) {
262
-            return $controller->send_response($e);
263
-        }
264
-    }
265
-
266
-
267
-
268
-    /**
269
-     * Gets all the related entities (or if its a belongs-to relation just the one)
270
-     * to the item with the given id
271
-     *
272
-     * @param \WP_REST_Request $request
273
-     * @return \WP_REST_Response|\WP_Error
274
-     */
275
-    public static function handle_request_get_related(\WP_REST_Request $request)
276
-    {
277
-        $controller = new Read();
278
-        try {
279
-            $matches = $controller->parse_route(
280
-                $request->get_route(),
281
-                '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)/(.*)/(.*)~',
282
-                array('version', 'model', 'id', 'related_model')
283
-            );
284
-            $controller->set_requested_version($matches['version']);
285
-            $main_model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
286
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($main_model_name_singular)) {
287
-                return $controller->send_response(
288
-                    new \WP_Error(
289
-                        'endpoint_parsing_error',
290
-                        sprintf(
291
-                            __('There is no model for endpoint %s. Please contact event espresso support',
292
-                                'event_espresso'),
293
-                            $main_model_name_singular
294
-                        )
295
-                    )
296
-                );
297
-            }
298
-            $main_model = $controller->get_model_version_info()->load_model($main_model_name_singular);
299
-            //assume the related model name is plural and try to find the model's name
300
-            $related_model_name_singular = \EEH_Inflector::singularize_and_upper($matches['related_model']);
301
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($related_model_name_singular)) {
302
-                //so the word didn't singularize well. Maybe that's just because it's a singular word?
303
-                $related_model_name_singular = \EEH_Inflector::humanize($matches['related_model']);
304
-            }
305
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($related_model_name_singular)) {
306
-                return $controller->send_response(
307
-                    new \WP_Error(
308
-                        'endpoint_parsing_error',
309
-                        sprintf(
310
-                            __('There is no model for endpoint %s. Please contact event espresso support',
311
-                                'event_espresso'),
312
-                            $related_model_name_singular
313
-                        )
314
-                    )
315
-                );
316
-            }
317
-            return $controller->send_response(
318
-                $controller->get_entities_from_relation(
319
-                    $request->get_param('id'),
320
-                    $main_model->related_settings_for($related_model_name_singular),
321
-                    $request
322
-                )
323
-            );
324
-        } catch (\Exception $e) {
325
-            return $controller->send_response($e);
326
-        }
327
-    }
328
-
329
-
330
-
331
-    /**
332
-     * Gets a collection for the given model and filters
333
-     *
334
-     * @param \EEM_Base        $model
335
-     * @param \WP_REST_Request $request
336
-     * @return array|\WP_Error
337
-     */
338
-    public function get_entities_from_model($model, $request)
339
-    {
340
-        $query_params = $this->create_model_query_params($model, $request->get_params());
341
-        if (! Capabilities::current_user_has_partial_access_to($model, $query_params['caps'])) {
342
-            $model_name_plural = \EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
343
-            return new \WP_Error(
344
-                sprintf('rest_%s_cannot_list', $model_name_plural),
345
-                sprintf(
346
-                    __('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
347
-                    $model_name_plural,
348
-                    Capabilities::get_missing_permissions_string($model, $query_params['caps'])
349
-                ),
350
-                array('status' => 403)
351
-            );
352
-        }
353
-        if (! $request->get_header('no_rest_headers')) {
354
-            $this->_set_headers_from_query_params($model, $query_params);
355
-        }
356
-        /** @type array $results */
357
-        $results = $model->get_all_wpdb_results($query_params);
358
-        $nice_results = array();
359
-        foreach ($results as $result) {
360
-            $nice_results[] = $this->create_entity_from_wpdb_result(
361
-                $model,
362
-                $result,
363
-                $request
364
-            );
365
-        }
366
-        return $nice_results;
367
-    }
368
-
369
-
370
-
371
-    /**
372
-     * @param array                   $primary_model_query_params query params for finding the item from which
373
-     *                                                            relations will be based
374
-     * @param \EE_Model_Relation_Base $relation
375
-     * @param \WP_REST_Request        $request
376
-     * @return \WP_Error|array
377
-     */
378
-    protected function _get_entities_from_relation($primary_model_query_params, $relation, $request)
379
-    {
380
-        $context = $this->validate_context($request->get_param('caps'));
381
-        $model = $relation->get_this_model();
382
-        $related_model = $relation->get_other_model();
383
-        if (! isset($primary_model_query_params[0])) {
384
-            $primary_model_query_params[0] = array();
385
-        }
386
-        //check if they can access the 1st model object
387
-        $primary_model_query_params = array(
388
-            0       => $primary_model_query_params[0],
389
-            'limit' => 1,
390
-        );
391
-        if ($model instanceof \EEM_Soft_Delete_Base) {
392
-            $primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($primary_model_query_params);
393
-        }
394
-        $restricted_query_params = $primary_model_query_params;
395
-        $restricted_query_params['caps'] = $context;
396
-        $this->_set_debug_info('main model query params', $restricted_query_params);
397
-        $this->_set_debug_info('missing caps', Capabilities::get_missing_permissions_string($related_model, $context));
398
-        if (
399
-        ! (
400
-            Capabilities::current_user_has_partial_access_to($related_model, $context)
401
-            && $model->exists($restricted_query_params)
402
-        )
403
-        ) {
404
-            if ($relation instanceof \EE_Belongs_To_Relation) {
405
-                $related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
406
-            } else {
407
-                $related_model_name_maybe_plural = \EEH_Inflector::pluralize_and_lower($related_model->get_this_model_name());
408
-            }
409
-            return new \WP_Error(
410
-                sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
411
-                sprintf(
412
-                    __('Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
413
-                        'event_espresso'),
414
-                    $related_model_name_maybe_plural,
415
-                    $relation->get_this_model()->get_this_model_name(),
416
-                    implode(
417
-                        ',',
418
-                        array_keys(
419
-                            Capabilities::get_missing_permissions($related_model, $context)
420
-                        )
421
-                    )
422
-                ),
423
-                array('status' => 403)
424
-            );
425
-        }
426
-        $query_params = $this->create_model_query_params($relation->get_other_model(), $request->get_params());
427
-        foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
428
-            $query_params[0][$relation->get_this_model()->get_this_model_name()
429
-                             . '.'
430
-                             . $where_condition_key] = $where_condition_value;
431
-        }
432
-        $query_params['default_where_conditions'] = 'none';
433
-        $query_params['caps'] = $context;
434
-        if (! $request->get_header('no_rest_headers')) {
435
-            $this->_set_headers_from_query_params($relation->get_other_model(), $query_params);
436
-        }
437
-        /** @type array $results */
438
-        $results = $relation->get_other_model()->get_all_wpdb_results($query_params);
439
-        $nice_results = array();
440
-        foreach ($results as $result) {
441
-            $nice_result = $this->create_entity_from_wpdb_result(
442
-                $relation->get_other_model(),
443
-                $result,
444
-                $request
445
-            );
446
-            if ($relation instanceof \EE_HABTM_Relation) {
447
-                //put the unusual stuff (properties from the HABTM relation) first, and make sure
448
-                //if there are conflicts we prefer the properties from the main model
449
-                $join_model_result = $this->create_entity_from_wpdb_result(
450
-                    $relation->get_join_model(),
451
-                    $result,
452
-                    $request
453
-                );
454
-                $joined_result = array_merge($nice_result, $join_model_result);
455
-                //but keep the meta stuff from the main model
456
-                if (isset($nice_result['meta'])) {
457
-                    $joined_result['meta'] = $nice_result['meta'];
458
-                }
459
-                $nice_result = $joined_result;
460
-            }
461
-            $nice_results[] = $nice_result;
462
-        }
463
-        if ($relation instanceof \EE_Belongs_To_Relation) {
464
-            return array_shift($nice_results);
465
-        } else {
466
-            return $nice_results;
467
-        }
468
-    }
469
-
470
-
471
-
472
-    /**
473
-     * Gets the collection for given relation object
474
-     * The same as Read::get_entities_from_model(), except if the relation
475
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
476
-     * the join-model-object into the results
477
-     *
478
-     * @param string                  $id the ID of the thing we are fetching related stuff from
479
-     * @param \EE_Model_Relation_Base $relation
480
-     * @param \WP_REST_Request        $request
481
-     * @return array|\WP_Error
482
-     * @throws \EE_Error
483
-     */
484
-    public function get_entities_from_relation($id, $relation, $request)
485
-    {
486
-        if (! $relation->get_this_model()->has_primary_key_field()) {
487
-            throw new \EE_Error(
488
-                sprintf(
489
-                    __('Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
490
-                        'event_espresso'),
491
-                    $relation->get_this_model()->get_this_model_name()
492
-                )
493
-            );
494
-        }
495
-        return $this->_get_entities_from_relation(
496
-            array(
497
-                array(
498
-                    $relation->get_this_model()->primary_key_name() => $id,
499
-                ),
500
-            ),
501
-            $relation,
502
-            $request
503
-        );
504
-    }
505
-
506
-
507
-
508
-    /**
509
-     * Sets the headers that are based on the model and query params,
510
-     * like the total records. This should only be called on the original request
511
-     * from the client, not on subsequent internal
512
-     *
513
-     * @param \EEM_Base $model
514
-     * @param array     $query_params
515
-     * @return void
516
-     */
517
-    protected function _set_headers_from_query_params($model, $query_params)
518
-    {
519
-        $this->_set_debug_info('model query params', $query_params);
520
-        $this->_set_debug_info('missing caps',
521
-            Capabilities::get_missing_permissions_string($model, $query_params['caps']));
522
-        //normally the limit to a 2-part array, where the 2nd item is the limit
523
-        if (! isset($query_params['limit'])) {
524
-            $query_params['limit'] = \EED_Core_Rest_Api::get_default_query_limit();
525
-        }
526
-        if (is_array($query_params['limit'])) {
527
-            $limit_parts = $query_params['limit'];
528
-        } else {
529
-            $limit_parts = explode(',', $query_params['limit']);
530
-            if (count($limit_parts) == 1) {
531
-                $limit_parts = array(0, $limit_parts[0]);
532
-            }
533
-        }
534
-        //remove the group by and having parts of the query, as those will
535
-        //make the sql query return an array of values, instead of just a single value
536
-        unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
537
-        $count = $model->count($query_params, null, true);
538
-        $pages = $count / $limit_parts[1];
539
-        $this->_set_response_header('Total', $count, false);
540
-        $this->_set_response_header('PageSize', $limit_parts[1], false);
541
-        $this->_set_response_header('TotalPages', ceil($pages), false);
542
-    }
543
-
544
-
545
-
546
-    /**
547
-     * Changes database results into REST API entities
548
-     *
549
-     * @param \EEM_Base        $model
550
-     * @param array            $db_row     like results from $wpdb->get_results()
551
-     * @param \WP_REST_Request $rest_request
552
-     * @param string           $deprecated no longer used
553
-     * @return array ready for being converted into json for sending to client
554
-     */
555
-    public function create_entity_from_wpdb_result($model, $db_row, $rest_request, $deprecated = null)
556
-    {
557
-        if (! $rest_request instanceof \WP_REST_Request) {
558
-            //ok so this was called in the old style, where the 3rd arg was
559
-            //$include, and the 4th arg was $context
560
-            //now setup the request just to avoid fatal errors, although we won't be able
561
-            //to truly make use of it because it's kinda devoid of info
562
-            $rest_request = new \WP_REST_Request();
563
-            $rest_request->set_param('include', $rest_request);
564
-            $rest_request->set_param('caps', $deprecated);
565
-        }
566
-        if ($rest_request->get_param('caps') == null) {
567
-            $rest_request->set_param('caps', \EEM_Base::caps_read);
568
-        }
569
-        $entity_array = $this->_create_bare_entity_from_wpdb_results($model, $db_row);
570
-        $entity_array = $this->_add_extra_fields($model, $db_row, $entity_array);
571
-        $entity_array['_links'] = $this->_get_entity_links($model, $db_row, $entity_array);
572
-        $entity_array['_calculated_fields'] = $this->_get_entity_calculations($model, $db_row, $rest_request);
573
-        $entity_array = apply_filters('FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
574
-            $entity_array, $model, $rest_request->get_param('caps'), $rest_request, $this);
575
-        $entity_array = $this->_include_requested_models($model, $rest_request, $entity_array, $db_row);
576
-        $entity_array = apply_filters(
577
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
578
-            $entity_array,
579
-            $model,
580
-            $rest_request->get_param('caps'),
581
-            $rest_request,
582
-            $this
583
-        );
584
-        $result_without_inaccessible_fields = Capabilities::filter_out_inaccessible_entity_fields(
585
-            $entity_array,
586
-            $model,
587
-            $rest_request->get_param('caps'),
588
-            $this->get_model_version_info(),
589
-            $model->get_index_primary_key_string(
590
-                $model->deduce_fields_n_values_from_cols_n_values($db_row)
591
-            )
592
-        );
593
-        $this->_set_debug_info(
594
-            'inaccessible fields',
595
-            array_keys(array_diff_key($entity_array, $result_without_inaccessible_fields))
596
-        );
597
-        return apply_filters(
598
-            'FHEE__Read__create_entity_from_wpdb_results__entity_return',
599
-            $result_without_inaccessible_fields,
600
-            $model,
601
-            $rest_request->get_param('caps')
602
-        );
603
-    }
604
-
605
-
606
-
607
-    /**
608
-     * Creates a REST entity array (JSON object we're going to return in the response, but
609
-     * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
610
-     * from $wpdb->get_row( $sql, ARRAY_A)
611
-     *
612
-     * @param \EEM_Base $model
613
-     * @param array     $db_row
614
-     * @return array entity mostly ready for converting to JSON and sending in the response
615
-     */
616
-    protected function _create_bare_entity_from_wpdb_results(\EEM_Base $model, $db_row)
617
-    {
618
-        $result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
619
-        $result = array_intersect_key($result,
620
-            $this->get_model_version_info()->fields_on_model_in_this_version($model));
621
-        //if this is a CPT, we need to set the global $post to it,
622
-        //otherwise shortcodes etc won't work properly while rendering it
623
-        if ($model instanceof \EEM_CPT_Base) {
624
-            $do_chevy_shuffle = true;
625
-        } else {
626
-            $do_chevy_shuffle = false;
627
-        }
628
-        if ($do_chevy_shuffle) {
629
-            global $post;
630
-            $old_post = $post;
631
-            $post = get_post($result[$model->primary_key_name()]);
632
-            if (! $post instanceof \WP_Post) {
633
-                //well that's weird, because $result is what we JUST fetched from the database
634
-                throw new Rest_Exception(
635
-                    'error_fetching_post_from_database_results',
636
-                    esc_html__(
637
-                        'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
638
-                        'event_espresso'
639
-                    )
640
-                );
641
-            }
642
-            $model_object_classname = 'EE_' . $model->get_this_model_name();
643
-            $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
644
-                $model_object_classname,
645
-                $result,
646
-                false,
647
-                false
648
-                );
649
-        }
650
-        foreach ($result as $field_name => $raw_field_value) {
651
-            $field_obj = $model->field_settings_for($field_name);
652
-            $field_value = $field_obj->prepare_for_set_from_db($raw_field_value);
653
-            if ($this->is_subclass_of_one($field_obj, $this->get_model_version_info()->fields_ignored())) {
654
-                unset($result[$field_name]);
655
-            } elseif (
656
-            $this->is_subclass_of_one($field_obj, $this->get_model_version_info()->fields_that_have_rendered_format())
657
-            ) {
658
-                $result[$field_name] = array(
659
-                    'raw'      => $field_obj->prepare_for_get($field_value),
660
-                    'rendered' => $field_obj->prepare_for_pretty_echoing($field_value),
661
-                );
662
-            } elseif (
663
-            $this->is_subclass_of_one($field_obj, $this->get_model_version_info()->fields_that_have_pretty_format())
664
-            ) {
665
-                $result[$field_name] = array(
666
-                    'raw'    => $field_obj->prepare_for_get($field_value),
667
-                    'pretty' => $field_obj->prepare_for_pretty_echoing($field_value),
668
-                );
669
-            } elseif ($field_obj instanceof \EE_Datetime_Field) {
670
-                if ($field_value instanceof \DateTime) {
671
-                    $timezone = $field_value->getTimezone();
672
-                    $field_value->setTimezone(new \DateTimeZone('UTC'));
673
-                    $result[$field_name . '_gmt'] = Model_Data_Translator::prepare_field_value_for_json(
674
-                        $field_obj,
675
-                        $field_value,
676
-                        $this->get_model_version_info()->requested_version()
677
-                    );
678
-                    $field_value->setTimezone($timezone);
679
-                    $result[$field_name] = Model_Data_Translator::prepare_field_value_for_json(
680
-                        $field_obj,
681
-                        $field_value,
682
-                        $this->get_model_version_info()->requested_version()
683
-                    );
684
-                }
685
-            } else {
686
-                $result[$field_name] = Model_Data_Translator::prepare_field_value_for_json(
687
-                    $field_obj,
688
-                    $field_obj->prepare_for_get($field_value),
689
-                    $this->get_model_version_info()->requested_version()
690
-                );
691
-            }
692
-        }
693
-        if ($do_chevy_shuffle) {
694
-            $post = $old_post;
695
-        }
696
-        return $result;
697
-    }
698
-
699
-
700
-
701
-    /**
702
-     * Adds a few extra fields to the entity response
703
-     *
704
-     * @param \EEM_Base $model
705
-     * @param array     $db_row
706
-     * @param array     $entity_array
707
-     * @return array modified entity
708
-     */
709
-    protected function _add_extra_fields(\EEM_Base $model, $db_row, $entity_array)
710
-    {
711
-        if ($model instanceof \EEM_CPT_Base) {
712
-            $entity_array['link'] = get_permalink($db_row[$model->get_primary_key_field()->get_qualified_column()]);
713
-        }
714
-        return $entity_array;
715
-    }
716
-
717
-
718
-
719
-    /**
720
-     * Gets links we want to add to the response
721
-     *
722
-     * @global \WP_REST_Server $wp_rest_server
723
-     * @param \EEM_Base        $model
724
-     * @param array            $db_row
725
-     * @param array            $entity_array
726
-     * @return array the _links item in the entity
727
-     */
728
-    protected function _get_entity_links($model, $db_row, $entity_array)
729
-    {
730
-        //add basic links
731
-        $links = array();
732
-        if ($model->has_primary_key_field()) {
733
-            $links['self'] = array(
734
-                array(
735
-                    'href' => $this->get_versioned_link_to(
736
-                        \EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
737
-                        . '/'
738
-                        . $entity_array[$model->primary_key_name()]
739
-                    ),
740
-                ),
741
-            );
742
-        }
743
-        $links['collection'] = array(
744
-            array(
745
-                'href' => $this->get_versioned_link_to(
746
-                    \EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
747
-                ),
748
-            ),
749
-        );
750
-        //add links to related models
751
-        if ($model->has_primary_key_field()) {
752
-            foreach ($this->get_model_version_info()->relation_settings($model) as $relation_name => $relation_obj) {
753
-                $related_model_part = Read::get_related_entity_name($relation_name, $relation_obj);
754
-                $links[\EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part] = array(
755
-                    array(
756
-                        'href'   => $this->get_versioned_link_to(
757
-                            \EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
758
-                            . '/'
759
-                            . $entity_array[$model->primary_key_name()]
760
-                            . '/'
761
-                            . $related_model_part
762
-                        ),
763
-                        'single' => $relation_obj instanceof \EE_Belongs_To_Relation ? true : false,
764
-                    ),
765
-                );
766
-            }
767
-        }
768
-        return $links;
769
-    }
770
-
771
-
772
-
773
-    /**
774
-     * Adds the included models indicated in the request to the entity provided
775
-     *
776
-     * @param \EEM_Base        $model
777
-     * @param \WP_REST_Request $rest_request
778
-     * @param array            $entity_array
779
-     * @param array            $db_row
780
-     * @return array the modified entity
781
-     */
782
-    protected function _include_requested_models(
783
-        \EEM_Base $model,
784
-        \WP_REST_Request $rest_request,
785
-        $entity_array,
786
-        $db_row = array()
787
-    ) {
788
-        //if $db_row not included, hope the entity array has what we need
789
-        if (! $db_row) {
790
-            $db_row = $entity_array;
791
-        }
792
-        $includes_for_this_model = $this->explode_and_get_items_prefixed_with($rest_request->get_param('include'), '');
793
-        $includes_for_this_model = $this->_remove_model_names_from_array($includes_for_this_model);
794
-        //if they passed in * or didn't specify any includes, return everything
795
-        if (! in_array('*', $includes_for_this_model)
796
-            && ! empty($includes_for_this_model)
797
-        ) {
798
-            if ($model->has_primary_key_field()) {
799
-                //always include the primary key. ya just gotta know that at least
800
-                $includes_for_this_model[] = $model->primary_key_name();
801
-            }
802
-            if ($this->explode_and_get_items_prefixed_with($rest_request->get_param('calculate'), '')) {
803
-                $includes_for_this_model[] = '_calculated_fields';
804
-            }
805
-            $entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
806
-        }
807
-        $relation_settings = $this->get_model_version_info()->relation_settings($model);
808
-        foreach ($relation_settings as $relation_name => $relation_obj) {
809
-            $related_fields_to_include = $this->explode_and_get_items_prefixed_with(
810
-                $rest_request->get_param('include'),
811
-                $relation_name
812
-            );
813
-            $related_fields_to_calculate = $this->explode_and_get_items_prefixed_with(
814
-                $rest_request->get_param('calculate'),
815
-                $relation_name
816
-            );
817
-            //did they specify they wanted to include a related model, or
818
-            //specific fields from a related model?
819
-            //or did they specify to calculate a field from a related model?
820
-            if ($related_fields_to_include || $related_fields_to_calculate) {
821
-                //if so, we should include at least some part of the related model
822
-                $pretend_related_request = new \WP_REST_Request();
823
-                $pretend_related_request->set_query_params(
824
-                    array(
825
-                        'caps'      => $rest_request->get_param('caps'),
826
-                        'include'   => $related_fields_to_include,
827
-                        'calculate' => $related_fields_to_calculate,
828
-                    )
829
-                );
830
-                $pretend_related_request->add_header('no_rest_headers', true);
831
-                $primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
832
-                    $model->get_index_primary_key_string(
833
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
834
-                    )
835
-                );
836
-                $related_results = $this->_get_entities_from_relation(
837
-                    $primary_model_query_params,
838
-                    $relation_obj,
839
-                    $pretend_related_request
840
-                );
841
-                $entity_array[Read::get_related_entity_name($relation_name, $relation_obj)] = $related_results
842
-                                                                                              instanceof
843
-                                                                                              \WP_Error
844
-                    ? null
845
-                    : $related_results;
846
-            }
847
-        }
848
-        return $entity_array;
849
-    }
850
-
851
-
852
-
853
-    /**
854
-     * Returns a new array with all the names of models removed. Eg
855
-     * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
856
-     *
857
-     * @param array $arr
858
-     * @return array
859
-     */
860
-    private function _remove_model_names_from_array($arr)
861
-    {
862
-        return array_diff($arr, array_keys(\EE_Registry::instance()->non_abstract_db_models));
863
-    }
864
-
865
-
866
-
867
-    /**
868
-     * Gets the calculated fields for the response
869
-     *
870
-     * @param \EEM_Base        $model
871
-     * @param array            $wpdb_row
872
-     * @param \WP_REST_Request $rest_request
873
-     * @return \stdClass the _calculations item in the entity
874
-     */
875
-    protected function _get_entity_calculations($model, $wpdb_row, $rest_request)
876
-    {
877
-        $calculated_fields = $this->explode_and_get_items_prefixed_with(
878
-            $rest_request->get_param('calculate'),
879
-            ''
880
-        );
881
-        //note: setting calculate=* doesn't do anything
882
-        $calculated_fields_to_return = new \stdClass();
883
-        foreach ($calculated_fields as $field_to_calculate) {
884
-            try {
885
-                $calculated_fields_to_return->$field_to_calculate = Model_Data_Translator::prepare_field_value_for_json(
886
-                    null,
887
-                    $this->_fields_calculator->retrieve_calculated_field_value(
888
-                        $model,
889
-                        $field_to_calculate,
890
-                        $wpdb_row,
891
-                        $rest_request,
892
-                        $this
893
-                    ),
894
-                    $this->get_model_version_info()->requested_version()
895
-                );
896
-            } catch (Rest_Exception $e) {
897
-                //if we don't have permission to read it, just leave it out. but let devs know about the problem
898
-                $this->_set_response_header(
899
-                    'Notices-Field-Calculation-Errors['
900
-                    . $e->get_string_code()
901
-                    . ']['
902
-                    . $model->get_this_model_name()
903
-                    . ']['
904
-                    . $field_to_calculate
905
-                    . ']',
906
-                    $e->getMessage(),
907
-                    true
908
-                );
909
-            }
910
-        }
911
-        return $calculated_fields_to_return;
912
-    }
913
-
914
-
915
-
916
-    /**
917
-     * Gets the full URL to the resource, taking the requested version into account
918
-     *
919
-     * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
920
-     * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
921
-     */
922
-    public function get_versioned_link_to($link_part_after_version_and_slash)
923
-    {
924
-        return rest_url(
925
-            \EED_Core_Rest_Api::ee_api_namespace
926
-            . $this->get_model_version_info()->requested_version()
927
-            . '/'
928
-            . $link_part_after_version_and_slash
929
-        );
930
-    }
931
-
932
-
933
-
934
-    /**
935
-     * Gets the correct lowercase name for the relation in the API according
936
-     * to the relation's type
937
-     *
938
-     * @param string                  $relation_name
939
-     * @param \EE_Model_Relation_Base $relation_obj
940
-     * @return string
941
-     */
942
-    public static function get_related_entity_name($relation_name, $relation_obj)
943
-    {
944
-        if ($relation_obj instanceof \EE_Belongs_To_Relation) {
945
-            return strtolower($relation_name);
946
-        } else {
947
-            return \EEH_Inflector::pluralize_and_lower($relation_name);
948
-        }
949
-    }
950
-
951
-
952
-
953
-    /**
954
-     * Gets the one model object with the specified id for the specified model
955
-     *
956
-     * @param \EEM_Base        $model
957
-     * @param \WP_REST_Request $request
958
-     * @return array|\WP_Error
959
-     */
960
-    public function get_entity_from_model($model, $request)
961
-    {
962
-        $query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
963
-        if ($model instanceof \EEM_Soft_Delete_Base) {
964
-            $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
965
-        }
966
-        $restricted_query_params = $query_params;
967
-        $restricted_query_params['caps'] = $this->validate_context($request->get_param('caps'));
968
-        $this->_set_debug_info('model query params', $restricted_query_params);
969
-        $model_rows = $model->get_all_wpdb_results($restricted_query_params);
970
-        if (! empty ($model_rows)) {
971
-            return $this->create_entity_from_wpdb_result(
972
-                $model,
973
-                array_shift($model_rows),
974
-                $request);
975
-        } else {
976
-            //ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
977
-            $lowercase_model_name = strtolower($model->get_this_model_name());
978
-            $model_rows_found_sans_restrictions = $model->get_all_wpdb_results($query_params);
979
-            if (! empty($model_rows_found_sans_restrictions)) {
980
-                //you got shafted- it existed but we didn't want to tell you!
981
-                return new \WP_Error(
982
-                    'rest_user_cannot_read',
983
-                    sprintf(
984
-                        __('Sorry, you cannot read this %1$s. Missing permissions are: %2$s', 'event_espresso'),
985
-                        strtolower($model->get_this_model_name()),
986
-                        Capabilities::get_missing_permissions_string(
987
-                            $model,
988
-                            $this->validate_context($request->get_param('caps')))
989
-                    ),
990
-                    array('status' => 403)
991
-                );
992
-            } else {
993
-                //it's not you. It just doesn't exist
994
-                return new \WP_Error(
995
-                    sprintf('rest_%s_invalid_id', $lowercase_model_name),
996
-                    sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
997
-                    array('status' => 404)
998
-                );
999
-            }
1000
-        }
1001
-    }
1002
-
1003
-
1004
-
1005
-    /**
1006
-     * If a context is provided which isn't valid, maybe it was added in a future
1007
-     * version so just treat it as a default read
1008
-     *
1009
-     * @param string $context
1010
-     * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1011
-     */
1012
-    public function validate_context($context)
1013
-    {
1014
-        if (! $context) {
1015
-            $context = \EEM_Base::caps_read;
1016
-        }
1017
-        $valid_contexts = \EEM_Base::valid_cap_contexts();
1018
-        if (in_array($context, $valid_contexts)) {
1019
-            return $context;
1020
-        } else {
1021
-            return \EEM_Base::caps_read;
1022
-        }
1023
-    }
1024
-
1025
-
1026
-
1027
-    /**
1028
-     * Verifies the passed in value is an allowable default where conditions value.
1029
-     *
1030
-     * @param $default_query_params
1031
-     * @return string
1032
-     */
1033
-    public function validate_default_query_params($default_query_params)
1034
-    {
1035
-        $valid_default_where_conditions_for_api_calls = array(
1036
-            \EEM_Base::default_where_conditions_all,
1037
-            \EEM_Base::default_where_conditions_minimum_all,
1038
-            \EEM_Base::default_where_conditions_minimum_others,
1039
-        );
1040
-        if (! $default_query_params) {
1041
-            $default_query_params = \EEM_Base::default_where_conditions_all;
1042
-        }
1043
-        if (
1044
-        in_array(
1045
-            $default_query_params,
1046
-            $valid_default_where_conditions_for_api_calls,
1047
-            true
1048
-        )
1049
-        ) {
1050
-            return $default_query_params;
1051
-        } else {
1052
-            return \EEM_Base::default_where_conditions_all;
1053
-        }
1054
-    }
1055
-
1056
-
1057
-
1058
-    /**
1059
-     * Translates API filter get parameter into $query_params array used by EEM_Base::get_all().
1060
-     * Note: right now the query parameter keys for fields (and related fields)
1061
-     * can be left as-is, but it's quite possible this will change someday.
1062
-     * Also, this method's contents might be candidate for moving to Model_Data_Translator
1063
-     *
1064
-     * @param \EEM_Base $model
1065
-     * @param array     $query_parameters from $_GET parameter @see Read:handle_request_get_all
1066
-     * @return array like what EEM_Base::get_all() expects or FALSE to indicate
1067
-     *                                    that absolutely no results should be returned
1068
-     * @throws \EE_Error
1069
-     */
1070
-    public function create_model_query_params($model, $query_parameters)
1071
-    {
1072
-        $model_query_params = array();
1073
-        if (isset($query_parameters['where'])) {
1074
-            $model_query_params[0] = Model_Data_Translator::prepare_conditions_query_params_for_models(
1075
-                $query_parameters['where'],
1076
-                $model,
1077
-                $this->get_model_version_info()->requested_version()
1078
-            );
1079
-        }
1080
-        if (isset($query_parameters['order_by'])) {
1081
-            $order_by = $query_parameters['order_by'];
1082
-        } elseif (isset($query_parameters['orderby'])) {
1083
-            $order_by = $query_parameters['orderby'];
1084
-        } else {
1085
-            $order_by = null;
1086
-        }
1087
-        if ($order_by !== null) {
1088
-            if (is_array($order_by)) {
1089
-                $order_by = Model_Data_Translator::prepare_field_names_in_array_keys_from_json($order_by);
1090
-            } else {
1091
-                //it's a single item
1092
-                $order_by = Model_Data_Translator::prepare_field_name_from_json($order_by);
1093
-            }
1094
-            $model_query_params['order_by'] = $order_by;
1095
-        }
1096
-        if (isset($query_parameters['group_by'])) {
1097
-            $group_by = $query_parameters['group_by'];
1098
-        } elseif (isset($query_parameters['groupby'])) {
1099
-            $group_by = $query_parameters['groupby'];
1100
-        } else {
1101
-            $group_by = array_keys($model->get_combined_primary_key_fields());
1102
-        }
1103
-        //make sure they're all real names
1104
-        if (is_array($group_by)) {
1105
-            $group_by = Model_Data_Translator::prepare_field_names_from_json($group_by);
1106
-        }
1107
-        if ($group_by !== null) {
1108
-            $model_query_params['group_by'] = $group_by;
1109
-        }
1110
-        if (isset($query_parameters['having'])) {
1111
-            $model_query_params['having'] = Model_Data_Translator::prepare_conditions_query_params_for_models(
1112
-                $query_parameters['having'],
1113
-                $model,
1114
-                $this->get_model_version_info()->requested_version()
1115
-            );
1116
-        }
1117
-        if (isset($query_parameters['order'])) {
1118
-            $model_query_params['order'] = $query_parameters['order'];
1119
-        }
1120
-        if (isset($query_parameters['mine'])) {
1121
-            $model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1122
-        }
1123
-        if (isset($query_parameters['limit'])) {
1124
-            //limit should be either a string like '23' or '23,43', or an array with two items in it
1125
-            if (! is_array($query_parameters['limit'])) {
1126
-                $limit_array = explode(',', (string)$query_parameters['limit']);
1127
-            } else {
1128
-                $limit_array = $query_parameters['limit'];
1129
-            }
1130
-            $sanitized_limit = array();
1131
-            foreach ($limit_array as $key => $limit_part) {
1132
-                if ($this->_debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1133
-                    throw new \EE_Error(
1134
-                        sprintf(
1135
-                            __('An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1136
-                                'event_espresso'),
1137
-                            wp_json_encode($query_parameters['limit'])
1138
-                        )
1139
-                    );
1140
-                }
1141
-                $sanitized_limit[] = (int)$limit_part;
1142
-            }
1143
-            $model_query_params['limit'] = implode(',', $sanitized_limit);
1144
-        } else {
1145
-            $model_query_params['limit'] = \EED_Core_Rest_Api::get_default_query_limit();
1146
-        }
1147
-        if (isset($query_parameters['caps'])) {
1148
-            $model_query_params['caps'] = $this->validate_context($query_parameters['caps']);
1149
-        } else {
1150
-            $model_query_params['caps'] = \EEM_Base::caps_read;
1151
-        }
1152
-        if (isset($query_parameters['default_where_conditions'])) {
1153
-            $model_query_params['default_where_conditions'] = $this->validate_default_query_params($query_parameters['default_where_conditions']);
1154
-        }
1155
-        return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_parameters, $model);
1156
-    }
1157
-
1158
-
1159
-
1160
-    /**
1161
-     * Changes the REST-style query params for use in the models
1162
-     *
1163
-     * @deprecated
1164
-     * @param \EEM_Base $model
1165
-     * @param array     $query_params sub-array from @see EEM_Base::get_all()
1166
-     * @return array
1167
-     */
1168
-    public function prepare_rest_query_params_key_for_models($model, $query_params)
1169
-    {
1170
-        $model_ready_query_params = array();
1171
-        foreach ($query_params as $key => $value) {
1172
-            if (is_array($value)) {
1173
-                $model_ready_query_params[$key] = $this->prepare_rest_query_params_key_for_models($model, $value);
1174
-            } else {
1175
-                $model_ready_query_params[$key] = $value;
1176
-            }
1177
-        }
1178
-        return $model_ready_query_params;
1179
-    }
1180
-
1181
-
1182
-
1183
-    /**
1184
-     * @deprecated
1185
-     * @param $model
1186
-     * @param $query_params
1187
-     * @return array
1188
-     */
1189
-    public function prepare_rest_query_params_values_for_models($model, $query_params)
1190
-    {
1191
-        $model_ready_query_params = array();
1192
-        foreach ($query_params as $key => $value) {
1193
-            if (is_array($value)) {
1194
-                $model_ready_query_params[$key] = $this->prepare_rest_query_params_values_for_models($model, $value);
1195
-            } else {
1196
-                $model_ready_query_params[$key] = $value;
1197
-            }
1198
-        }
1199
-        return $model_ready_query_params;
1200
-    }
1201
-
1202
-
1203
-
1204
-    /**
1205
-     * Explodes the string on commas, and only returns items with $prefix followed by a period.
1206
-     * If no prefix is specified, returns items with no period.
1207
-     *
1208
-     * @param string|array $string_to_explode eg "jibba,jabba, blah, blaabla" or array('jibba', 'jabba' )
1209
-     * @param string       $prefix            "Event" or "foobar"
1210
-     * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1211
-     *                                        we only return strings starting with that and a period; if no prefix was
1212
-     *                                        specified we return all items containing NO periods
1213
-     */
1214
-    public function explode_and_get_items_prefixed_with($string_to_explode, $prefix)
1215
-    {
1216
-        if (is_string($string_to_explode)) {
1217
-            $exploded_contents = explode(',', $string_to_explode);
1218
-        } else if (is_array($string_to_explode)) {
1219
-            $exploded_contents = $string_to_explode;
1220
-        } else {
1221
-            $exploded_contents = array();
1222
-        }
1223
-        //if the string was empty, we want an empty array
1224
-        $exploded_contents = array_filter($exploded_contents);
1225
-        $contents_with_prefix = array();
1226
-        foreach ($exploded_contents as $item) {
1227
-            $item = trim($item);
1228
-            //if no prefix was provided, so we look for items with no "." in them
1229
-            if (! $prefix) {
1230
-                //does this item have a period?
1231
-                if (strpos($item, '.') === false) {
1232
-                    //if not, then its what we're looking for
1233
-                    $contents_with_prefix[] = $item;
1234
-                }
1235
-            } else if (strpos($item, $prefix . '.') === 0) {
1236
-                //this item has the prefix and a period, grab it
1237
-                $contents_with_prefix[] = substr(
1238
-                    $item,
1239
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1240
-                );
1241
-            } else if ($item === $prefix) {
1242
-                //this item is JUST the prefix
1243
-                //so let's grab everything after, which is a blank string
1244
-                $contents_with_prefix[] = '';
1245
-            }
1246
-        }
1247
-        return $contents_with_prefix;
1248
-    }
1249
-
1250
-
1251
-
1252
-    /**
1253
-     * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1254
-     * Deprecated because its return values were really quite confusing- sometimes it returned
1255
-     * an empty array (when the include string was blank or '*') or sometimes it returned
1256
-     * array('*') (when you provided a model and a model of that kind was found).
1257
-     * Parses the $include_string so we fetch all the field names relating to THIS model
1258
-     * (ie have NO period in them), or for the provided model (ie start with the model
1259
-     * name and then a period).
1260
-     * @param string $include_string @see Read:handle_request_get_all
1261
-     * @param string $model_name
1262
-     * @return array of fields for this model. If $model_name is provided, then
1263
-     *                               the fields for that model, with the model's name removed from each.
1264
-     *                               If $include_string was blank or '*' returns an empty array
1265
-     */
1266
-    public function extract_includes_for_this_model($include_string, $model_name = null)
1267
-    {
1268
-        if (is_array($include_string)) {
1269
-            $include_string = implode(',', $include_string);
1270
-        }
1271
-        if ($include_string === '*' || $include_string === '') {
1272
-            return array();
1273
-        }
1274
-        $includes = explode(',', $include_string);
1275
-        $extracted_fields_to_include = array();
1276
-        if ($model_name) {
1277
-            foreach ($includes as $field_to_include) {
1278
-                $field_to_include = trim($field_to_include);
1279
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1280
-                    //found the model name at the exact start
1281
-                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1282
-                    $extracted_fields_to_include[] = $field_sans_model_name;
1283
-                } elseif ($field_to_include == $model_name) {
1284
-                    $extracted_fields_to_include[] = '*';
1285
-                }
1286
-            }
1287
-        } else {
1288
-            //look for ones with no period
1289
-            foreach ($includes as $field_to_include) {
1290
-                $field_to_include = trim($field_to_include);
1291
-                if (
1292
-                    strpos($field_to_include, '.') === false
1293
-                    && ! $this->get_model_version_info()->is_model_name_in_this_version($field_to_include)
1294
-                ) {
1295
-                    $extracted_fields_to_include[] = $field_to_include;
1296
-                }
1297
-            }
1298
-        }
1299
-        return $extracted_fields_to_include;
1300
-    }
30
+	/**
31
+	 * @var Calculated_Model_Fields
32
+	 */
33
+	protected $_fields_calculator;
34
+
35
+
36
+
37
+	/**
38
+	 * Read constructor.
39
+	 */
40
+	public function __construct()
41
+	{
42
+		parent::__construct();
43
+		$this->_fields_calculator = new Calculated_Model_Fields();
44
+	}
45
+
46
+
47
+
48
+	/**
49
+	 * Handles requests to get all (or a filtered subset) of entities for a particular model
50
+	 *
51
+	 * @param \WP_REST_Request $request
52
+	 * @return \WP_REST_Response|\WP_Error
53
+	 */
54
+	public static function handle_request_get_all(\WP_REST_Request $request)
55
+	{
56
+		$controller = new Read();
57
+		try {
58
+			$matches = $controller->parse_route(
59
+				$request->get_route(),
60
+				'~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)~',
61
+				array('version', 'model')
62
+			);
63
+			$controller->set_requested_version($matches['version']);
64
+			$model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
65
+			if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name_singular)) {
66
+				return $controller->send_response(
67
+					new \WP_Error(
68
+						'endpoint_parsing_error',
69
+						sprintf(
70
+							__('There is no model for endpoint %s. Please contact event espresso support',
71
+								'event_espresso'),
72
+							$model_name_singular
73
+						)
74
+					)
75
+				);
76
+			}
77
+			return $controller->send_response(
78
+				$controller->get_entities_from_model(
79
+					$controller->get_model_version_info()->load_model($model_name_singular),
80
+					$request
81
+				)
82
+			);
83
+		} catch (\Exception $e) {
84
+			return $controller->send_response($e);
85
+		}
86
+	}
87
+
88
+
89
+
90
+	/**
91
+	 * Prepares and returns schema for any OPTIONS request.
92
+	 *
93
+	 * @param string $model_name Something like `Event` or `Registration`
94
+	 * @param string $version    The API endpoint version being used.
95
+	 * @return array
96
+	 */
97
+	public static function handle_schema_request($model_name, $version)
98
+	{
99
+		$controller = new Read();
100
+		try {
101
+			$controller->set_requested_version($version);
102
+			if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name)) {
103
+				return array();
104
+			}
105
+			//get the model for this version
106
+			$model = $controller->get_model_version_info()->load_model($model_name);
107
+			$model_schema = new JsonModelSchema($model);
108
+			return $model_schema->getModelSchemaForRelations(
109
+				$controller->get_model_version_info()->relation_settings($model),
110
+				$controller->_customize_schema_for_rest_response(
111
+					$model,
112
+					$model_schema->getModelSchemaForFields(
113
+						$controller->get_model_version_info()->fields_on_model_in_this_version($model),
114
+						$model_schema->getInitialSchemaStructure()
115
+					)
116
+				)
117
+			);
118
+		} catch (\Exception $e) {
119
+			return array();
120
+		}
121
+	}
122
+
123
+
124
+
125
+	/**
126
+	 * This loops through each field in the given schema for the model and does the following:
127
+	 * - add any extra fields that are REST API specific and related to existing fields.
128
+	 * - transform default values into the correct format for a REST API response.
129
+	 *
130
+	 * @param \EEM_Base $model
131
+	 * @param array     $schema
132
+	 * @return array  The final schema.
133
+	 */
134
+	protected function _customize_schema_for_rest_response(\EEM_Base $model, array $schema)
135
+	{
136
+		foreach ($this->get_model_version_info()->fields_on_model_in_this_version($model) as $field_name => $field) {
137
+			$schema = $this->_translate_defaults_for_rest_response(
138
+				$field_name,
139
+				$field,
140
+				$this->_maybe_add_extra_fields_to_schema($field_name, $field, $schema)
141
+			);
142
+		}
143
+		return $schema;
144
+	}
145
+
146
+
147
+
148
+	/**
149
+	 * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
150
+	 * response.
151
+	 *
152
+	 * @param                      $field_name
153
+	 * @param \EE_Model_Field_Base $field
154
+	 * @param array                $schema
155
+	 * @return array
156
+	 */
157
+	protected function _translate_defaults_for_rest_response($field_name, \EE_Model_Field_Base $field, array $schema)
158
+	{
159
+		if (isset($schema['properties'][$field_name]['default'])) {
160
+			if (is_array($schema['properties'][$field_name]['default'])) {
161
+				foreach ($schema['properties'][$field_name]['default'] as $default_key => $default_value) {
162
+					if ($default_key === 'raw') {
163
+						$schema['properties'][$field_name]['default'][$default_key] = Model_Data_Translator::prepare_field_value_for_json(
164
+							$field,
165
+							$default_value,
166
+							$this->get_model_version_info()->requested_version()
167
+						);
168
+					}
169
+				}
170
+			} else {
171
+				$schema['properties'][$field_name]['default'] = Model_Data_Translator::prepare_field_value_for_json(
172
+					$field,
173
+					$schema['properties'][$field_name]['default'],
174
+					$this->get_model_version_info()->requested_version()
175
+				);
176
+			}
177
+		}
178
+		return $schema;
179
+	}
180
+
181
+
182
+
183
+	/**
184
+	 * Adds additional fields to the schema
185
+	 * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
186
+	 * needs to be added to the schema.
187
+	 *
188
+	 * @param                      $field_name
189
+	 * @param \EE_Model_Field_Base $field
190
+	 * @param array                $schema
191
+	 * @return array
192
+	 */
193
+	protected function _maybe_add_extra_fields_to_schema($field_name, \EE_Model_Field_Base $field, array $schema)
194
+	{
195
+		if ($field instanceof EE_Datetime_Field) {
196
+			$schema['properties'][$field_name . '_gmt'] = $field->getSchema();
197
+			//modify the description
198
+			$schema['properties'][$field_name . '_gmt']['description'] = sprintf(
199
+				esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
200
+				$field->get_nicename()
201
+			);
202
+		}
203
+		return $schema;
204
+	}
205
+
206
+
207
+
208
+	/**
209
+	 * Used to figure out the route from the request when a `WP_REST_Request` object is not available
210
+	 *
211
+	 * @return string
212
+	 */
213
+	protected function get_route_from_request()
214
+	{
215
+		if (isset($GLOBALS['wp'])
216
+			&& $GLOBALS['wp'] instanceof \WP
217
+			&& isset($GLOBALS['wp']->query_vars['rest_route'])
218
+		) {
219
+			return $GLOBALS['wp']->query_vars['rest_route'];
220
+		} else {
221
+			return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
222
+		}
223
+	}
224
+
225
+
226
+
227
+	/**
228
+	 * Gets a single entity related to the model indicated in the path and its id
229
+	 *
230
+	 * @param \WP_REST_Request $request
231
+	 * @return \WP_REST_Response|\WP_Error
232
+	 */
233
+	public static function handle_request_get_one(\WP_REST_Request $request)
234
+	{
235
+		$controller = new Read();
236
+		try {
237
+			$matches = $controller->parse_route(
238
+				$request->get_route(),
239
+				'~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)/(.*)~',
240
+				array('version', 'model', 'id'));
241
+			$controller->set_requested_version($matches['version']);
242
+			$model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
243
+			if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name_singular)) {
244
+				return $controller->send_response(
245
+					new \WP_Error(
246
+						'endpoint_parsing_error',
247
+						sprintf(
248
+							__('There is no model for endpoint %s. Please contact event espresso support',
249
+								'event_espresso'),
250
+							$model_name_singular
251
+						)
252
+					)
253
+				);
254
+			}
255
+			return $controller->send_response(
256
+				$controller->get_entity_from_model(
257
+					$controller->get_model_version_info()->load_model($model_name_singular),
258
+					$request
259
+				)
260
+			);
261
+		} catch (\Exception $e) {
262
+			return $controller->send_response($e);
263
+		}
264
+	}
265
+
266
+
267
+
268
+	/**
269
+	 * Gets all the related entities (or if its a belongs-to relation just the one)
270
+	 * to the item with the given id
271
+	 *
272
+	 * @param \WP_REST_Request $request
273
+	 * @return \WP_REST_Response|\WP_Error
274
+	 */
275
+	public static function handle_request_get_related(\WP_REST_Request $request)
276
+	{
277
+		$controller = new Read();
278
+		try {
279
+			$matches = $controller->parse_route(
280
+				$request->get_route(),
281
+				'~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)/(.*)/(.*)~',
282
+				array('version', 'model', 'id', 'related_model')
283
+			);
284
+			$controller->set_requested_version($matches['version']);
285
+			$main_model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
286
+			if (! $controller->get_model_version_info()->is_model_name_in_this_version($main_model_name_singular)) {
287
+				return $controller->send_response(
288
+					new \WP_Error(
289
+						'endpoint_parsing_error',
290
+						sprintf(
291
+							__('There is no model for endpoint %s. Please contact event espresso support',
292
+								'event_espresso'),
293
+							$main_model_name_singular
294
+						)
295
+					)
296
+				);
297
+			}
298
+			$main_model = $controller->get_model_version_info()->load_model($main_model_name_singular);
299
+			//assume the related model name is plural and try to find the model's name
300
+			$related_model_name_singular = \EEH_Inflector::singularize_and_upper($matches['related_model']);
301
+			if (! $controller->get_model_version_info()->is_model_name_in_this_version($related_model_name_singular)) {
302
+				//so the word didn't singularize well. Maybe that's just because it's a singular word?
303
+				$related_model_name_singular = \EEH_Inflector::humanize($matches['related_model']);
304
+			}
305
+			if (! $controller->get_model_version_info()->is_model_name_in_this_version($related_model_name_singular)) {
306
+				return $controller->send_response(
307
+					new \WP_Error(
308
+						'endpoint_parsing_error',
309
+						sprintf(
310
+							__('There is no model for endpoint %s. Please contact event espresso support',
311
+								'event_espresso'),
312
+							$related_model_name_singular
313
+						)
314
+					)
315
+				);
316
+			}
317
+			return $controller->send_response(
318
+				$controller->get_entities_from_relation(
319
+					$request->get_param('id'),
320
+					$main_model->related_settings_for($related_model_name_singular),
321
+					$request
322
+				)
323
+			);
324
+		} catch (\Exception $e) {
325
+			return $controller->send_response($e);
326
+		}
327
+	}
328
+
329
+
330
+
331
+	/**
332
+	 * Gets a collection for the given model and filters
333
+	 *
334
+	 * @param \EEM_Base        $model
335
+	 * @param \WP_REST_Request $request
336
+	 * @return array|\WP_Error
337
+	 */
338
+	public function get_entities_from_model($model, $request)
339
+	{
340
+		$query_params = $this->create_model_query_params($model, $request->get_params());
341
+		if (! Capabilities::current_user_has_partial_access_to($model, $query_params['caps'])) {
342
+			$model_name_plural = \EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
343
+			return new \WP_Error(
344
+				sprintf('rest_%s_cannot_list', $model_name_plural),
345
+				sprintf(
346
+					__('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
347
+					$model_name_plural,
348
+					Capabilities::get_missing_permissions_string($model, $query_params['caps'])
349
+				),
350
+				array('status' => 403)
351
+			);
352
+		}
353
+		if (! $request->get_header('no_rest_headers')) {
354
+			$this->_set_headers_from_query_params($model, $query_params);
355
+		}
356
+		/** @type array $results */
357
+		$results = $model->get_all_wpdb_results($query_params);
358
+		$nice_results = array();
359
+		foreach ($results as $result) {
360
+			$nice_results[] = $this->create_entity_from_wpdb_result(
361
+				$model,
362
+				$result,
363
+				$request
364
+			);
365
+		}
366
+		return $nice_results;
367
+	}
368
+
369
+
370
+
371
+	/**
372
+	 * @param array                   $primary_model_query_params query params for finding the item from which
373
+	 *                                                            relations will be based
374
+	 * @param \EE_Model_Relation_Base $relation
375
+	 * @param \WP_REST_Request        $request
376
+	 * @return \WP_Error|array
377
+	 */
378
+	protected function _get_entities_from_relation($primary_model_query_params, $relation, $request)
379
+	{
380
+		$context = $this->validate_context($request->get_param('caps'));
381
+		$model = $relation->get_this_model();
382
+		$related_model = $relation->get_other_model();
383
+		if (! isset($primary_model_query_params[0])) {
384
+			$primary_model_query_params[0] = array();
385
+		}
386
+		//check if they can access the 1st model object
387
+		$primary_model_query_params = array(
388
+			0       => $primary_model_query_params[0],
389
+			'limit' => 1,
390
+		);
391
+		if ($model instanceof \EEM_Soft_Delete_Base) {
392
+			$primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($primary_model_query_params);
393
+		}
394
+		$restricted_query_params = $primary_model_query_params;
395
+		$restricted_query_params['caps'] = $context;
396
+		$this->_set_debug_info('main model query params', $restricted_query_params);
397
+		$this->_set_debug_info('missing caps', Capabilities::get_missing_permissions_string($related_model, $context));
398
+		if (
399
+		! (
400
+			Capabilities::current_user_has_partial_access_to($related_model, $context)
401
+			&& $model->exists($restricted_query_params)
402
+		)
403
+		) {
404
+			if ($relation instanceof \EE_Belongs_To_Relation) {
405
+				$related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
406
+			} else {
407
+				$related_model_name_maybe_plural = \EEH_Inflector::pluralize_and_lower($related_model->get_this_model_name());
408
+			}
409
+			return new \WP_Error(
410
+				sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
411
+				sprintf(
412
+					__('Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
413
+						'event_espresso'),
414
+					$related_model_name_maybe_plural,
415
+					$relation->get_this_model()->get_this_model_name(),
416
+					implode(
417
+						',',
418
+						array_keys(
419
+							Capabilities::get_missing_permissions($related_model, $context)
420
+						)
421
+					)
422
+				),
423
+				array('status' => 403)
424
+			);
425
+		}
426
+		$query_params = $this->create_model_query_params($relation->get_other_model(), $request->get_params());
427
+		foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
428
+			$query_params[0][$relation->get_this_model()->get_this_model_name()
429
+							 . '.'
430
+							 . $where_condition_key] = $where_condition_value;
431
+		}
432
+		$query_params['default_where_conditions'] = 'none';
433
+		$query_params['caps'] = $context;
434
+		if (! $request->get_header('no_rest_headers')) {
435
+			$this->_set_headers_from_query_params($relation->get_other_model(), $query_params);
436
+		}
437
+		/** @type array $results */
438
+		$results = $relation->get_other_model()->get_all_wpdb_results($query_params);
439
+		$nice_results = array();
440
+		foreach ($results as $result) {
441
+			$nice_result = $this->create_entity_from_wpdb_result(
442
+				$relation->get_other_model(),
443
+				$result,
444
+				$request
445
+			);
446
+			if ($relation instanceof \EE_HABTM_Relation) {
447
+				//put the unusual stuff (properties from the HABTM relation) first, and make sure
448
+				//if there are conflicts we prefer the properties from the main model
449
+				$join_model_result = $this->create_entity_from_wpdb_result(
450
+					$relation->get_join_model(),
451
+					$result,
452
+					$request
453
+				);
454
+				$joined_result = array_merge($nice_result, $join_model_result);
455
+				//but keep the meta stuff from the main model
456
+				if (isset($nice_result['meta'])) {
457
+					$joined_result['meta'] = $nice_result['meta'];
458
+				}
459
+				$nice_result = $joined_result;
460
+			}
461
+			$nice_results[] = $nice_result;
462
+		}
463
+		if ($relation instanceof \EE_Belongs_To_Relation) {
464
+			return array_shift($nice_results);
465
+		} else {
466
+			return $nice_results;
467
+		}
468
+	}
469
+
470
+
471
+
472
+	/**
473
+	 * Gets the collection for given relation object
474
+	 * The same as Read::get_entities_from_model(), except if the relation
475
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
476
+	 * the join-model-object into the results
477
+	 *
478
+	 * @param string                  $id the ID of the thing we are fetching related stuff from
479
+	 * @param \EE_Model_Relation_Base $relation
480
+	 * @param \WP_REST_Request        $request
481
+	 * @return array|\WP_Error
482
+	 * @throws \EE_Error
483
+	 */
484
+	public function get_entities_from_relation($id, $relation, $request)
485
+	{
486
+		if (! $relation->get_this_model()->has_primary_key_field()) {
487
+			throw new \EE_Error(
488
+				sprintf(
489
+					__('Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
490
+						'event_espresso'),
491
+					$relation->get_this_model()->get_this_model_name()
492
+				)
493
+			);
494
+		}
495
+		return $this->_get_entities_from_relation(
496
+			array(
497
+				array(
498
+					$relation->get_this_model()->primary_key_name() => $id,
499
+				),
500
+			),
501
+			$relation,
502
+			$request
503
+		);
504
+	}
505
+
506
+
507
+
508
+	/**
509
+	 * Sets the headers that are based on the model and query params,
510
+	 * like the total records. This should only be called on the original request
511
+	 * from the client, not on subsequent internal
512
+	 *
513
+	 * @param \EEM_Base $model
514
+	 * @param array     $query_params
515
+	 * @return void
516
+	 */
517
+	protected function _set_headers_from_query_params($model, $query_params)
518
+	{
519
+		$this->_set_debug_info('model query params', $query_params);
520
+		$this->_set_debug_info('missing caps',
521
+			Capabilities::get_missing_permissions_string($model, $query_params['caps']));
522
+		//normally the limit to a 2-part array, where the 2nd item is the limit
523
+		if (! isset($query_params['limit'])) {
524
+			$query_params['limit'] = \EED_Core_Rest_Api::get_default_query_limit();
525
+		}
526
+		if (is_array($query_params['limit'])) {
527
+			$limit_parts = $query_params['limit'];
528
+		} else {
529
+			$limit_parts = explode(',', $query_params['limit']);
530
+			if (count($limit_parts) == 1) {
531
+				$limit_parts = array(0, $limit_parts[0]);
532
+			}
533
+		}
534
+		//remove the group by and having parts of the query, as those will
535
+		//make the sql query return an array of values, instead of just a single value
536
+		unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
537
+		$count = $model->count($query_params, null, true);
538
+		$pages = $count / $limit_parts[1];
539
+		$this->_set_response_header('Total', $count, false);
540
+		$this->_set_response_header('PageSize', $limit_parts[1], false);
541
+		$this->_set_response_header('TotalPages', ceil($pages), false);
542
+	}
543
+
544
+
545
+
546
+	/**
547
+	 * Changes database results into REST API entities
548
+	 *
549
+	 * @param \EEM_Base        $model
550
+	 * @param array            $db_row     like results from $wpdb->get_results()
551
+	 * @param \WP_REST_Request $rest_request
552
+	 * @param string           $deprecated no longer used
553
+	 * @return array ready for being converted into json for sending to client
554
+	 */
555
+	public function create_entity_from_wpdb_result($model, $db_row, $rest_request, $deprecated = null)
556
+	{
557
+		if (! $rest_request instanceof \WP_REST_Request) {
558
+			//ok so this was called in the old style, where the 3rd arg was
559
+			//$include, and the 4th arg was $context
560
+			//now setup the request just to avoid fatal errors, although we won't be able
561
+			//to truly make use of it because it's kinda devoid of info
562
+			$rest_request = new \WP_REST_Request();
563
+			$rest_request->set_param('include', $rest_request);
564
+			$rest_request->set_param('caps', $deprecated);
565
+		}
566
+		if ($rest_request->get_param('caps') == null) {
567
+			$rest_request->set_param('caps', \EEM_Base::caps_read);
568
+		}
569
+		$entity_array = $this->_create_bare_entity_from_wpdb_results($model, $db_row);
570
+		$entity_array = $this->_add_extra_fields($model, $db_row, $entity_array);
571
+		$entity_array['_links'] = $this->_get_entity_links($model, $db_row, $entity_array);
572
+		$entity_array['_calculated_fields'] = $this->_get_entity_calculations($model, $db_row, $rest_request);
573
+		$entity_array = apply_filters('FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
574
+			$entity_array, $model, $rest_request->get_param('caps'), $rest_request, $this);
575
+		$entity_array = $this->_include_requested_models($model, $rest_request, $entity_array, $db_row);
576
+		$entity_array = apply_filters(
577
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
578
+			$entity_array,
579
+			$model,
580
+			$rest_request->get_param('caps'),
581
+			$rest_request,
582
+			$this
583
+		);
584
+		$result_without_inaccessible_fields = Capabilities::filter_out_inaccessible_entity_fields(
585
+			$entity_array,
586
+			$model,
587
+			$rest_request->get_param('caps'),
588
+			$this->get_model_version_info(),
589
+			$model->get_index_primary_key_string(
590
+				$model->deduce_fields_n_values_from_cols_n_values($db_row)
591
+			)
592
+		);
593
+		$this->_set_debug_info(
594
+			'inaccessible fields',
595
+			array_keys(array_diff_key($entity_array, $result_without_inaccessible_fields))
596
+		);
597
+		return apply_filters(
598
+			'FHEE__Read__create_entity_from_wpdb_results__entity_return',
599
+			$result_without_inaccessible_fields,
600
+			$model,
601
+			$rest_request->get_param('caps')
602
+		);
603
+	}
604
+
605
+
606
+
607
+	/**
608
+	 * Creates a REST entity array (JSON object we're going to return in the response, but
609
+	 * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
610
+	 * from $wpdb->get_row( $sql, ARRAY_A)
611
+	 *
612
+	 * @param \EEM_Base $model
613
+	 * @param array     $db_row
614
+	 * @return array entity mostly ready for converting to JSON and sending in the response
615
+	 */
616
+	protected function _create_bare_entity_from_wpdb_results(\EEM_Base $model, $db_row)
617
+	{
618
+		$result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
619
+		$result = array_intersect_key($result,
620
+			$this->get_model_version_info()->fields_on_model_in_this_version($model));
621
+		//if this is a CPT, we need to set the global $post to it,
622
+		//otherwise shortcodes etc won't work properly while rendering it
623
+		if ($model instanceof \EEM_CPT_Base) {
624
+			$do_chevy_shuffle = true;
625
+		} else {
626
+			$do_chevy_shuffle = false;
627
+		}
628
+		if ($do_chevy_shuffle) {
629
+			global $post;
630
+			$old_post = $post;
631
+			$post = get_post($result[$model->primary_key_name()]);
632
+			if (! $post instanceof \WP_Post) {
633
+				//well that's weird, because $result is what we JUST fetched from the database
634
+				throw new Rest_Exception(
635
+					'error_fetching_post_from_database_results',
636
+					esc_html__(
637
+						'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
638
+						'event_espresso'
639
+					)
640
+				);
641
+			}
642
+			$model_object_classname = 'EE_' . $model->get_this_model_name();
643
+			$post->{$model_object_classname} = \EE_Registry::instance()->load_class(
644
+				$model_object_classname,
645
+				$result,
646
+				false,
647
+				false
648
+				);
649
+		}
650
+		foreach ($result as $field_name => $raw_field_value) {
651
+			$field_obj = $model->field_settings_for($field_name);
652
+			$field_value = $field_obj->prepare_for_set_from_db($raw_field_value);
653
+			if ($this->is_subclass_of_one($field_obj, $this->get_model_version_info()->fields_ignored())) {
654
+				unset($result[$field_name]);
655
+			} elseif (
656
+			$this->is_subclass_of_one($field_obj, $this->get_model_version_info()->fields_that_have_rendered_format())
657
+			) {
658
+				$result[$field_name] = array(
659
+					'raw'      => $field_obj->prepare_for_get($field_value),
660
+					'rendered' => $field_obj->prepare_for_pretty_echoing($field_value),
661
+				);
662
+			} elseif (
663
+			$this->is_subclass_of_one($field_obj, $this->get_model_version_info()->fields_that_have_pretty_format())
664
+			) {
665
+				$result[$field_name] = array(
666
+					'raw'    => $field_obj->prepare_for_get($field_value),
667
+					'pretty' => $field_obj->prepare_for_pretty_echoing($field_value),
668
+				);
669
+			} elseif ($field_obj instanceof \EE_Datetime_Field) {
670
+				if ($field_value instanceof \DateTime) {
671
+					$timezone = $field_value->getTimezone();
672
+					$field_value->setTimezone(new \DateTimeZone('UTC'));
673
+					$result[$field_name . '_gmt'] = Model_Data_Translator::prepare_field_value_for_json(
674
+						$field_obj,
675
+						$field_value,
676
+						$this->get_model_version_info()->requested_version()
677
+					);
678
+					$field_value->setTimezone($timezone);
679
+					$result[$field_name] = Model_Data_Translator::prepare_field_value_for_json(
680
+						$field_obj,
681
+						$field_value,
682
+						$this->get_model_version_info()->requested_version()
683
+					);
684
+				}
685
+			} else {
686
+				$result[$field_name] = Model_Data_Translator::prepare_field_value_for_json(
687
+					$field_obj,
688
+					$field_obj->prepare_for_get($field_value),
689
+					$this->get_model_version_info()->requested_version()
690
+				);
691
+			}
692
+		}
693
+		if ($do_chevy_shuffle) {
694
+			$post = $old_post;
695
+		}
696
+		return $result;
697
+	}
698
+
699
+
700
+
701
+	/**
702
+	 * Adds a few extra fields to the entity response
703
+	 *
704
+	 * @param \EEM_Base $model
705
+	 * @param array     $db_row
706
+	 * @param array     $entity_array
707
+	 * @return array modified entity
708
+	 */
709
+	protected function _add_extra_fields(\EEM_Base $model, $db_row, $entity_array)
710
+	{
711
+		if ($model instanceof \EEM_CPT_Base) {
712
+			$entity_array['link'] = get_permalink($db_row[$model->get_primary_key_field()->get_qualified_column()]);
713
+		}
714
+		return $entity_array;
715
+	}
716
+
717
+
718
+
719
+	/**
720
+	 * Gets links we want to add to the response
721
+	 *
722
+	 * @global \WP_REST_Server $wp_rest_server
723
+	 * @param \EEM_Base        $model
724
+	 * @param array            $db_row
725
+	 * @param array            $entity_array
726
+	 * @return array the _links item in the entity
727
+	 */
728
+	protected function _get_entity_links($model, $db_row, $entity_array)
729
+	{
730
+		//add basic links
731
+		$links = array();
732
+		if ($model->has_primary_key_field()) {
733
+			$links['self'] = array(
734
+				array(
735
+					'href' => $this->get_versioned_link_to(
736
+						\EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
737
+						. '/'
738
+						. $entity_array[$model->primary_key_name()]
739
+					),
740
+				),
741
+			);
742
+		}
743
+		$links['collection'] = array(
744
+			array(
745
+				'href' => $this->get_versioned_link_to(
746
+					\EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
747
+				),
748
+			),
749
+		);
750
+		//add links to related models
751
+		if ($model->has_primary_key_field()) {
752
+			foreach ($this->get_model_version_info()->relation_settings($model) as $relation_name => $relation_obj) {
753
+				$related_model_part = Read::get_related_entity_name($relation_name, $relation_obj);
754
+				$links[\EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part] = array(
755
+					array(
756
+						'href'   => $this->get_versioned_link_to(
757
+							\EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
758
+							. '/'
759
+							. $entity_array[$model->primary_key_name()]
760
+							. '/'
761
+							. $related_model_part
762
+						),
763
+						'single' => $relation_obj instanceof \EE_Belongs_To_Relation ? true : false,
764
+					),
765
+				);
766
+			}
767
+		}
768
+		return $links;
769
+	}
770
+
771
+
772
+
773
+	/**
774
+	 * Adds the included models indicated in the request to the entity provided
775
+	 *
776
+	 * @param \EEM_Base        $model
777
+	 * @param \WP_REST_Request $rest_request
778
+	 * @param array            $entity_array
779
+	 * @param array            $db_row
780
+	 * @return array the modified entity
781
+	 */
782
+	protected function _include_requested_models(
783
+		\EEM_Base $model,
784
+		\WP_REST_Request $rest_request,
785
+		$entity_array,
786
+		$db_row = array()
787
+	) {
788
+		//if $db_row not included, hope the entity array has what we need
789
+		if (! $db_row) {
790
+			$db_row = $entity_array;
791
+		}
792
+		$includes_for_this_model = $this->explode_and_get_items_prefixed_with($rest_request->get_param('include'), '');
793
+		$includes_for_this_model = $this->_remove_model_names_from_array($includes_for_this_model);
794
+		//if they passed in * or didn't specify any includes, return everything
795
+		if (! in_array('*', $includes_for_this_model)
796
+			&& ! empty($includes_for_this_model)
797
+		) {
798
+			if ($model->has_primary_key_field()) {
799
+				//always include the primary key. ya just gotta know that at least
800
+				$includes_for_this_model[] = $model->primary_key_name();
801
+			}
802
+			if ($this->explode_and_get_items_prefixed_with($rest_request->get_param('calculate'), '')) {
803
+				$includes_for_this_model[] = '_calculated_fields';
804
+			}
805
+			$entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
806
+		}
807
+		$relation_settings = $this->get_model_version_info()->relation_settings($model);
808
+		foreach ($relation_settings as $relation_name => $relation_obj) {
809
+			$related_fields_to_include = $this->explode_and_get_items_prefixed_with(
810
+				$rest_request->get_param('include'),
811
+				$relation_name
812
+			);
813
+			$related_fields_to_calculate = $this->explode_and_get_items_prefixed_with(
814
+				$rest_request->get_param('calculate'),
815
+				$relation_name
816
+			);
817
+			//did they specify they wanted to include a related model, or
818
+			//specific fields from a related model?
819
+			//or did they specify to calculate a field from a related model?
820
+			if ($related_fields_to_include || $related_fields_to_calculate) {
821
+				//if so, we should include at least some part of the related model
822
+				$pretend_related_request = new \WP_REST_Request();
823
+				$pretend_related_request->set_query_params(
824
+					array(
825
+						'caps'      => $rest_request->get_param('caps'),
826
+						'include'   => $related_fields_to_include,
827
+						'calculate' => $related_fields_to_calculate,
828
+					)
829
+				);
830
+				$pretend_related_request->add_header('no_rest_headers', true);
831
+				$primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
832
+					$model->get_index_primary_key_string(
833
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
834
+					)
835
+				);
836
+				$related_results = $this->_get_entities_from_relation(
837
+					$primary_model_query_params,
838
+					$relation_obj,
839
+					$pretend_related_request
840
+				);
841
+				$entity_array[Read::get_related_entity_name($relation_name, $relation_obj)] = $related_results
842
+																							  instanceof
843
+																							  \WP_Error
844
+					? null
845
+					: $related_results;
846
+			}
847
+		}
848
+		return $entity_array;
849
+	}
850
+
851
+
852
+
853
+	/**
854
+	 * Returns a new array with all the names of models removed. Eg
855
+	 * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
856
+	 *
857
+	 * @param array $arr
858
+	 * @return array
859
+	 */
860
+	private function _remove_model_names_from_array($arr)
861
+	{
862
+		return array_diff($arr, array_keys(\EE_Registry::instance()->non_abstract_db_models));
863
+	}
864
+
865
+
866
+
867
+	/**
868
+	 * Gets the calculated fields for the response
869
+	 *
870
+	 * @param \EEM_Base        $model
871
+	 * @param array            $wpdb_row
872
+	 * @param \WP_REST_Request $rest_request
873
+	 * @return \stdClass the _calculations item in the entity
874
+	 */
875
+	protected function _get_entity_calculations($model, $wpdb_row, $rest_request)
876
+	{
877
+		$calculated_fields = $this->explode_and_get_items_prefixed_with(
878
+			$rest_request->get_param('calculate'),
879
+			''
880
+		);
881
+		//note: setting calculate=* doesn't do anything
882
+		$calculated_fields_to_return = new \stdClass();
883
+		foreach ($calculated_fields as $field_to_calculate) {
884
+			try {
885
+				$calculated_fields_to_return->$field_to_calculate = Model_Data_Translator::prepare_field_value_for_json(
886
+					null,
887
+					$this->_fields_calculator->retrieve_calculated_field_value(
888
+						$model,
889
+						$field_to_calculate,
890
+						$wpdb_row,
891
+						$rest_request,
892
+						$this
893
+					),
894
+					$this->get_model_version_info()->requested_version()
895
+				);
896
+			} catch (Rest_Exception $e) {
897
+				//if we don't have permission to read it, just leave it out. but let devs know about the problem
898
+				$this->_set_response_header(
899
+					'Notices-Field-Calculation-Errors['
900
+					. $e->get_string_code()
901
+					. ']['
902
+					. $model->get_this_model_name()
903
+					. ']['
904
+					. $field_to_calculate
905
+					. ']',
906
+					$e->getMessage(),
907
+					true
908
+				);
909
+			}
910
+		}
911
+		return $calculated_fields_to_return;
912
+	}
913
+
914
+
915
+
916
+	/**
917
+	 * Gets the full URL to the resource, taking the requested version into account
918
+	 *
919
+	 * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
920
+	 * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
921
+	 */
922
+	public function get_versioned_link_to($link_part_after_version_and_slash)
923
+	{
924
+		return rest_url(
925
+			\EED_Core_Rest_Api::ee_api_namespace
926
+			. $this->get_model_version_info()->requested_version()
927
+			. '/'
928
+			. $link_part_after_version_and_slash
929
+		);
930
+	}
931
+
932
+
933
+
934
+	/**
935
+	 * Gets the correct lowercase name for the relation in the API according
936
+	 * to the relation's type
937
+	 *
938
+	 * @param string                  $relation_name
939
+	 * @param \EE_Model_Relation_Base $relation_obj
940
+	 * @return string
941
+	 */
942
+	public static function get_related_entity_name($relation_name, $relation_obj)
943
+	{
944
+		if ($relation_obj instanceof \EE_Belongs_To_Relation) {
945
+			return strtolower($relation_name);
946
+		} else {
947
+			return \EEH_Inflector::pluralize_and_lower($relation_name);
948
+		}
949
+	}
950
+
951
+
952
+
953
+	/**
954
+	 * Gets the one model object with the specified id for the specified model
955
+	 *
956
+	 * @param \EEM_Base        $model
957
+	 * @param \WP_REST_Request $request
958
+	 * @return array|\WP_Error
959
+	 */
960
+	public function get_entity_from_model($model, $request)
961
+	{
962
+		$query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
963
+		if ($model instanceof \EEM_Soft_Delete_Base) {
964
+			$query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
965
+		}
966
+		$restricted_query_params = $query_params;
967
+		$restricted_query_params['caps'] = $this->validate_context($request->get_param('caps'));
968
+		$this->_set_debug_info('model query params', $restricted_query_params);
969
+		$model_rows = $model->get_all_wpdb_results($restricted_query_params);
970
+		if (! empty ($model_rows)) {
971
+			return $this->create_entity_from_wpdb_result(
972
+				$model,
973
+				array_shift($model_rows),
974
+				$request);
975
+		} else {
976
+			//ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
977
+			$lowercase_model_name = strtolower($model->get_this_model_name());
978
+			$model_rows_found_sans_restrictions = $model->get_all_wpdb_results($query_params);
979
+			if (! empty($model_rows_found_sans_restrictions)) {
980
+				//you got shafted- it existed but we didn't want to tell you!
981
+				return new \WP_Error(
982
+					'rest_user_cannot_read',
983
+					sprintf(
984
+						__('Sorry, you cannot read this %1$s. Missing permissions are: %2$s', 'event_espresso'),
985
+						strtolower($model->get_this_model_name()),
986
+						Capabilities::get_missing_permissions_string(
987
+							$model,
988
+							$this->validate_context($request->get_param('caps')))
989
+					),
990
+					array('status' => 403)
991
+				);
992
+			} else {
993
+				//it's not you. It just doesn't exist
994
+				return new \WP_Error(
995
+					sprintf('rest_%s_invalid_id', $lowercase_model_name),
996
+					sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
997
+					array('status' => 404)
998
+				);
999
+			}
1000
+		}
1001
+	}
1002
+
1003
+
1004
+
1005
+	/**
1006
+	 * If a context is provided which isn't valid, maybe it was added in a future
1007
+	 * version so just treat it as a default read
1008
+	 *
1009
+	 * @param string $context
1010
+	 * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1011
+	 */
1012
+	public function validate_context($context)
1013
+	{
1014
+		if (! $context) {
1015
+			$context = \EEM_Base::caps_read;
1016
+		}
1017
+		$valid_contexts = \EEM_Base::valid_cap_contexts();
1018
+		if (in_array($context, $valid_contexts)) {
1019
+			return $context;
1020
+		} else {
1021
+			return \EEM_Base::caps_read;
1022
+		}
1023
+	}
1024
+
1025
+
1026
+
1027
+	/**
1028
+	 * Verifies the passed in value is an allowable default where conditions value.
1029
+	 *
1030
+	 * @param $default_query_params
1031
+	 * @return string
1032
+	 */
1033
+	public function validate_default_query_params($default_query_params)
1034
+	{
1035
+		$valid_default_where_conditions_for_api_calls = array(
1036
+			\EEM_Base::default_where_conditions_all,
1037
+			\EEM_Base::default_where_conditions_minimum_all,
1038
+			\EEM_Base::default_where_conditions_minimum_others,
1039
+		);
1040
+		if (! $default_query_params) {
1041
+			$default_query_params = \EEM_Base::default_where_conditions_all;
1042
+		}
1043
+		if (
1044
+		in_array(
1045
+			$default_query_params,
1046
+			$valid_default_where_conditions_for_api_calls,
1047
+			true
1048
+		)
1049
+		) {
1050
+			return $default_query_params;
1051
+		} else {
1052
+			return \EEM_Base::default_where_conditions_all;
1053
+		}
1054
+	}
1055
+
1056
+
1057
+
1058
+	/**
1059
+	 * Translates API filter get parameter into $query_params array used by EEM_Base::get_all().
1060
+	 * Note: right now the query parameter keys for fields (and related fields)
1061
+	 * can be left as-is, but it's quite possible this will change someday.
1062
+	 * Also, this method's contents might be candidate for moving to Model_Data_Translator
1063
+	 *
1064
+	 * @param \EEM_Base $model
1065
+	 * @param array     $query_parameters from $_GET parameter @see Read:handle_request_get_all
1066
+	 * @return array like what EEM_Base::get_all() expects or FALSE to indicate
1067
+	 *                                    that absolutely no results should be returned
1068
+	 * @throws \EE_Error
1069
+	 */
1070
+	public function create_model_query_params($model, $query_parameters)
1071
+	{
1072
+		$model_query_params = array();
1073
+		if (isset($query_parameters['where'])) {
1074
+			$model_query_params[0] = Model_Data_Translator::prepare_conditions_query_params_for_models(
1075
+				$query_parameters['where'],
1076
+				$model,
1077
+				$this->get_model_version_info()->requested_version()
1078
+			);
1079
+		}
1080
+		if (isset($query_parameters['order_by'])) {
1081
+			$order_by = $query_parameters['order_by'];
1082
+		} elseif (isset($query_parameters['orderby'])) {
1083
+			$order_by = $query_parameters['orderby'];
1084
+		} else {
1085
+			$order_by = null;
1086
+		}
1087
+		if ($order_by !== null) {
1088
+			if (is_array($order_by)) {
1089
+				$order_by = Model_Data_Translator::prepare_field_names_in_array_keys_from_json($order_by);
1090
+			} else {
1091
+				//it's a single item
1092
+				$order_by = Model_Data_Translator::prepare_field_name_from_json($order_by);
1093
+			}
1094
+			$model_query_params['order_by'] = $order_by;
1095
+		}
1096
+		if (isset($query_parameters['group_by'])) {
1097
+			$group_by = $query_parameters['group_by'];
1098
+		} elseif (isset($query_parameters['groupby'])) {
1099
+			$group_by = $query_parameters['groupby'];
1100
+		} else {
1101
+			$group_by = array_keys($model->get_combined_primary_key_fields());
1102
+		}
1103
+		//make sure they're all real names
1104
+		if (is_array($group_by)) {
1105
+			$group_by = Model_Data_Translator::prepare_field_names_from_json($group_by);
1106
+		}
1107
+		if ($group_by !== null) {
1108
+			$model_query_params['group_by'] = $group_by;
1109
+		}
1110
+		if (isset($query_parameters['having'])) {
1111
+			$model_query_params['having'] = Model_Data_Translator::prepare_conditions_query_params_for_models(
1112
+				$query_parameters['having'],
1113
+				$model,
1114
+				$this->get_model_version_info()->requested_version()
1115
+			);
1116
+		}
1117
+		if (isset($query_parameters['order'])) {
1118
+			$model_query_params['order'] = $query_parameters['order'];
1119
+		}
1120
+		if (isset($query_parameters['mine'])) {
1121
+			$model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1122
+		}
1123
+		if (isset($query_parameters['limit'])) {
1124
+			//limit should be either a string like '23' or '23,43', or an array with two items in it
1125
+			if (! is_array($query_parameters['limit'])) {
1126
+				$limit_array = explode(',', (string)$query_parameters['limit']);
1127
+			} else {
1128
+				$limit_array = $query_parameters['limit'];
1129
+			}
1130
+			$sanitized_limit = array();
1131
+			foreach ($limit_array as $key => $limit_part) {
1132
+				if ($this->_debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1133
+					throw new \EE_Error(
1134
+						sprintf(
1135
+							__('An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1136
+								'event_espresso'),
1137
+							wp_json_encode($query_parameters['limit'])
1138
+						)
1139
+					);
1140
+				}
1141
+				$sanitized_limit[] = (int)$limit_part;
1142
+			}
1143
+			$model_query_params['limit'] = implode(',', $sanitized_limit);
1144
+		} else {
1145
+			$model_query_params['limit'] = \EED_Core_Rest_Api::get_default_query_limit();
1146
+		}
1147
+		if (isset($query_parameters['caps'])) {
1148
+			$model_query_params['caps'] = $this->validate_context($query_parameters['caps']);
1149
+		} else {
1150
+			$model_query_params['caps'] = \EEM_Base::caps_read;
1151
+		}
1152
+		if (isset($query_parameters['default_where_conditions'])) {
1153
+			$model_query_params['default_where_conditions'] = $this->validate_default_query_params($query_parameters['default_where_conditions']);
1154
+		}
1155
+		return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_parameters, $model);
1156
+	}
1157
+
1158
+
1159
+
1160
+	/**
1161
+	 * Changes the REST-style query params for use in the models
1162
+	 *
1163
+	 * @deprecated
1164
+	 * @param \EEM_Base $model
1165
+	 * @param array     $query_params sub-array from @see EEM_Base::get_all()
1166
+	 * @return array
1167
+	 */
1168
+	public function prepare_rest_query_params_key_for_models($model, $query_params)
1169
+	{
1170
+		$model_ready_query_params = array();
1171
+		foreach ($query_params as $key => $value) {
1172
+			if (is_array($value)) {
1173
+				$model_ready_query_params[$key] = $this->prepare_rest_query_params_key_for_models($model, $value);
1174
+			} else {
1175
+				$model_ready_query_params[$key] = $value;
1176
+			}
1177
+		}
1178
+		return $model_ready_query_params;
1179
+	}
1180
+
1181
+
1182
+
1183
+	/**
1184
+	 * @deprecated
1185
+	 * @param $model
1186
+	 * @param $query_params
1187
+	 * @return array
1188
+	 */
1189
+	public function prepare_rest_query_params_values_for_models($model, $query_params)
1190
+	{
1191
+		$model_ready_query_params = array();
1192
+		foreach ($query_params as $key => $value) {
1193
+			if (is_array($value)) {
1194
+				$model_ready_query_params[$key] = $this->prepare_rest_query_params_values_for_models($model, $value);
1195
+			} else {
1196
+				$model_ready_query_params[$key] = $value;
1197
+			}
1198
+		}
1199
+		return $model_ready_query_params;
1200
+	}
1201
+
1202
+
1203
+
1204
+	/**
1205
+	 * Explodes the string on commas, and only returns items with $prefix followed by a period.
1206
+	 * If no prefix is specified, returns items with no period.
1207
+	 *
1208
+	 * @param string|array $string_to_explode eg "jibba,jabba, blah, blaabla" or array('jibba', 'jabba' )
1209
+	 * @param string       $prefix            "Event" or "foobar"
1210
+	 * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1211
+	 *                                        we only return strings starting with that and a period; if no prefix was
1212
+	 *                                        specified we return all items containing NO periods
1213
+	 */
1214
+	public function explode_and_get_items_prefixed_with($string_to_explode, $prefix)
1215
+	{
1216
+		if (is_string($string_to_explode)) {
1217
+			$exploded_contents = explode(',', $string_to_explode);
1218
+		} else if (is_array($string_to_explode)) {
1219
+			$exploded_contents = $string_to_explode;
1220
+		} else {
1221
+			$exploded_contents = array();
1222
+		}
1223
+		//if the string was empty, we want an empty array
1224
+		$exploded_contents = array_filter($exploded_contents);
1225
+		$contents_with_prefix = array();
1226
+		foreach ($exploded_contents as $item) {
1227
+			$item = trim($item);
1228
+			//if no prefix was provided, so we look for items with no "." in them
1229
+			if (! $prefix) {
1230
+				//does this item have a period?
1231
+				if (strpos($item, '.') === false) {
1232
+					//if not, then its what we're looking for
1233
+					$contents_with_prefix[] = $item;
1234
+				}
1235
+			} else if (strpos($item, $prefix . '.') === 0) {
1236
+				//this item has the prefix and a period, grab it
1237
+				$contents_with_prefix[] = substr(
1238
+					$item,
1239
+					strpos($item, $prefix . '.') + strlen($prefix . '.')
1240
+				);
1241
+			} else if ($item === $prefix) {
1242
+				//this item is JUST the prefix
1243
+				//so let's grab everything after, which is a blank string
1244
+				$contents_with_prefix[] = '';
1245
+			}
1246
+		}
1247
+		return $contents_with_prefix;
1248
+	}
1249
+
1250
+
1251
+
1252
+	/**
1253
+	 * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1254
+	 * Deprecated because its return values were really quite confusing- sometimes it returned
1255
+	 * an empty array (when the include string was blank or '*') or sometimes it returned
1256
+	 * array('*') (when you provided a model and a model of that kind was found).
1257
+	 * Parses the $include_string so we fetch all the field names relating to THIS model
1258
+	 * (ie have NO period in them), or for the provided model (ie start with the model
1259
+	 * name and then a period).
1260
+	 * @param string $include_string @see Read:handle_request_get_all
1261
+	 * @param string $model_name
1262
+	 * @return array of fields for this model. If $model_name is provided, then
1263
+	 *                               the fields for that model, with the model's name removed from each.
1264
+	 *                               If $include_string was blank or '*' returns an empty array
1265
+	 */
1266
+	public function extract_includes_for_this_model($include_string, $model_name = null)
1267
+	{
1268
+		if (is_array($include_string)) {
1269
+			$include_string = implode(',', $include_string);
1270
+		}
1271
+		if ($include_string === '*' || $include_string === '') {
1272
+			return array();
1273
+		}
1274
+		$includes = explode(',', $include_string);
1275
+		$extracted_fields_to_include = array();
1276
+		if ($model_name) {
1277
+			foreach ($includes as $field_to_include) {
1278
+				$field_to_include = trim($field_to_include);
1279
+				if (strpos($field_to_include, $model_name . '.') === 0) {
1280
+					//found the model name at the exact start
1281
+					$field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1282
+					$extracted_fields_to_include[] = $field_sans_model_name;
1283
+				} elseif ($field_to_include == $model_name) {
1284
+					$extracted_fields_to_include[] = '*';
1285
+				}
1286
+			}
1287
+		} else {
1288
+			//look for ones with no period
1289
+			foreach ($includes as $field_to_include) {
1290
+				$field_to_include = trim($field_to_include);
1291
+				if (
1292
+					strpos($field_to_include, '.') === false
1293
+					&& ! $this->get_model_version_info()->is_model_name_in_this_version($field_to_include)
1294
+				) {
1295
+					$extracted_fields_to_include[] = $field_to_include;
1296
+				}
1297
+			}
1298
+		}
1299
+		return $extracted_fields_to_include;
1300
+	}
1301 1301
 }
1302 1302
 
1303 1303
 
Please login to merge, or discard this patch.
Spacing   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -8,7 +8,7 @@  discard block
 block discarded – undo
8 8
 use EventEspresso\core\entities\models\JsonModelSchema;
9 9
 use EE_Datetime_Field;
10 10
 
11
-if (! defined('EVENT_ESPRESSO_VERSION')) {
11
+if ( ! defined('EVENT_ESPRESSO_VERSION')) {
12 12
     exit('No direct script access allowed');
13 13
 }
14 14
 
@@ -57,12 +57,12 @@  discard block
 block discarded – undo
57 57
         try {
58 58
             $matches = $controller->parse_route(
59 59
                 $request->get_route(),
60
-                '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)~',
60
+                '~'.\EED_Core_Rest_Api::ee_api_namespace_for_regex.'(.*)~',
61 61
                 array('version', 'model')
62 62
             );
63 63
             $controller->set_requested_version($matches['version']);
64 64
             $model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
65
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name_singular)) {
65
+            if ( ! $controller->get_model_version_info()->is_model_name_in_this_version($model_name_singular)) {
66 66
                 return $controller->send_response(
67 67
                     new \WP_Error(
68 68
                         'endpoint_parsing_error',
@@ -99,7 +99,7 @@  discard block
 block discarded – undo
99 99
         $controller = new Read();
100 100
         try {
101 101
             $controller->set_requested_version($version);
102
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name)) {
102
+            if ( ! $controller->get_model_version_info()->is_model_name_in_this_version($model_name)) {
103 103
                 return array();
104 104
             }
105 105
             //get the model for this version
@@ -193,9 +193,9 @@  discard block
 block discarded – undo
193 193
     protected function _maybe_add_extra_fields_to_schema($field_name, \EE_Model_Field_Base $field, array $schema)
194 194
     {
195 195
         if ($field instanceof EE_Datetime_Field) {
196
-            $schema['properties'][$field_name . '_gmt'] = $field->getSchema();
196
+            $schema['properties'][$field_name.'_gmt'] = $field->getSchema();
197 197
             //modify the description
198
-            $schema['properties'][$field_name . '_gmt']['description'] = sprintf(
198
+            $schema['properties'][$field_name.'_gmt']['description'] = sprintf(
199 199
                 esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
200 200
                 $field->get_nicename()
201 201
             );
@@ -236,11 +236,11 @@  discard block
 block discarded – undo
236 236
         try {
237 237
             $matches = $controller->parse_route(
238 238
                 $request->get_route(),
239
-                '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)/(.*)~',
239
+                '~'.\EED_Core_Rest_Api::ee_api_namespace_for_regex.'(.*)/(.*)~',
240 240
                 array('version', 'model', 'id'));
241 241
             $controller->set_requested_version($matches['version']);
242 242
             $model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
243
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($model_name_singular)) {
243
+            if ( ! $controller->get_model_version_info()->is_model_name_in_this_version($model_name_singular)) {
244 244
                 return $controller->send_response(
245 245
                     new \WP_Error(
246 246
                         'endpoint_parsing_error',
@@ -278,12 +278,12 @@  discard block
 block discarded – undo
278 278
         try {
279 279
             $matches = $controller->parse_route(
280 280
                 $request->get_route(),
281
-                '~' . \EED_Core_Rest_Api::ee_api_namespace_for_regex . '(.*)/(.*)/(.*)~',
281
+                '~'.\EED_Core_Rest_Api::ee_api_namespace_for_regex.'(.*)/(.*)/(.*)~',
282 282
                 array('version', 'model', 'id', 'related_model')
283 283
             );
284 284
             $controller->set_requested_version($matches['version']);
285 285
             $main_model_name_singular = \EEH_Inflector::singularize_and_upper($matches['model']);
286
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($main_model_name_singular)) {
286
+            if ( ! $controller->get_model_version_info()->is_model_name_in_this_version($main_model_name_singular)) {
287 287
                 return $controller->send_response(
288 288
                     new \WP_Error(
289 289
                         'endpoint_parsing_error',
@@ -298,11 +298,11 @@  discard block
 block discarded – undo
298 298
             $main_model = $controller->get_model_version_info()->load_model($main_model_name_singular);
299 299
             //assume the related model name is plural and try to find the model's name
300 300
             $related_model_name_singular = \EEH_Inflector::singularize_and_upper($matches['related_model']);
301
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($related_model_name_singular)) {
301
+            if ( ! $controller->get_model_version_info()->is_model_name_in_this_version($related_model_name_singular)) {
302 302
                 //so the word didn't singularize well. Maybe that's just because it's a singular word?
303 303
                 $related_model_name_singular = \EEH_Inflector::humanize($matches['related_model']);
304 304
             }
305
-            if (! $controller->get_model_version_info()->is_model_name_in_this_version($related_model_name_singular)) {
305
+            if ( ! $controller->get_model_version_info()->is_model_name_in_this_version($related_model_name_singular)) {
306 306
                 return $controller->send_response(
307 307
                     new \WP_Error(
308 308
                         'endpoint_parsing_error',
@@ -338,7 +338,7 @@  discard block
 block discarded – undo
338 338
     public function get_entities_from_model($model, $request)
339 339
     {
340 340
         $query_params = $this->create_model_query_params($model, $request->get_params());
341
-        if (! Capabilities::current_user_has_partial_access_to($model, $query_params['caps'])) {
341
+        if ( ! Capabilities::current_user_has_partial_access_to($model, $query_params['caps'])) {
342 342
             $model_name_plural = \EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
343 343
             return new \WP_Error(
344 344
                 sprintf('rest_%s_cannot_list', $model_name_plural),
@@ -350,7 +350,7 @@  discard block
 block discarded – undo
350 350
                 array('status' => 403)
351 351
             );
352 352
         }
353
-        if (! $request->get_header('no_rest_headers')) {
353
+        if ( ! $request->get_header('no_rest_headers')) {
354 354
             $this->_set_headers_from_query_params($model, $query_params);
355 355
         }
356 356
         /** @type array $results */
@@ -380,7 +380,7 @@  discard block
 block discarded – undo
380 380
         $context = $this->validate_context($request->get_param('caps'));
381 381
         $model = $relation->get_this_model();
382 382
         $related_model = $relation->get_other_model();
383
-        if (! isset($primary_model_query_params[0])) {
383
+        if ( ! isset($primary_model_query_params[0])) {
384 384
             $primary_model_query_params[0] = array();
385 385
         }
386 386
         //check if they can access the 1st model object
@@ -431,7 +431,7 @@  discard block
 block discarded – undo
431 431
         }
432 432
         $query_params['default_where_conditions'] = 'none';
433 433
         $query_params['caps'] = $context;
434
-        if (! $request->get_header('no_rest_headers')) {
434
+        if ( ! $request->get_header('no_rest_headers')) {
435 435
             $this->_set_headers_from_query_params($relation->get_other_model(), $query_params);
436 436
         }
437 437
         /** @type array $results */
@@ -483,7 +483,7 @@  discard block
 block discarded – undo
483 483
      */
484 484
     public function get_entities_from_relation($id, $relation, $request)
485 485
     {
486
-        if (! $relation->get_this_model()->has_primary_key_field()) {
486
+        if ( ! $relation->get_this_model()->has_primary_key_field()) {
487 487
             throw new \EE_Error(
488 488
                 sprintf(
489 489
                     __('Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
@@ -520,7 +520,7 @@  discard block
 block discarded – undo
520 520
         $this->_set_debug_info('missing caps',
521 521
             Capabilities::get_missing_permissions_string($model, $query_params['caps']));
522 522
         //normally the limit to a 2-part array, where the 2nd item is the limit
523
-        if (! isset($query_params['limit'])) {
523
+        if ( ! isset($query_params['limit'])) {
524 524
             $query_params['limit'] = \EED_Core_Rest_Api::get_default_query_limit();
525 525
         }
526 526
         if (is_array($query_params['limit'])) {
@@ -554,7 +554,7 @@  discard block
 block discarded – undo
554 554
      */
555 555
     public function create_entity_from_wpdb_result($model, $db_row, $rest_request, $deprecated = null)
556 556
     {
557
-        if (! $rest_request instanceof \WP_REST_Request) {
557
+        if ( ! $rest_request instanceof \WP_REST_Request) {
558 558
             //ok so this was called in the old style, where the 3rd arg was
559 559
             //$include, and the 4th arg was $context
560 560
             //now setup the request just to avoid fatal errors, although we won't be able
@@ -629,7 +629,7 @@  discard block
 block discarded – undo
629 629
             global $post;
630 630
             $old_post = $post;
631 631
             $post = get_post($result[$model->primary_key_name()]);
632
-            if (! $post instanceof \WP_Post) {
632
+            if ( ! $post instanceof \WP_Post) {
633 633
                 //well that's weird, because $result is what we JUST fetched from the database
634 634
                 throw new Rest_Exception(
635 635
                     'error_fetching_post_from_database_results',
@@ -639,7 +639,7 @@  discard block
 block discarded – undo
639 639
                     )
640 640
                 );
641 641
             }
642
-            $model_object_classname = 'EE_' . $model->get_this_model_name();
642
+            $model_object_classname = 'EE_'.$model->get_this_model_name();
643 643
             $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
644 644
                 $model_object_classname,
645 645
                 $result,
@@ -670,7 +670,7 @@  discard block
 block discarded – undo
670 670
                 if ($field_value instanceof \DateTime) {
671 671
                     $timezone = $field_value->getTimezone();
672 672
                     $field_value->setTimezone(new \DateTimeZone('UTC'));
673
-                    $result[$field_name . '_gmt'] = Model_Data_Translator::prepare_field_value_for_json(
673
+                    $result[$field_name.'_gmt'] = Model_Data_Translator::prepare_field_value_for_json(
674 674
                         $field_obj,
675 675
                         $field_value,
676 676
                         $this->get_model_version_info()->requested_version()
@@ -751,7 +751,7 @@  discard block
 block discarded – undo
751 751
         if ($model->has_primary_key_field()) {
752 752
             foreach ($this->get_model_version_info()->relation_settings($model) as $relation_name => $relation_obj) {
753 753
                 $related_model_part = Read::get_related_entity_name($relation_name, $relation_obj);
754
-                $links[\EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part] = array(
754
+                $links[\EED_Core_Rest_Api::ee_api_link_namespace.$related_model_part] = array(
755 755
                     array(
756 756
                         'href'   => $this->get_versioned_link_to(
757 757
                             \EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
@@ -786,13 +786,13 @@  discard block
 block discarded – undo
786 786
         $db_row = array()
787 787
     ) {
788 788
         //if $db_row not included, hope the entity array has what we need
789
-        if (! $db_row) {
789
+        if ( ! $db_row) {
790 790
             $db_row = $entity_array;
791 791
         }
792 792
         $includes_for_this_model = $this->explode_and_get_items_prefixed_with($rest_request->get_param('include'), '');
793 793
         $includes_for_this_model = $this->_remove_model_names_from_array($includes_for_this_model);
794 794
         //if they passed in * or didn't specify any includes, return everything
795
-        if (! in_array('*', $includes_for_this_model)
795
+        if ( ! in_array('*', $includes_for_this_model)
796 796
             && ! empty($includes_for_this_model)
797 797
         ) {
798 798
             if ($model->has_primary_key_field()) {
@@ -967,7 +967,7 @@  discard block
 block discarded – undo
967 967
         $restricted_query_params['caps'] = $this->validate_context($request->get_param('caps'));
968 968
         $this->_set_debug_info('model query params', $restricted_query_params);
969 969
         $model_rows = $model->get_all_wpdb_results($restricted_query_params);
970
-        if (! empty ($model_rows)) {
970
+        if ( ! empty ($model_rows)) {
971 971
             return $this->create_entity_from_wpdb_result(
972 972
                 $model,
973 973
                 array_shift($model_rows),
@@ -976,7 +976,7 @@  discard block
 block discarded – undo
976 976
             //ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
977 977
             $lowercase_model_name = strtolower($model->get_this_model_name());
978 978
             $model_rows_found_sans_restrictions = $model->get_all_wpdb_results($query_params);
979
-            if (! empty($model_rows_found_sans_restrictions)) {
979
+            if ( ! empty($model_rows_found_sans_restrictions)) {
980 980
                 //you got shafted- it existed but we didn't want to tell you!
981 981
                 return new \WP_Error(
982 982
                     'rest_user_cannot_read',
@@ -1011,7 +1011,7 @@  discard block
 block discarded – undo
1011 1011
      */
1012 1012
     public function validate_context($context)
1013 1013
     {
1014
-        if (! $context) {
1014
+        if ( ! $context) {
1015 1015
             $context = \EEM_Base::caps_read;
1016 1016
         }
1017 1017
         $valid_contexts = \EEM_Base::valid_cap_contexts();
@@ -1037,7 +1037,7 @@  discard block
 block discarded – undo
1037 1037
             \EEM_Base::default_where_conditions_minimum_all,
1038 1038
             \EEM_Base::default_where_conditions_minimum_others,
1039 1039
         );
1040
-        if (! $default_query_params) {
1040
+        if ( ! $default_query_params) {
1041 1041
             $default_query_params = \EEM_Base::default_where_conditions_all;
1042 1042
         }
1043 1043
         if (
@@ -1122,14 +1122,14 @@  discard block
 block discarded – undo
1122 1122
         }
1123 1123
         if (isset($query_parameters['limit'])) {
1124 1124
             //limit should be either a string like '23' or '23,43', or an array with two items in it
1125
-            if (! is_array($query_parameters['limit'])) {
1126
-                $limit_array = explode(',', (string)$query_parameters['limit']);
1125
+            if ( ! is_array($query_parameters['limit'])) {
1126
+                $limit_array = explode(',', (string) $query_parameters['limit']);
1127 1127
             } else {
1128 1128
                 $limit_array = $query_parameters['limit'];
1129 1129
             }
1130 1130
             $sanitized_limit = array();
1131 1131
             foreach ($limit_array as $key => $limit_part) {
1132
-                if ($this->_debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1132
+                if ($this->_debug_mode && ( ! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1133 1133
                     throw new \EE_Error(
1134 1134
                         sprintf(
1135 1135
                             __('An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
@@ -1138,7 +1138,7 @@  discard block
 block discarded – undo
1138 1138
                         )
1139 1139
                     );
1140 1140
                 }
1141
-                $sanitized_limit[] = (int)$limit_part;
1141
+                $sanitized_limit[] = (int) $limit_part;
1142 1142
             }
1143 1143
             $model_query_params['limit'] = implode(',', $sanitized_limit);
1144 1144
         } else {
@@ -1226,17 +1226,17 @@  discard block
 block discarded – undo
1226 1226
         foreach ($exploded_contents as $item) {
1227 1227
             $item = trim($item);
1228 1228
             //if no prefix was provided, so we look for items with no "." in them
1229
-            if (! $prefix) {
1229
+            if ( ! $prefix) {
1230 1230
                 //does this item have a period?
1231 1231
                 if (strpos($item, '.') === false) {
1232 1232
                     //if not, then its what we're looking for
1233 1233
                     $contents_with_prefix[] = $item;
1234 1234
                 }
1235
-            } else if (strpos($item, $prefix . '.') === 0) {
1235
+            } else if (strpos($item, $prefix.'.') === 0) {
1236 1236
                 //this item has the prefix and a period, grab it
1237 1237
                 $contents_with_prefix[] = substr(
1238 1238
                     $item,
1239
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1239
+                    strpos($item, $prefix.'.') + strlen($prefix.'.')
1240 1240
                 );
1241 1241
             } else if ($item === $prefix) {
1242 1242
                 //this item is JUST the prefix
@@ -1276,9 +1276,9 @@  discard block
 block discarded – undo
1276 1276
         if ($model_name) {
1277 1277
             foreach ($includes as $field_to_include) {
1278 1278
                 $field_to_include = trim($field_to_include);
1279
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1279
+                if (strpos($field_to_include, $model_name.'.') === 0) {
1280 1280
                     //found the model name at the exact start
1281
-                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1281
+                    $field_sans_model_name = str_replace($model_name.'.', '', $field_to_include);
1282 1282
                     $extracted_fields_to_include[] = $field_sans_model_name;
1283 1283
                 } elseif ($field_to_include == $model_name) {
1284 1284
                     $extracted_fields_to_include[] = '*';
Please login to merge, or discard this patch.
core/services/shortcodes/EspressoShortcode.php 2 patches
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -84,7 +84,7 @@  discard block
 block discarded – undo
84 84
             }
85 85
         }
86 86
         return $this->shortcodeContent(
87
-            $this->sanitizeAttributes((array)$attributes)
87
+            $this->sanitizeAttributes((array) $attributes)
88 88
         );
89 89
     }
90 90
 
@@ -112,8 +112,8 @@  discard block
 block discarded – undo
112 112
             // serialized attributes
113 113
             wp_json_encode($attributes),
114 114
             // Closure for generating content if cache is expired
115
-            function () use ($shortcode, $attributes) {
116
-                if($shortcode->initialized() === false){
115
+            function() use ($shortcode, $attributes) {
116
+                if ($shortcode->initialized() === false) {
117 117
                     $shortcode->initializeShortcode();
118 118
                 }
119 119
                 return $shortcode->processShortcode($attributes);
@@ -247,7 +247,7 @@  discard block
 block discarded – undo
247 247
      * Returns whether or not this shortcode has been initialized
248 248
      * @return boolean
249 249
      */
250
-    public function initialized(){
250
+    public function initialized() {
251 251
         return $this->initialized;
252 252
     }
253 253
 
Please login to merge, or discard this patch.
Indentation   +224 added lines, -224 removed lines patch added patch discarded remove patch
@@ -23,230 +23,230 @@
 block discarded – undo
23 23
 abstract class EspressoShortcode implements ShortcodeInterface
24 24
 {
25 25
 
26
-    /**
27
-     * transient prefix
28
-     *
29
-     * @type string
30
-     */
31
-    const CACHE_TRANSIENT_PREFIX = 'ee_sc_';
32
-
33
-    /**
34
-     * @var PostRelatedCacheManager $cache_manager
35
-     */
36
-    private $cache_manager;
37
-
38
-    /**
39
-     * true if ShortcodeInterface::initializeShortcode() has been called
40
-     * if false, then that will get called before processing
41
-     *
42
-     * @var boolean $initialized
43
-     */
44
-    private $initialized = false;
45
-
46
-
47
-
48
-    /**
49
-     * EspressoShortcode constructor
50
-     *
51
-     * @param PostRelatedCacheManager $cache_manager
52
-     */
53
-    public function __construct(PostRelatedCacheManager $cache_manager)
54
-    {
55
-        $this->cache_manager = $cache_manager;
56
-    }
57
-
58
-
59
-
60
-    /**
61
-     * @return void
62
-     */
63
-    public function shortcodeHasBeenInitialized()
64
-    {
65
-        $this->initialized = true;
66
-    }
67
-
68
-
69
-
70
-    /**
71
-     * enqueues scripts then processes the shortcode
72
-     *
73
-     * @param array $attributes
74
-     * @return string
75
-     * @throws EE_Error
76
-     */
77
-    final public function processShortcodeCallback($attributes = array())
78
-    {
79
-        if ($this instanceof EnqueueAssetsInterface) {
80
-            if (is_admin()) {
81
-                $this->enqueueAdminScripts();
82
-            } else {
83
-                $this->enqueueScripts();
84
-            }
85
-        }
86
-        return $this->shortcodeContent(
87
-            $this->sanitizeAttributes((array)$attributes)
88
-        );
89
-    }
90
-
91
-
92
-
93
-    /**
94
-     * If shortcode caching is enabled for the shortcode,
95
-     * and cached results exist, then that will be returned
96
-     * else new content will be generated.
97
-     * If caching is enabled, then the new content will be cached for later.
98
-     *
99
-     * @param array $attributes
100
-     * @return mixed|string
101
-     * @throws EE_Error
102
-     */
103
-    private function shortcodeContent(array $attributes)
104
-    {
105
-        $shortcode = $this;
106
-        $post_ID = $this->currentPostID();
107
-        // something like "SC_EVENTS-123"
108
-        $cache_ID = $this->shortcodeCacheID($post_ID);
109
-        $this->cache_manager->clearPostRelatedCacheOnUpdate($post_ID, $cache_ID);
110
-        return $this->cache_manager->get(
111
-            $cache_ID,
112
-            // serialized attributes
113
-            wp_json_encode($attributes),
114
-            // Closure for generating content if cache is expired
115
-            function () use ($shortcode, $attributes) {
116
-                if($shortcode->initialized() === false){
117
-                    $shortcode->initializeShortcode();
118
-                }
119
-                return $shortcode->processShortcode($attributes);
120
-            },
121
-            // filterable cache expiration set by each shortcode
122
-            apply_filters(
123
-                'FHEE__EventEspresso_core_services_shortcodes_EspressoShortcode__shortcodeContent__cache_expiration',
124
-                $this->cacheExpiration(),
125
-                $this->getTag(),
126
-                $this
127
-            )
128
-        );
129
-    }
130
-
131
-
132
-
133
-    /**
134
-     * @return int
135
-     * @throws EE_Error
136
-     */
137
-    private function currentPostID()
138
-    {
139
-        // try to get EE_Event any way we can
140
-        $event = EEH_Event_View::get_event();
141
-        // then get some kind of ID
142
-        if ($event instanceof EE_Event) {
143
-            return $event->ID();
144
-        }
145
-        global $post;
146
-        if ($post instanceof WP_Post) {
147
-            return $post->ID;
148
-        }
149
-        return 0;
150
-    }
151
-
152
-
153
-
154
-    /**
155
-     * @param int $post_ID
156
-     * @return string
157
-     * @throws EE_Error
158
-     */
159
-    private function shortcodeCacheID($post_ID)
160
-    {
161
-        $tag = str_replace('ESPRESSO_', '', $this->getTag());
162
-        return "SC_{$tag}-{$post_ID}";
163
-    }
164
-
165
-
166
-
167
-    /**
168
-     * array for defining custom attribute sanitization callbacks,
169
-     * where keys match keys in your attributes array,
170
-     * and values represent the sanitization function you wish to be applied to that attribute.
171
-     * So for example, if you had an integer attribute named "event_id"
172
-     * that you wanted to be sanitized using absint(),
173
-     * then you would return the following:
174
-     *      array('event_id' => 'absint')
175
-     * Entering 'skip_sanitization' for the callback value
176
-     * means that no sanitization will be applied
177
-     * on the assumption that the attribute
178
-     * will be sanitized at some point... right?
179
-     * You wouldn't pass around unsanitized attributes would you?
180
-     * That would be very Tom Foolery of you!!!
181
-     *
182
-     * @return array
183
-     */
184
-    protected function customAttributeSanitizationMap()
185
-    {
186
-        return array();
187
-    }
188
-
189
-
190
-
191
-    /**
192
-     * Performs basic sanitization on shortcode attributes
193
-     * Since incoming attributes from the shortcode usage in the WP editor will all be strings,
194
-     * most attributes will by default be sanitized using the sanitize_text_field() function.
195
-     * This can be overridden using the customAttributeSanitizationMap() method (see above),
196
-     * all other attributes would be sanitized using the defaults in the switch statement below
197
-     *
198
-     * @param array $attributes
199
-     * @return array
200
-     */
201
-    private function sanitizeAttributes(array $attributes)
202
-    {
203
-        $custom_sanitization = $this->customAttributeSanitizationMap();
204
-        foreach ($attributes as $key => $value) {
205
-            // is a custom sanitization callback specified ?
206
-            if (isset($custom_sanitization[$key])) {
207
-                $callback = $custom_sanitization[$key];
208
-                if ($callback === 'skip_sanitization') {
209
-                    $attributes[$key] = $value;
210
-                    continue;
211
-                }
212
-                if (function_exists($callback)) {
213
-                    $attributes[$key] = $callback($value);
214
-                    continue;
215
-                }
216
-            }
217
-            switch (true) {
218
-                case $value === null :
219
-                case is_int($value) :
220
-                case is_float($value) :
221
-                    // typical booleans
222
-                case in_array($value, array(true, 'true', '1', 'on', 'yes', false, 'false', '0', 'off', 'no'), true) :
223
-                    $attributes[$key] = $value;
224
-                    break;
225
-                case is_string($value) :
226
-                    $attributes[$key] = sanitize_text_field($value);
227
-                    break;
228
-                case is_array($value) :
229
-                    $attributes[$key] = $this->sanitizeAttributes($value);
230
-                    break;
231
-                default :
232
-                    // only remaining data types are Object and Resource
233
-                    // which are not allowed as shortcode attributes
234
-                    $attributes[$key] = null;
235
-                    break;
236
-            }
237
-        }
238
-        return $attributes;
239
-    }
240
-
241
-
242
-
243
-    /**
244
-     * Returns whether or not this shortcode has been initialized
245
-     * @return boolean
246
-     */
247
-    public function initialized(){
248
-        return $this->initialized;
249
-    }
26
+	/**
27
+	 * transient prefix
28
+	 *
29
+	 * @type string
30
+	 */
31
+	const CACHE_TRANSIENT_PREFIX = 'ee_sc_';
32
+
33
+	/**
34
+	 * @var PostRelatedCacheManager $cache_manager
35
+	 */
36
+	private $cache_manager;
37
+
38
+	/**
39
+	 * true if ShortcodeInterface::initializeShortcode() has been called
40
+	 * if false, then that will get called before processing
41
+	 *
42
+	 * @var boolean $initialized
43
+	 */
44
+	private $initialized = false;
45
+
46
+
47
+
48
+	/**
49
+	 * EspressoShortcode constructor
50
+	 *
51
+	 * @param PostRelatedCacheManager $cache_manager
52
+	 */
53
+	public function __construct(PostRelatedCacheManager $cache_manager)
54
+	{
55
+		$this->cache_manager = $cache_manager;
56
+	}
57
+
58
+
59
+
60
+	/**
61
+	 * @return void
62
+	 */
63
+	public function shortcodeHasBeenInitialized()
64
+	{
65
+		$this->initialized = true;
66
+	}
67
+
68
+
69
+
70
+	/**
71
+	 * enqueues scripts then processes the shortcode
72
+	 *
73
+	 * @param array $attributes
74
+	 * @return string
75
+	 * @throws EE_Error
76
+	 */
77
+	final public function processShortcodeCallback($attributes = array())
78
+	{
79
+		if ($this instanceof EnqueueAssetsInterface) {
80
+			if (is_admin()) {
81
+				$this->enqueueAdminScripts();
82
+			} else {
83
+				$this->enqueueScripts();
84
+			}
85
+		}
86
+		return $this->shortcodeContent(
87
+			$this->sanitizeAttributes((array)$attributes)
88
+		);
89
+	}
90
+
91
+
92
+
93
+	/**
94
+	 * If shortcode caching is enabled for the shortcode,
95
+	 * and cached results exist, then that will be returned
96
+	 * else new content will be generated.
97
+	 * If caching is enabled, then the new content will be cached for later.
98
+	 *
99
+	 * @param array $attributes
100
+	 * @return mixed|string
101
+	 * @throws EE_Error
102
+	 */
103
+	private function shortcodeContent(array $attributes)
104
+	{
105
+		$shortcode = $this;
106
+		$post_ID = $this->currentPostID();
107
+		// something like "SC_EVENTS-123"
108
+		$cache_ID = $this->shortcodeCacheID($post_ID);
109
+		$this->cache_manager->clearPostRelatedCacheOnUpdate($post_ID, $cache_ID);
110
+		return $this->cache_manager->get(
111
+			$cache_ID,
112
+			// serialized attributes
113
+			wp_json_encode($attributes),
114
+			// Closure for generating content if cache is expired
115
+			function () use ($shortcode, $attributes) {
116
+				if($shortcode->initialized() === false){
117
+					$shortcode->initializeShortcode();
118
+				}
119
+				return $shortcode->processShortcode($attributes);
120
+			},
121
+			// filterable cache expiration set by each shortcode
122
+			apply_filters(
123
+				'FHEE__EventEspresso_core_services_shortcodes_EspressoShortcode__shortcodeContent__cache_expiration',
124
+				$this->cacheExpiration(),
125
+				$this->getTag(),
126
+				$this
127
+			)
128
+		);
129
+	}
130
+
131
+
132
+
133
+	/**
134
+	 * @return int
135
+	 * @throws EE_Error
136
+	 */
137
+	private function currentPostID()
138
+	{
139
+		// try to get EE_Event any way we can
140
+		$event = EEH_Event_View::get_event();
141
+		// then get some kind of ID
142
+		if ($event instanceof EE_Event) {
143
+			return $event->ID();
144
+		}
145
+		global $post;
146
+		if ($post instanceof WP_Post) {
147
+			return $post->ID;
148
+		}
149
+		return 0;
150
+	}
151
+
152
+
153
+
154
+	/**
155
+	 * @param int $post_ID
156
+	 * @return string
157
+	 * @throws EE_Error
158
+	 */
159
+	private function shortcodeCacheID($post_ID)
160
+	{
161
+		$tag = str_replace('ESPRESSO_', '', $this->getTag());
162
+		return "SC_{$tag}-{$post_ID}";
163
+	}
164
+
165
+
166
+
167
+	/**
168
+	 * array for defining custom attribute sanitization callbacks,
169
+	 * where keys match keys in your attributes array,
170
+	 * and values represent the sanitization function you wish to be applied to that attribute.
171
+	 * So for example, if you had an integer attribute named "event_id"
172
+	 * that you wanted to be sanitized using absint(),
173
+	 * then you would return the following:
174
+	 *      array('event_id' => 'absint')
175
+	 * Entering 'skip_sanitization' for the callback value
176
+	 * means that no sanitization will be applied
177
+	 * on the assumption that the attribute
178
+	 * will be sanitized at some point... right?
179
+	 * You wouldn't pass around unsanitized attributes would you?
180
+	 * That would be very Tom Foolery of you!!!
181
+	 *
182
+	 * @return array
183
+	 */
184
+	protected function customAttributeSanitizationMap()
185
+	{
186
+		return array();
187
+	}
188
+
189
+
190
+
191
+	/**
192
+	 * Performs basic sanitization on shortcode attributes
193
+	 * Since incoming attributes from the shortcode usage in the WP editor will all be strings,
194
+	 * most attributes will by default be sanitized using the sanitize_text_field() function.
195
+	 * This can be overridden using the customAttributeSanitizationMap() method (see above),
196
+	 * all other attributes would be sanitized using the defaults in the switch statement below
197
+	 *
198
+	 * @param array $attributes
199
+	 * @return array
200
+	 */
201
+	private function sanitizeAttributes(array $attributes)
202
+	{
203
+		$custom_sanitization = $this->customAttributeSanitizationMap();
204
+		foreach ($attributes as $key => $value) {
205
+			// is a custom sanitization callback specified ?
206
+			if (isset($custom_sanitization[$key])) {
207
+				$callback = $custom_sanitization[$key];
208
+				if ($callback === 'skip_sanitization') {
209
+					$attributes[$key] = $value;
210
+					continue;
211
+				}
212
+				if (function_exists($callback)) {
213
+					$attributes[$key] = $callback($value);
214
+					continue;
215
+				}
216
+			}
217
+			switch (true) {
218
+				case $value === null :
219
+				case is_int($value) :
220
+				case is_float($value) :
221
+					// typical booleans
222
+				case in_array($value, array(true, 'true', '1', 'on', 'yes', false, 'false', '0', 'off', 'no'), true) :
223
+					$attributes[$key] = $value;
224
+					break;
225
+				case is_string($value) :
226
+					$attributes[$key] = sanitize_text_field($value);
227
+					break;
228
+				case is_array($value) :
229
+					$attributes[$key] = $this->sanitizeAttributes($value);
230
+					break;
231
+				default :
232
+					// only remaining data types are Object and Resource
233
+					// which are not allowed as shortcode attributes
234
+					$attributes[$key] = null;
235
+					break;
236
+			}
237
+		}
238
+		return $attributes;
239
+	}
240
+
241
+
242
+
243
+	/**
244
+	 * Returns whether or not this shortcode has been initialized
245
+	 * @return boolean
246
+	 */
247
+	public function initialized(){
248
+		return $this->initialized;
249
+	}
250 250
 
251 251
 
252 252
 
Please login to merge, or discard this patch.
core/services/shortcodes/ShortcodeInterface.php 1 patch
Indentation   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -8,52 +8,52 @@
 block discarded – undo
8 8
 interface ShortcodeInterface
9 9
 {
10 10
 
11
-    /**
12
-     * the actual shortcode tag that gets registered with WordPress
13
-     *
14
-     * @return string
15
-     */
16
-    public function getTag();
17
-
18
-    /**
19
-     * the length of time in seconds to cache the results of the processShortcode() method
20
-     * 0 means the processShortcode() results will NOT be cached at all
21
-     *
22
-     * @return int
23
-     */
24
-    public function cacheExpiration();
25
-
26
-    /**
27
-     * a place for adding any initialization code that needs to run prior to wp_header().
28
-     * this may be required for shortcodes that utilize a corresponding module,
29
-     * and need to enqueue assets for that module
30
-     *
31
-     * !!! IMPORTANT !!!
32
-     * After performing any logic within this method required for initialization
33
-     *         $this->shortcodeHasBeenInitialized();
34
-     * should be called to ensure that the shortcode is setup correctly.
35
-     *
36
-     * @return void
37
-     */
38
-    public function initializeShortcode();
39
-
40
-    /**
41
-     * callback that runs when the shortcode is encountered in post content.
42
-     * IMPORTANT !!!
43
-     * remember that shortcode content should be RETURNED and NOT echoed out
44
-     *
45
-     * @param array $attributes
46
-     * @return string
47
-     */
48
-    public function processShortcode($attributes = array());
49
-
50
-
51
-
52
-    /**
53
-     * Returns whether or not this shortcode class has already been initialized
54
-     * @return boolean
55
-     */
56
-    public function initialized();
11
+	/**
12
+	 * the actual shortcode tag that gets registered with WordPress
13
+	 *
14
+	 * @return string
15
+	 */
16
+	public function getTag();
17
+
18
+	/**
19
+	 * the length of time in seconds to cache the results of the processShortcode() method
20
+	 * 0 means the processShortcode() results will NOT be cached at all
21
+	 *
22
+	 * @return int
23
+	 */
24
+	public function cacheExpiration();
25
+
26
+	/**
27
+	 * a place for adding any initialization code that needs to run prior to wp_header().
28
+	 * this may be required for shortcodes that utilize a corresponding module,
29
+	 * and need to enqueue assets for that module
30
+	 *
31
+	 * !!! IMPORTANT !!!
32
+	 * After performing any logic within this method required for initialization
33
+	 *         $this->shortcodeHasBeenInitialized();
34
+	 * should be called to ensure that the shortcode is setup correctly.
35
+	 *
36
+	 * @return void
37
+	 */
38
+	public function initializeShortcode();
39
+
40
+	/**
41
+	 * callback that runs when the shortcode is encountered in post content.
42
+	 * IMPORTANT !!!
43
+	 * remember that shortcode content should be RETURNED and NOT echoed out
44
+	 *
45
+	 * @param array $attributes
46
+	 * @return string
47
+	 */
48
+	public function processShortcode($attributes = array());
49
+
50
+
51
+
52
+	/**
53
+	 * Returns whether or not this shortcode class has already been initialized
54
+	 * @return boolean
55
+	 */
56
+	public function initialized();
57 57
 
58 58
 }
59 59
 // End of file ShortcodeInterface.php
Please login to merge, or discard this patch.
caffeinated/admin/extend/events/Extend_Events_Admin_Page.core.php 1 patch
Indentation   +1260 added lines, -1260 removed lines patch added patch discarded remove patch
@@ -14,1264 +14,1264 @@
 block discarded – undo
14 14
 {
15 15
 
16 16
 
17
-    /**
18
-     * Extend_Events_Admin_Page constructor.
19
-     *
20
-     * @param bool $routing
21
-     */
22
-    public function __construct($routing = true)
23
-    {
24
-        parent::__construct($routing);
25
-        if (! defined('EVENTS_CAF_TEMPLATE_PATH')) {
26
-            define('EVENTS_CAF_TEMPLATE_PATH', EE_CORE_CAF_ADMIN_EXTEND . 'events/templates/');
27
-            define('EVENTS_CAF_ASSETS', EE_CORE_CAF_ADMIN_EXTEND . 'events/assets/');
28
-            define('EVENTS_CAF_ASSETS_URL', EE_CORE_CAF_ADMIN_EXTEND_URL . 'events/assets/');
29
-        }
30
-    }
31
-
32
-
33
-    /**
34
-     * Sets routes.
35
-     */
36
-    protected function _extend_page_config()
37
-    {
38
-        $this->_admin_base_path = EE_CORE_CAF_ADMIN_EXTEND . 'events';
39
-        //is there a evt_id in the request?
40
-        $evt_id = ! empty($this->_req_data['EVT_ID']) && ! is_array($this->_req_data['EVT_ID'])
41
-            ? $this->_req_data['EVT_ID']
42
-            : 0;
43
-        $evt_id = ! empty($this->_req_data['post']) ? $this->_req_data['post'] : $evt_id;
44
-        //tkt_id?
45
-        $tkt_id             = ! empty($this->_req_data['TKT_ID']) && ! is_array($this->_req_data['TKT_ID'])
46
-            ? $this->_req_data['TKT_ID']
47
-            : 0;
48
-        $new_page_routes    = array(
49
-            'duplicate_event'          => array(
50
-                'func'       => '_duplicate_event',
51
-                'capability' => 'ee_edit_event',
52
-                'obj_id'     => $evt_id,
53
-                'noheader'   => true,
54
-            ),
55
-            'ticket_list_table'        => array(
56
-                'func'       => '_tickets_overview_list_table',
57
-                'capability' => 'ee_read_default_tickets',
58
-            ),
59
-            'trash_ticket'             => array(
60
-                'func'       => '_trash_or_restore_ticket',
61
-                'capability' => 'ee_delete_default_ticket',
62
-                'obj_id'     => $tkt_id,
63
-                'noheader'   => true,
64
-                'args'       => array('trash' => true),
65
-            ),
66
-            'trash_tickets'            => array(
67
-                'func'       => '_trash_or_restore_ticket',
68
-                'capability' => 'ee_delete_default_tickets',
69
-                'noheader'   => true,
70
-                'args'       => array('trash' => true),
71
-            ),
72
-            'restore_ticket'           => array(
73
-                'func'       => '_trash_or_restore_ticket',
74
-                'capability' => 'ee_delete_default_ticket',
75
-                'obj_id'     => $tkt_id,
76
-                'noheader'   => true,
77
-            ),
78
-            'restore_tickets'          => array(
79
-                'func'       => '_trash_or_restore_ticket',
80
-                'capability' => 'ee_delete_default_tickets',
81
-                'noheader'   => true,
82
-            ),
83
-            'delete_ticket'            => array(
84
-                'func'       => '_delete_ticket',
85
-                'capability' => 'ee_delete_default_ticket',
86
-                'obj_id'     => $tkt_id,
87
-                'noheader'   => true,
88
-            ),
89
-            'delete_tickets'           => array(
90
-                'func'       => '_delete_ticket',
91
-                'capability' => 'ee_delete_default_tickets',
92
-                'noheader'   => true,
93
-            ),
94
-            'import_page'              => array(
95
-                'func'       => '_import_page',
96
-                'capability' => 'import',
97
-            ),
98
-            'import'                   => array(
99
-                'func'       => '_import_events',
100
-                'capability' => 'import',
101
-                'noheader'   => true,
102
-            ),
103
-            'import_events'            => array(
104
-                'func'       => '_import_events',
105
-                'capability' => 'import',
106
-                'noheader'   => true,
107
-            ),
108
-            'export_events'            => array(
109
-                'func'       => '_events_export',
110
-                'capability' => 'export',
111
-                'noheader'   => true,
112
-            ),
113
-            'export_categories'        => array(
114
-                'func'       => '_categories_export',
115
-                'capability' => 'export',
116
-                'noheader'   => true,
117
-            ),
118
-            'sample_export_file'       => array(
119
-                'func'       => '_sample_export_file',
120
-                'capability' => 'export',
121
-                'noheader'   => true,
122
-            ),
123
-            'update_template_settings' => array(
124
-                'func'       => '_update_template_settings',
125
-                'capability' => 'manage_options',
126
-                'noheader'   => true,
127
-            ),
128
-        );
129
-        $this->_page_routes = array_merge($this->_page_routes, $new_page_routes);
130
-        //partial route/config override
131
-        $this->_page_config['import_events']['metaboxes'] = $this->_default_espresso_metaboxes;
132
-        $this->_page_config['create_new']['metaboxes'][]  = '_premium_event_editor_meta_boxes';
133
-        $this->_page_config['create_new']['qtips'][]      = 'EE_Event_Editor_Tips';
134
-        $this->_page_config['edit']['qtips'][]            = 'EE_Event_Editor_Tips';
135
-        $this->_page_config['edit']['metaboxes'][]        = '_premium_event_editor_meta_boxes';
136
-        $this->_page_config['default']['list_table']      = 'Extend_Events_Admin_List_Table';
137
-        //add tickets tab but only if there are more than one default ticket!
138
-        $tkt_count = EEM_Ticket::instance()->count_deleted_and_undeleted(
139
-            array(array('TKT_is_default' => 1)),
140
-            'TKT_ID',
141
-            true
142
-        );
143
-        if ($tkt_count > 1) {
144
-            $new_page_config = array(
145
-                'ticket_list_table' => array(
146
-                    'nav'           => array(
147
-                        'label' => esc_html__('Default Tickets', 'event_espresso'),
148
-                        'order' => 60,
149
-                    ),
150
-                    'list_table'    => 'Tickets_List_Table',
151
-                    'require_nonce' => false,
152
-                ),
153
-            );
154
-        }
155
-        //template settings
156
-        $new_page_config['template_settings'] = array(
157
-            'nav'           => array(
158
-                'label' => esc_html__('Templates', 'event_espresso'),
159
-                'order' => 30,
160
-            ),
161
-            'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
162
-            'help_tabs'     => array(
163
-                'general_settings_templates_help_tab' => array(
164
-                    'title'    => esc_html__('Templates', 'event_espresso'),
165
-                    'filename' => 'general_settings_templates',
166
-                ),
167
-            ),
168
-            'help_tour'     => array('Templates_Help_Tour'),
169
-            'require_nonce' => false,
170
-        );
171
-        $this->_page_config                   = array_merge($this->_page_config, $new_page_config);
172
-        //add filters and actions
173
-        //modifying _views
174
-        add_filter(
175
-            'FHEE_event_datetime_metabox_add_additional_date_time_template',
176
-            array($this, 'add_additional_datetime_button'),
177
-            10,
178
-            2
179
-        );
180
-        add_filter(
181
-            'FHEE_event_datetime_metabox_clone_button_template',
182
-            array($this, 'add_datetime_clone_button'),
183
-            10,
184
-            2
185
-        );
186
-        add_filter(
187
-            'FHEE_event_datetime_metabox_timezones_template',
188
-            array($this, 'datetime_timezones_template'),
189
-            10,
190
-            2
191
-        );
192
-        //filters for event list table
193
-        add_filter('FHEE__Extend_Events_Admin_List_Table__filters', array($this, 'list_table_filters'), 10, 2);
194
-        add_filter(
195
-            'FHEE__Events_Admin_List_Table__column_actions__action_links',
196
-            array($this, 'extra_list_table_actions'),
197
-            10,
198
-            2
199
-        );
200
-        //legend item
201
-        add_filter('FHEE__Events_Admin_Page___event_legend_items__items', array($this, 'additional_legend_items'));
202
-        add_action('admin_init', array($this, 'admin_init'));
203
-        //heartbeat stuff
204
-        add_filter('heartbeat_received', array($this, 'heartbeat_response'), 10, 2);
205
-    }
206
-
207
-
208
-    /**
209
-     * admin_init
210
-     */
211
-    public function admin_init()
212
-    {
213
-        EE_Registry::$i18n_js_strings = array_merge(
214
-            EE_Registry::$i18n_js_strings,
215
-            array(
216
-                'image_confirm'          => esc_html__(
217
-                    'Do you really want to delete this image? Please remember to update your event to complete the removal.',
218
-                    'event_espresso'
219
-                ),
220
-                'event_starts_on'        => esc_html__('Event Starts on', 'event_espresso'),
221
-                'event_ends_on'          => esc_html__('Event Ends on', 'event_espresso'),
222
-                'event_datetime_actions' => esc_html__('Actions', 'event_espresso'),
223
-                'event_clone_dt_msg'     => esc_html__('Clone this Event Date and Time', 'event_espresso'),
224
-                'remove_event_dt_msg'    => esc_html__('Remove this Event Time', 'event_espresso'),
225
-            )
226
-        );
227
-    }
228
-
229
-
230
-    /**
231
-     * This will be used to listen for any heartbeat data packages coming via the WordPress heartbeat API and handle
232
-     * accordingly.
233
-     *
234
-     * @param array $response The existing heartbeat response array.
235
-     * @param array $data     The incoming data package.
236
-     * @return array  possibly appended response.
237
-     */
238
-    public function heartbeat_response($response, $data)
239
-    {
240
-        /**
241
-         * check whether count of tickets is approaching the potential
242
-         * limits for the server.
243
-         */
244
-        if (! empty($data['input_count'])) {
245
-            $response['max_input_vars_check'] = EE_Registry::instance()->CFG->environment->max_input_vars_limit_check(
246
-                $data['input_count']
247
-            );
248
-        }
249
-        return $response;
250
-    }
251
-
252
-
253
-    /**
254
-     * Add per page screen options to the default ticket list table view.
255
-     */
256
-    protected function _add_screen_options_ticket_list_table()
257
-    {
258
-        $this->_per_page_screen_option();
259
-    }
260
-
261
-
262
-    /**
263
-     * @param string $return
264
-     * @param int    $id
265
-     * @param string $new_title
266
-     * @param string $new_slug
267
-     * @return string
268
-     */
269
-    public function extra_permalink_field_buttons($return, $id, $new_title, $new_slug)
270
-    {
271
-        $return = parent::extra_permalink_field_buttons($return, $id, $new_title, $new_slug);
272
-        //make sure this is only when editing
273
-        if (! empty($id)) {
274
-            $href   = EE_Admin_Page::add_query_args_and_nonce(
275
-                array('action' => 'duplicate_event', 'EVT_ID' => $id),
276
-                $this->_admin_base_url
277
-            );
278
-            $title  = esc_attr__('Duplicate Event', 'event_espresso');
279
-            $return .= '<a href="'
280
-                       . $href
281
-                       . '" title="'
282
-                       . $title
283
-                       . '" id="ee-duplicate-event-button" class="button button-small"  value="duplicate_event">'
284
-                       . $title
285
-                       . '</button>';
286
-        }
287
-        return $return;
288
-    }
289
-
290
-
291
-    /**
292
-     * Set the list table views for the default ticket list table view.
293
-     */
294
-    public function _set_list_table_views_ticket_list_table()
295
-    {
296
-        $this->_views = array(
297
-            'all'     => array(
298
-                'slug'        => 'all',
299
-                'label'       => esc_html__('All', 'event_espresso'),
300
-                'count'       => 0,
301
-                'bulk_action' => array(
302
-                    'trash_tickets' => esc_html__('Move to Trash', 'event_espresso'),
303
-                ),
304
-            ),
305
-            'trashed' => array(
306
-                'slug'        => 'trashed',
307
-                'label'       => esc_html__('Trash', 'event_espresso'),
308
-                'count'       => 0,
309
-                'bulk_action' => array(
310
-                    'restore_tickets' => esc_html__('Restore from Trash', 'event_espresso'),
311
-                    'delete_tickets'  => esc_html__('Delete Permanently', 'event_espresso'),
312
-                ),
313
-            ),
314
-        );
315
-    }
316
-
317
-
318
-    /**
319
-     * Enqueue scripts and styles for the event editor.
320
-     */
321
-    public function load_scripts_styles_edit()
322
-    {
323
-        wp_register_script(
324
-            'ee-event-editor-heartbeat',
325
-            EVENTS_CAF_ASSETS_URL . 'event-editor-heartbeat.js',
326
-            array('ee_admin_js', 'heartbeat'),
327
-            EVENT_ESPRESSO_VERSION,
328
-            true
329
-        );
330
-        wp_enqueue_script('ee-accounting');
331
-        //styles
332
-        wp_enqueue_style('espresso-ui-theme');
333
-        wp_enqueue_script('event_editor_js');
334
-        wp_enqueue_script('ee-event-editor-heartbeat');
335
-    }
336
-
337
-
338
-    /**
339
-     * Returns template for the additional datetime.
340
-     * @param $template
341
-     * @param $template_args
342
-     * @return mixed
343
-     * @throws DomainException
344
-     */
345
-    public function add_additional_datetime_button($template, $template_args)
346
-    {
347
-        return EEH_Template::display_template(
348
-            EVENTS_CAF_TEMPLATE_PATH . 'event_datetime_add_additional_time.template.php',
349
-            $template_args,
350
-            true
351
-        );
352
-    }
353
-
354
-
355
-    /**
356
-     * Returns the template for cloning a datetime.
357
-     * @param $template
358
-     * @param $template_args
359
-     * @return mixed
360
-     * @throws DomainException
361
-     */
362
-    public function add_datetime_clone_button($template, $template_args)
363
-    {
364
-        return EEH_Template::display_template(
365
-            EVENTS_CAF_TEMPLATE_PATH . 'event_datetime_metabox_clone_button.template.php',
366
-            $template_args,
367
-            true
368
-        );
369
-    }
370
-
371
-
372
-    /**
373
-     * Returns the template for datetime timezones.
374
-     * @param $template
375
-     * @param $template_args
376
-     * @return mixed
377
-     * @throws DomainException
378
-     */
379
-    public function datetime_timezones_template($template, $template_args)
380
-    {
381
-        return EEH_Template::display_template(
382
-            EVENTS_CAF_TEMPLATE_PATH . 'event_datetime_timezones.template.php',
383
-            $template_args,
384
-            true
385
-        );
386
-    }
387
-
388
-
389
-    /**
390
-     * Sets the views for the default list table view.
391
-     */
392
-    protected function _set_list_table_views_default()
393
-    {
394
-        parent::_set_list_table_views_default();
395
-        $new_views    = array(
396
-            'today' => array(
397
-                'slug'        => 'today',
398
-                'label'       => esc_html__('Today', 'event_espresso'),
399
-                'count'       => $this->total_events_today(),
400
-                'bulk_action' => array(
401
-                    'trash_events' => esc_html__('Move to Trash', 'event_espresso'),
402
-                ),
403
-            ),
404
-            'month' => array(
405
-                'slug'        => 'month',
406
-                'label'       => esc_html__('This Month', 'event_espresso'),
407
-                'count'       => $this->total_events_this_month(),
408
-                'bulk_action' => array(
409
-                    'trash_events' => esc_html__('Move to Trash', 'event_espresso'),
410
-                ),
411
-            ),
412
-        );
413
-        $this->_views = array_merge($this->_views, $new_views);
414
-    }
415
-
416
-
417
-    /**
418
-     * Returns the extra action links for the default list table view.
419
-     * @param array     $action_links
420
-     * @param \EE_Event $event
421
-     * @return array
422
-     * @throws EE_Error
423
-     */
424
-    public function extra_list_table_actions(array $action_links, \EE_Event $event)
425
-    {
426
-        if (EE_Registry::instance()->CAP->current_user_can(
427
-            'ee_read_registrations',
428
-            'espresso_registrations_reports',
429
-            $event->ID()
430
-        )
431
-        ) {
432
-            $reports_query_args = array(
433
-                'action' => 'reports',
434
-                'EVT_ID' => $event->ID(),
435
-            );
436
-            $reports_link       = EE_Admin_Page::add_query_args_and_nonce($reports_query_args, REG_ADMIN_URL);
437
-            $action_links[]     = '<a href="'
438
-                                  . $reports_link
439
-                                  . '" title="'
440
-                                  . esc_attr__('View Report', 'event_espresso')
441
-                                  . '"><div class="dashicons dashicons-chart-bar"></div></a>'
442
-                                  . "\n\t";
443
-        }
444
-        if (EE_Registry::instance()->CAP->current_user_can('ee_read_global_messages', 'view_filtered_messages')) {
445
-            EE_Registry::instance()->load_helper('MSG_Template');
446
-            $action_links[] = EEH_MSG_Template::get_message_action_link(
447
-                'see_notifications_for',
448
-                null,
449
-                array('EVT_ID' => $event->ID())
450
-            );
451
-        }
452
-        return $action_links;
453
-    }
454
-
455
-
456
-    /**
457
-     * @param $items
458
-     * @return mixed
459
-     */
460
-    public function additional_legend_items($items)
461
-    {
462
-        if (EE_Registry::instance()->CAP->current_user_can(
463
-            'ee_read_registrations',
464
-            'espresso_registrations_reports'
465
-        )
466
-        ) {
467
-            $items['reports'] = array(
468
-                'class' => 'dashicons dashicons-chart-bar',
469
-                'desc'  => esc_html__('Event Reports', 'event_espresso'),
470
-            );
471
-        }
472
-        if (EE_Registry::instance()->CAP->current_user_can('ee_read_global_messages', 'view_filtered_messages')) {
473
-            $related_for_icon = EEH_MSG_Template::get_message_action_icon('see_notifications_for');
474
-            if (isset($related_for_icon['css_class']) && isset($related_for_icon['label'])) {
475
-                $items['view_related_messages'] = array(
476
-                    'class' => $related_for_icon['css_class'],
477
-                    'desc'  => $related_for_icon['label'],
478
-                );
479
-            }
480
-        }
481
-        return $items;
482
-    }
483
-
484
-
485
-    /**
486
-     * This is the callback method for the duplicate event route
487
-     * Method looks for 'EVT_ID' in the request and retrieves that event and its details and duplicates them
488
-     * into a new event.  We add a hook so that any plugins that add extra event details can hook into this
489
-     * action.  Note that the dupe will have **DUPLICATE** as its title and slug.
490
-     * After duplication the redirect is to the new event edit page.
491
-     *
492
-     * @return void
493
-     * @access protected
494
-     * @throws EE_Error If EE_Event is not available with given ID
495
-     */
496
-    protected function _duplicate_event()
497
-    {
498
-        // first make sure the ID for the event is in the request.
499
-        //  If it isn't then we need to bail and redirect back to overview list table (cause how did we get here?)
500
-        if (! isset($this->_req_data['EVT_ID'])) {
501
-            EE_Error::add_error(
502
-                esc_html__(
503
-                    'In order to duplicate an event an Event ID is required.  None was given.',
504
-                    'event_espresso'
505
-                ),
506
-                __FILE__,
507
-                __FUNCTION__,
508
-                __LINE__
509
-            );
510
-            $this->_redirect_after_action(false, '', '', array(), true);
511
-            return;
512
-        }
513
-        //k we've got EVT_ID so let's use that to get the event we'll duplicate
514
-        $orig_event = EEM_Event::instance()->get_one_by_ID($this->_req_data['EVT_ID']);
515
-        if (! $orig_event instanceof EE_Event) {
516
-            throw new EE_Error(
517
-                sprintf(
518
-                    esc_html__('An EE_Event object could not be retrieved for the given ID (%s)', 'event_espresso'),
519
-                    $this->_req_data['EVT_ID']
520
-                )
521
-            );
522
-        }
523
-        //k now let's clone the $orig_event before getting relations
524
-        $new_event = clone $orig_event;
525
-        //original datetimes
526
-        $orig_datetimes = $orig_event->get_many_related('Datetime');
527
-        //other original relations
528
-        $orig_ven = $orig_event->get_many_related('Venue');
529
-        //reset the ID and modify other details to make it clear this is a dupe
530
-        $new_event->set('EVT_ID', 0);
531
-        $new_name = $new_event->name() . ' ' . esc_html__('**DUPLICATE**', 'event_espresso');
532
-        $new_event->set('EVT_name', $new_name);
533
-        $new_event->set(
534
-            'EVT_slug',
535
-            wp_unique_post_slug(
536
-                sanitize_title($orig_event->name()),
537
-                0,
538
-                'publish',
539
-                'espresso_events',
540
-                0
541
-            )
542
-        );
543
-        $new_event->set('status', 'draft');
544
-        //duplicate discussion settings
545
-        $new_event->set('comment_status', $orig_event->get('comment_status'));
546
-        $new_event->set('ping_status', $orig_event->get('ping_status'));
547
-        //save the new event
548
-        $new_event->save();
549
-        //venues
550
-        foreach ($orig_ven as $ven) {
551
-            $new_event->_add_relation_to($ven, 'Venue');
552
-        }
553
-        $new_event->save();
554
-        //now we need to get the question group relations and handle that
555
-        //first primary question groups
556
-        $orig_primary_qgs = $orig_event->get_many_related(
557
-            'Question_Group',
558
-            array(array('Event_Question_Group.EQG_primary' => 1))
559
-        );
560
-        if (! empty($orig_primary_qgs)) {
561
-            foreach ($orig_primary_qgs as $id => $obj) {
562
-                if ($obj instanceof EE_Question_Group) {
563
-                    $new_event->_add_relation_to($obj, 'Question_Group', array('EQG_primary' => 1));
564
-                }
565
-            }
566
-        }
567
-        //next additional attendee question groups
568
-        $orig_additional_qgs = $orig_event->get_many_related(
569
-            'Question_Group',
570
-            array(array('Event_Question_Group.EQG_primary' => 0))
571
-        );
572
-        if (! empty($orig_additional_qgs)) {
573
-            foreach ($orig_additional_qgs as $id => $obj) {
574
-                if ($obj instanceof EE_Question_Group) {
575
-                    $new_event->_add_relation_to($obj, 'Question_Group', array('EQG_primary' => 0));
576
-                }
577
-            }
578
-        }
579
-
580
-        $new_event->save();
581
-
582
-        //k now that we have the new event saved we can loop through the datetimes and start adding relations.
583
-        $cloned_tickets = array();
584
-        foreach ($orig_datetimes as $orig_dtt) {
585
-            if (! $orig_dtt instanceof EE_Datetime) {
586
-                continue;
587
-            }
588
-            $new_dtt   = clone $orig_dtt;
589
-            $orig_tkts = $orig_dtt->tickets();
590
-            //save new dtt then add to event
591
-            $new_dtt->set('DTT_ID', 0);
592
-            $new_dtt->set('DTT_sold', 0);
593
-            $new_dtt->set_reserved(0);
594
-            $new_dtt->save();
595
-            $new_event->_add_relation_to($new_dtt, 'Datetime');
596
-            $new_event->save();
597
-            //now let's get the ticket relations setup.
598
-            foreach ((array)$orig_tkts as $orig_tkt) {
599
-                //it's possible a datetime will have no tickets so let's verify we HAVE a ticket first.
600
-                if (! $orig_tkt instanceof EE_Ticket) {
601
-                    continue;
602
-                }
603
-                //is this ticket archived?  If it is then let's skip
604
-                if ($orig_tkt->get('TKT_deleted')) {
605
-                    continue;
606
-                }
607
-                // does this original ticket already exist in the clone_tickets cache?
608
-                //  If so we'll just use the new ticket from it.
609
-                if (isset($cloned_tickets[$orig_tkt->ID()])) {
610
-                    $new_tkt = $cloned_tickets[$orig_tkt->ID()];
611
-                } else {
612
-                    $new_tkt = clone $orig_tkt;
613
-                    //get relations on the $orig_tkt that we need to setup.
614
-                    $orig_prices = $orig_tkt->prices();
615
-                    $new_tkt->set('TKT_ID', 0);
616
-                    $new_tkt->set('TKT_sold', 0);
617
-                    $new_tkt->set('TKT_reserved', 0);
618
-                    $new_tkt->save(); //make sure new ticket has ID.
619
-                    //price relations on new ticket need to be setup.
620
-                    foreach ($orig_prices as $orig_price) {
621
-                        $new_price = clone $orig_price;
622
-                        $new_price->set('PRC_ID', 0);
623
-                        $new_price->save();
624
-                        $new_tkt->_add_relation_to($new_price, 'Price');
625
-                        $new_tkt->save();
626
-                    }
627
-
628
-                    do_action(
629
-                        'AHEE__Extend_Events_Admin_Page___duplicate_event__duplicate_ticket__after',
630
-                        $orig_tkt,
631
-                        $new_tkt,
632
-                        $orig_prices,
633
-                        $orig_event,
634
-                        $orig_dtt,
635
-                        $new_dtt
636
-                    );
637
-                }
638
-                // k now we can add the new ticket as a relation to the new datetime
639
-                // and make sure its added to our cached $cloned_tickets array
640
-                // for use with later datetimes that have the same ticket.
641
-                $new_dtt->_add_relation_to($new_tkt, 'Ticket');
642
-                $new_dtt->save();
643
-                $cloned_tickets[$orig_tkt->ID()] = $new_tkt;
644
-            }
645
-        }
646
-        //clone taxonomy information
647
-        $taxonomies_to_clone_with = apply_filters(
648
-            'FHEE__Extend_Events_Admin_Page___duplicate_event__taxonomies_to_clone',
649
-            array('espresso_event_categories', 'espresso_event_type', 'post_tag')
650
-        );
651
-        //get terms for original event (notice)
652
-        $orig_terms = wp_get_object_terms($orig_event->ID(), $taxonomies_to_clone_with);
653
-        //loop through terms and add them to new event.
654
-        foreach ($orig_terms as $term) {
655
-            wp_set_object_terms($new_event->ID(), $term->term_id, $term->taxonomy, true);
656
-        }
657
-
658
-        //duplicate other core WP_Post items for this event.
659
-        //post thumbnail (feature image).
660
-        $feature_image_id = get_post_thumbnail_id($orig_event->ID());
661
-        if ($feature_image_id) {
662
-            update_post_meta($new_event->ID(), '_thumbnail_id', $feature_image_id);
663
-        }
664
-
665
-        //duplicate page_template setting
666
-        $page_template = get_post_meta($orig_event->ID(), '_wp_page_template', true);
667
-        if ($page_template) {
668
-            update_post_meta($new_event->ID(), '_wp_page_template', $page_template);
669
-        }
670
-
671
-        do_action('AHEE__Extend_Events_Admin_Page___duplicate_event__after', $new_event, $orig_event);
672
-        //now let's redirect to the edit page for this duplicated event if we have a new event id.
673
-        if ($new_event->ID()) {
674
-            $redirect_args = array(
675
-                'post'   => $new_event->ID(),
676
-                'action' => 'edit',
677
-            );
678
-            EE_Error::add_success(
679
-                esc_html__(
680
-                    'Event successfully duplicated.  Please review the details below and make any necessary edits',
681
-                    'event_espresso'
682
-                )
683
-            );
684
-        } else {
685
-            $redirect_args = array(
686
-                'action' => 'default',
687
-            );
688
-            EE_Error::add_error(
689
-                esc_html__('Not able to duplicate event.  Something went wrong.', 'event_espresso'),
690
-                __FILE__,
691
-                __FUNCTION__,
692
-                __LINE__
693
-            );
694
-        }
695
-        $this->_redirect_after_action(false, '', '', $redirect_args, true);
696
-    }
697
-
698
-
699
-    /**
700
-     * Generates output for the import page.
701
-     * @throws DomainException
702
-     */
703
-    protected function _import_page()
704
-    {
705
-        $title                                      = esc_html__('Import', 'event_espresso');
706
-        $intro                                      = esc_html__(
707
-            'If you have a previously exported Event Espresso 4 information in a Comma Separated Value (CSV) file format, you can upload the file here: ',
708
-            'event_espresso'
709
-        );
710
-        $form_url                                   = EVENTS_ADMIN_URL;
711
-        $action                                     = 'import_events';
712
-        $type                                       = 'csv';
713
-        $this->_template_args['form']               = EE_Import::instance()->upload_form(
714
-            $title, $intro, $form_url, $action, $type
715
-        );
716
-        $this->_template_args['sample_file_link']   = EE_Admin_Page::add_query_args_and_nonce(
717
-            array('action' => 'sample_export_file'),
718
-            $this->_admin_base_url
719
-        );
720
-        $content                                    = EEH_Template::display_template(
721
-            EVENTS_CAF_TEMPLATE_PATH . 'import_page.template.php',
722
-            $this->_template_args,
723
-            true
724
-        );
725
-        $this->_template_args['admin_page_content'] = $content;
726
-        $this->display_admin_page_with_sidebar();
727
-    }
728
-
729
-
730
-    /**
731
-     * _import_events
732
-     * This handles displaying the screen and running imports for importing events.
733
-     *
734
-     * @return void
735
-     */
736
-    protected function _import_events()
737
-    {
738
-        require_once(EE_CLASSES . 'EE_Import.class.php');
739
-        $success = EE_Import::instance()->import();
740
-        $this->_redirect_after_action($success, 'Import File', 'ran', array('action' => 'import_page'), true);
741
-    }
742
-
743
-
744
-    /**
745
-     * _events_export
746
-     * Will export all (or just the given event) to a Excel compatible file.
747
-     *
748
-     * @access protected
749
-     * @return void
750
-     */
751
-    protected function _events_export()
752
-    {
753
-        if (isset($this->_req_data['EVT_ID'])) {
754
-            $event_ids = $this->_req_data['EVT_ID'];
755
-        } elseif (isset($this->_req_data['EVT_IDs'])) {
756
-            $event_ids = $this->_req_data['EVT_IDs'];
757
-        } else {
758
-            $event_ids = null;
759
-        }
760
-        //todo: I don't like doing this but it'll do until we modify EE_Export Class.
761
-        $new_request_args = array(
762
-            'export' => 'report',
763
-            'action' => 'all_event_data',
764
-            'EVT_ID' => $event_ids,
765
-        );
766
-        $this->_req_data  = array_merge($this->_req_data, $new_request_args);
767
-        if (is_readable(EE_CLASSES . 'EE_Export.class.php')) {
768
-            require_once(EE_CLASSES . 'EE_Export.class.php');
769
-            $EE_Export = EE_Export::instance($this->_req_data);
770
-            $EE_Export->export();
771
-        }
772
-    }
773
-
774
-
775
-    /**
776
-     * handle category exports()
777
-     *
778
-     * @return void
779
-     */
780
-    protected function _categories_export()
781
-    {
782
-        //todo: I don't like doing this but it'll do until we modify EE_Export Class.
783
-        $new_request_args = array(
784
-            'export'       => 'report',
785
-            'action'       => 'categories',
786
-            'category_ids' => $this->_req_data['EVT_CAT_ID'],
787
-        );
788
-        $this->_req_data  = array_merge($this->_req_data, $new_request_args);
789
-        if (is_readable(EE_CLASSES . 'EE_Export.class.php')) {
790
-            require_once(EE_CLASSES . 'EE_Export.class.php');
791
-            $EE_Export = EE_Export::instance($this->_req_data);
792
-            $EE_Export->export();
793
-        }
794
-    }
795
-
796
-
797
-    /**
798
-     * Creates a sample CSV file for importing
799
-     */
800
-    protected function _sample_export_file()
801
-    {
802
-        //		require_once(EE_CLASSES . 'EE_Export.class.php');
803
-        EE_Export::instance()->export_sample();
804
-    }
805
-
806
-
807
-    /*************        Template Settings        *************/
808
-    /**
809
-     * Generates template settings page output
810
-     * @throws DomainException
811
-     * @throws EE_Error
812
-     */
813
-    protected function _template_settings()
814
-    {
815
-        $this->_template_args['values'] = $this->_yes_no_values;
816
-        /**
817
-         * Note leaving this filter in for backward compatibility this was moved in 4.6.x
818
-         * from General_Settings_Admin_Page to here.
819
-         */
820
-        $this->_template_args = apply_filters(
821
-            'FHEE__General_Settings_Admin_Page__template_settings__template_args',
822
-            $this->_template_args
823
-        );
824
-        $this->_set_add_edit_form_tags('update_template_settings');
825
-        $this->_set_publish_post_box_vars(null, false, false, null, false);
826
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
827
-            EVENTS_CAF_TEMPLATE_PATH . 'template_settings.template.php',
828
-            $this->_template_args,
829
-            true
830
-        );
831
-        $this->display_admin_page_with_sidebar();
832
-    }
833
-
834
-
835
-    /**
836
-     * Handler for updating template settings.
837
-     */
838
-    protected function _update_template_settings()
839
-    {
840
-        /**
841
-         * Note leaving this filter in for backward compatibility this was moved in 4.6.x
842
-         * from General_Settings_Admin_Page to here.
843
-         */
844
-        EE_Registry::instance()->CFG->template_settings = apply_filters(
845
-            'FHEE__General_Settings_Admin_Page__update_template_settings__data',
846
-            EE_Registry::instance()->CFG->template_settings,
847
-            $this->_req_data
848
-        );
849
-        //update custom post type slugs and detect if we need to flush rewrite rules
850
-        $old_slug                                          = EE_Registry::instance()->CFG->core->event_cpt_slug;
851
-        EE_Registry::instance()->CFG->core->event_cpt_slug = empty($this->_req_data['event_cpt_slug'])
852
-            ? EE_Registry::instance()->CFG->core->event_cpt_slug
853
-            : sanitize_title_with_dashes($this->_req_data['event_cpt_slug']);
854
-        $what                                              = 'Template Settings';
855
-        $success                                           = $this->_update_espresso_configuration(
856
-            $what,
857
-            EE_Registry::instance()->CFG->template_settings,
858
-            __FILE__,
859
-            __FUNCTION__,
860
-            __LINE__
861
-        );
862
-        if (EE_Registry::instance()->CFG->core->event_cpt_slug != $old_slug) {
863
-            update_option('ee_flush_rewrite_rules', true);
864
-        }
865
-        $this->_redirect_after_action($success, $what, 'updated', array('action' => 'template_settings'));
866
-    }
867
-
868
-
869
-    /**
870
-     * _premium_event_editor_meta_boxes
871
-     * add all metaboxes related to the event_editor
872
-     *
873
-     * @access protected
874
-     * @return void
875
-     * @throws EE_Error
876
-     */
877
-    protected function _premium_event_editor_meta_boxes()
878
-    {
879
-        $this->verify_cpt_object();
880
-        add_meta_box(
881
-            'espresso_event_editor_event_options',
882
-            esc_html__('Event Registration Options', 'event_espresso'),
883
-            array($this, 'registration_options_meta_box'),
884
-            $this->page_slug,
885
-            'side',
886
-            'core'
887
-        );
888
-    }
889
-
890
-
891
-    /**
892
-     * override caf metabox
893
-     *
894
-     * @return void
895
-     * @throws DomainException
896
-     */
897
-    public function registration_options_meta_box()
898
-    {
899
-        $yes_no_values                                    = array(
900
-            array('id' => true, 'text' => esc_html__('Yes', 'event_espresso')),
901
-            array('id' => false, 'text' => esc_html__('No', 'event_espresso')),
902
-        );
903
-        $default_reg_status_values                        = EEM_Registration::reg_status_array(
904
-            array(
905
-                EEM_Registration::status_id_cancelled,
906
-                EEM_Registration::status_id_declined,
907
-                EEM_Registration::status_id_incomplete,
908
-                EEM_Registration::status_id_wait_list,
909
-            ),
910
-            true
911
-        );
912
-        $template_args['active_status']                   = $this->_cpt_model_obj->pretty_active_status(false);
913
-        $template_args['_event']                          = $this->_cpt_model_obj;
914
-        $template_args['additional_limit']                = $this->_cpt_model_obj->additional_limit();
915
-        $template_args['default_registration_status']     = EEH_Form_Fields::select_input(
916
-            'default_reg_status',
917
-            $default_reg_status_values,
918
-            $this->_cpt_model_obj->default_registration_status()
919
-        );
920
-        $template_args['display_description']             = EEH_Form_Fields::select_input(
921
-            'display_desc',
922
-            $yes_no_values,
923
-            $this->_cpt_model_obj->display_description()
924
-        );
925
-        $template_args['display_ticket_selector']         = EEH_Form_Fields::select_input(
926
-            'display_ticket_selector',
927
-            $yes_no_values,
928
-            $this->_cpt_model_obj->display_ticket_selector(),
929
-            '',
930
-            '',
931
-            false
932
-        );
933
-        $template_args['EVT_default_registration_status'] = EEH_Form_Fields::select_input(
934
-            'EVT_default_registration_status',
935
-            $default_reg_status_values,
936
-            $this->_cpt_model_obj->default_registration_status()
937
-        );
938
-        $template_args['additional_registration_options'] = apply_filters(
939
-            'FHEE__Events_Admin_Page__registration_options_meta_box__additional_registration_options',
940
-            '',
941
-            $template_args,
942
-            $yes_no_values,
943
-            $default_reg_status_values
944
-        );
945
-        EEH_Template::display_template(
946
-            EVENTS_CAF_TEMPLATE_PATH . 'event_registration_options.template.php',
947
-            $template_args
948
-        );
949
-    }
950
-
951
-
952
-
953
-    /**
954
-     * wp_list_table_mods for caf
955
-     * ============================
956
-     */
957
-    /**
958
-     * hook into list table filters and provide filters for caffeinated list table
959
-     *
960
-     * @param  array $old_filters    any existing filters present
961
-     * @param  array $list_table_obj the list table object
962
-     * @return array                  new filters
963
-     */
964
-    public function list_table_filters($old_filters, $list_table_obj)
965
-    {
966
-        $filters = array();
967
-        //first month/year filters
968
-        $filters[] = $this->espresso_event_months_dropdown();
969
-        $status    = isset($this->_req_data['status']) ? $this->_req_data['status'] : null;
970
-        //active status dropdown
971
-        if ($status !== 'draft') {
972
-            $filters[] = $this->active_status_dropdown(
973
-                isset($this->_req_data['active_status']) ? $this->_req_data['active_status'] : ''
974
-            );
975
-        }
976
-        //category filter
977
-        $filters[] = $this->category_dropdown();
978
-        return array_merge($old_filters, $filters);
979
-    }
980
-
981
-
982
-    /**
983
-     * espresso_event_months_dropdown
984
-     *
985
-     * @access public
986
-     * @return string                dropdown listing month/year selections for events.
987
-     */
988
-    public function espresso_event_months_dropdown()
989
-    {
990
-        // what we need to do is get all PRIMARY datetimes for all events to filter on.
991
-        // Note we need to include any other filters that are set!
992
-        $status = isset($this->_req_data['status']) ? $this->_req_data['status'] : null;
993
-        //categories?
994
-        $category = isset($this->_req_data['EVT_CAT']) && $this->_req_data['EVT_CAT'] > 0
995
-            ? $this->_req_data['EVT_CAT']
996
-            : null;
997
-        //active status?
998
-        $active_status = isset($this->_req_data['active_status']) ? $this->_req_data['active_status'] : null;
999
-        $cur_date      = isset($this->_req_data['month_range']) ? $this->_req_data['month_range'] : '';
1000
-        return EEH_Form_Fields::generate_event_months_dropdown($cur_date, $status, $category, $active_status);
1001
-    }
1002
-
1003
-
1004
-    /**
1005
-     * returns a list of "active" statuses on the event
1006
-     *
1007
-     * @param  string $current_value whatever the current active status is
1008
-     * @return string
1009
-     */
1010
-    public function active_status_dropdown($current_value = '')
1011
-    {
1012
-        $select_name = 'active_status';
1013
-        $values      = array(
1014
-            'none'     => esc_html__('Show Active/Inactive', 'event_espresso'),
1015
-            'active'   => esc_html__('Active', 'event_espresso'),
1016
-            'upcoming' => esc_html__('Upcoming', 'event_espresso'),
1017
-            'expired'  => esc_html__('Expired', 'event_espresso'),
1018
-            'inactive' => esc_html__('Inactive', 'event_espresso'),
1019
-        );
1020
-        $id          = 'id="espresso-active-status-dropdown-filter"';
1021
-        $class       = 'wide';
1022
-        return EEH_Form_Fields::select_input($select_name, $values, $current_value, $id, $class);
1023
-    }
1024
-
1025
-
1026
-    /**
1027
-     * output a dropdown of the categories for the category filter on the event admin list table
1028
-     *
1029
-     * @access  public
1030
-     * @return string html
1031
-     */
1032
-    public function category_dropdown()
1033
-    {
1034
-        $cur_cat = isset($this->_req_data['EVT_CAT']) ? $this->_req_data['EVT_CAT'] : -1;
1035
-        return EEH_Form_Fields::generate_event_category_dropdown($cur_cat);
1036
-    }
1037
-
1038
-
1039
-    /**
1040
-     * get total number of events today
1041
-     *
1042
-     * @access public
1043
-     * @return int
1044
-     * @throws EE_Error
1045
-     */
1046
-    public function total_events_today()
1047
-    {
1048
-        $start = EEM_Datetime::instance()->convert_datetime_for_query(
1049
-            'DTT_EVT_start',
1050
-            date('Y-m-d') . ' 00:00:00',
1051
-            'Y-m-d H:i:s',
1052
-            'UTC'
1053
-        );
1054
-        $end   = EEM_Datetime::instance()->convert_datetime_for_query(
1055
-            'DTT_EVT_start',
1056
-            date('Y-m-d') . ' 23:59:59',
1057
-            'Y-m-d H:i:s',
1058
-            'UTC'
1059
-        );
1060
-        $where = array(
1061
-            'Datetime.DTT_EVT_start' => array('BETWEEN', array($start, $end)),
1062
-        );
1063
-        $count = EEM_Event::instance()->count(array($where, 'caps' => 'read_admin'), 'EVT_ID', true);
1064
-        return $count;
1065
-    }
1066
-
1067
-
1068
-    /**
1069
-     * get total number of events this month
1070
-     *
1071
-     * @access public
1072
-     * @return int
1073
-     * @throws EE_Error
1074
-     */
1075
-    public function total_events_this_month()
1076
-    {
1077
-        //Dates
1078
-        $this_year_r     = date('Y');
1079
-        $this_month_r    = date('m');
1080
-        $days_this_month = date('t');
1081
-        $start           = EEM_Datetime::instance()->convert_datetime_for_query(
1082
-            'DTT_EVT_start',
1083
-            $this_year_r . '-' . $this_month_r . '-01 00:00:00',
1084
-            'Y-m-d H:i:s',
1085
-            'UTC'
1086
-        );
1087
-        $end             = EEM_Datetime::instance()->convert_datetime_for_query(
1088
-            'DTT_EVT_start',
1089
-            $this_year_r . '-' . $this_month_r . '-' . $days_this_month . ' 23:59:59',
1090
-            'Y-m-d H:i:s',
1091
-            'UTC'
1092
-        );
1093
-        $where           = array(
1094
-            'Datetime.DTT_EVT_start' => array('BETWEEN', array($start, $end)),
1095
-        );
1096
-        $count           = EEM_Event::instance()->count(array($where, 'caps' => 'read_admin'), 'EVT_ID', true);
1097
-        return $count;
1098
-    }
1099
-
1100
-
1101
-    /** DEFAULT TICKETS STUFF **/
1102
-
1103
-    /**
1104
-     * Output default tickets list table view.
1105
-     */
1106
-    public function _tickets_overview_list_table()
1107
-    {
1108
-        $this->_search_btn_label = esc_html__('Tickets', 'event_espresso');
1109
-        $this->display_admin_list_table_page_with_no_sidebar();
1110
-    }
1111
-
1112
-
1113
-    /**
1114
-     * @param int  $per_page
1115
-     * @param bool $count
1116
-     * @param bool $trashed
1117
-     * @return \EE_Soft_Delete_Base_Class[]|int
1118
-     */
1119
-    public function get_default_tickets($per_page = 10, $count = false, $trashed = false)
1120
-    {
1121
-        $orderby = empty($this->_req_data['orderby']) ? 'TKT_name' : $this->_req_data['orderby'];
1122
-        $order   = empty($this->_req_data['order']) ? 'ASC' : $this->_req_data['order'];
1123
-        switch ($orderby) {
1124
-            case 'TKT_name':
1125
-                $orderby = array('TKT_name' => $order);
1126
-                break;
1127
-            case 'TKT_price':
1128
-                $orderby = array('TKT_price' => $order);
1129
-                break;
1130
-            case 'TKT_uses':
1131
-                $orderby = array('TKT_uses' => $order);
1132
-                break;
1133
-            case 'TKT_min':
1134
-                $orderby = array('TKT_min' => $order);
1135
-                break;
1136
-            case 'TKT_max':
1137
-                $orderby = array('TKT_max' => $order);
1138
-                break;
1139
-            case 'TKT_qty':
1140
-                $orderby = array('TKT_qty' => $order);
1141
-                break;
1142
-        }
1143
-        $current_page = isset($this->_req_data['paged']) && ! empty($this->_req_data['paged'])
1144
-            ? $this->_req_data['paged']
1145
-            : 1;
1146
-        $per_page     = isset($this->_req_data['perpage']) && ! empty($this->_req_data['perpage'])
1147
-            ? $this->_req_data['perpage']
1148
-            : $per_page;
1149
-        $_where       = array(
1150
-            'TKT_is_default' => 1,
1151
-            'TKT_deleted'    => $trashed,
1152
-        );
1153
-        $offset       = ($current_page - 1) * $per_page;
1154
-        $limit        = array($offset, $per_page);
1155
-        if (isset($this->_req_data['s'])) {
1156
-            $sstr         = '%' . $this->_req_data['s'] . '%';
1157
-            $_where['OR'] = array(
1158
-                'TKT_name'        => array('LIKE', $sstr),
1159
-                'TKT_description' => array('LIKE', $sstr),
1160
-            );
1161
-        }
1162
-        $query_params = array(
1163
-            $_where,
1164
-            'order_by' => $orderby,
1165
-            'limit'    => $limit,
1166
-            'group_by' => 'TKT_ID',
1167
-        );
1168
-        if ($count) {
1169
-            return EEM_Ticket::instance()->count_deleted_and_undeleted(array($_where));
1170
-        } else {
1171
-            return EEM_Ticket::instance()->get_all_deleted_and_undeleted($query_params);
1172
-        }
1173
-    }
1174
-
1175
-
1176
-    /**
1177
-     * @param bool $trash
1178
-     * @throws EE_Error
1179
-     */
1180
-    protected function _trash_or_restore_ticket($trash = false)
1181
-    {
1182
-        $success = 1;
1183
-        $TKT     = EEM_Ticket::instance();
1184
-        //checkboxes?
1185
-        if (! empty($this->_req_data['checkbox']) && is_array($this->_req_data['checkbox'])) {
1186
-            //if array has more than one element then success message should be plural
1187
-            $success = count($this->_req_data['checkbox']) > 1 ? 2 : 1;
1188
-            //cycle thru the boxes
1189
-            while (list($TKT_ID, $value) = each($this->_req_data['checkbox'])) {
1190
-                if ($trash) {
1191
-                    if (! $TKT->delete_by_ID($TKT_ID)) {
1192
-                        $success = 0;
1193
-                    }
1194
-                } else {
1195
-                    if (! $TKT->restore_by_ID($TKT_ID)) {
1196
-                        $success = 0;
1197
-                    }
1198
-                }
1199
-            }
1200
-        } else {
1201
-            //grab single id and trash
1202
-            $TKT_ID = absint($this->_req_data['TKT_ID']);
1203
-            if ($trash) {
1204
-                if (! $TKT->delete_by_ID($TKT_ID)) {
1205
-                    $success = 0;
1206
-                }
1207
-            } else {
1208
-                if (! $TKT->restore_by_ID($TKT_ID)) {
1209
-                    $success = 0;
1210
-                }
1211
-            }
1212
-        }
1213
-        $action_desc = $trash ? 'moved to the trash' : 'restored';
1214
-        $query_args  = array(
1215
-            'action' => 'ticket_list_table',
1216
-            'status' => $trash ? '' : 'trashed',
1217
-        );
1218
-        $this->_redirect_after_action($success, 'Tickets', $action_desc, $query_args);
1219
-    }
1220
-
1221
-
1222
-    /**
1223
-     * Handles trashing default ticket.
1224
-     */
1225
-    protected function _delete_ticket()
1226
-    {
1227
-        $success = 1;
1228
-        //checkboxes?
1229
-        if (! empty($this->_req_data['checkbox']) && is_array($this->_req_data['checkbox'])) {
1230
-            //if array has more than one element then success message should be plural
1231
-            $success = count($this->_req_data['checkbox']) > 1 ? 2 : 1;
1232
-            //cycle thru the boxes
1233
-            while (list($TKT_ID, $value) = each($this->_req_data['checkbox'])) {
1234
-                //delete
1235
-                if (! $this->_delete_the_ticket($TKT_ID)) {
1236
-                    $success = 0;
1237
-                }
1238
-            }
1239
-        } else {
1240
-            //grab single id and trash
1241
-            $TKT_ID = absint($this->_req_data['TKT_ID']);
1242
-            if (! $this->_delete_the_ticket($TKT_ID)) {
1243
-                $success = 0;
1244
-            }
1245
-        }
1246
-        $action_desc = 'deleted';
1247
-        $query_args  = array(
1248
-            'action' => 'ticket_list_table',
1249
-            'status' => 'trashed',
1250
-        );
1251
-        //fail safe.  If the default ticket count === 1 then we need to redirect to event overview.
1252
-        if (EEM_Ticket::instance()->count_deleted_and_undeleted(
1253
-            array(array('TKT_is_default' => 1)),
1254
-            'TKT_ID',
1255
-            true
1256
-        )
1257
-        ) {
1258
-            $query_args = array();
1259
-        }
1260
-        $this->_redirect_after_action($success, 'Tickets', $action_desc, $query_args);
1261
-    }
1262
-
1263
-
1264
-    /**
1265
-     * @param int $TKT_ID
1266
-     * @return bool|int
1267
-     * @throws EE_Error
1268
-     */
1269
-    protected function _delete_the_ticket($TKT_ID)
1270
-    {
1271
-        $tkt = EEM_Ticket::instance()->get_one_by_ID($TKT_ID);
1272
-        $tkt->_remove_relations('Datetime');
1273
-        //delete all related prices first
1274
-        $tkt->delete_related_permanently('Price');
1275
-        return $tkt->delete_permanently();
1276
-    }
17
+	/**
18
+	 * Extend_Events_Admin_Page constructor.
19
+	 *
20
+	 * @param bool $routing
21
+	 */
22
+	public function __construct($routing = true)
23
+	{
24
+		parent::__construct($routing);
25
+		if (! defined('EVENTS_CAF_TEMPLATE_PATH')) {
26
+			define('EVENTS_CAF_TEMPLATE_PATH', EE_CORE_CAF_ADMIN_EXTEND . 'events/templates/');
27
+			define('EVENTS_CAF_ASSETS', EE_CORE_CAF_ADMIN_EXTEND . 'events/assets/');
28
+			define('EVENTS_CAF_ASSETS_URL', EE_CORE_CAF_ADMIN_EXTEND_URL . 'events/assets/');
29
+		}
30
+	}
31
+
32
+
33
+	/**
34
+	 * Sets routes.
35
+	 */
36
+	protected function _extend_page_config()
37
+	{
38
+		$this->_admin_base_path = EE_CORE_CAF_ADMIN_EXTEND . 'events';
39
+		//is there a evt_id in the request?
40
+		$evt_id = ! empty($this->_req_data['EVT_ID']) && ! is_array($this->_req_data['EVT_ID'])
41
+			? $this->_req_data['EVT_ID']
42
+			: 0;
43
+		$evt_id = ! empty($this->_req_data['post']) ? $this->_req_data['post'] : $evt_id;
44
+		//tkt_id?
45
+		$tkt_id             = ! empty($this->_req_data['TKT_ID']) && ! is_array($this->_req_data['TKT_ID'])
46
+			? $this->_req_data['TKT_ID']
47
+			: 0;
48
+		$new_page_routes    = array(
49
+			'duplicate_event'          => array(
50
+				'func'       => '_duplicate_event',
51
+				'capability' => 'ee_edit_event',
52
+				'obj_id'     => $evt_id,
53
+				'noheader'   => true,
54
+			),
55
+			'ticket_list_table'        => array(
56
+				'func'       => '_tickets_overview_list_table',
57
+				'capability' => 'ee_read_default_tickets',
58
+			),
59
+			'trash_ticket'             => array(
60
+				'func'       => '_trash_or_restore_ticket',
61
+				'capability' => 'ee_delete_default_ticket',
62
+				'obj_id'     => $tkt_id,
63
+				'noheader'   => true,
64
+				'args'       => array('trash' => true),
65
+			),
66
+			'trash_tickets'            => array(
67
+				'func'       => '_trash_or_restore_ticket',
68
+				'capability' => 'ee_delete_default_tickets',
69
+				'noheader'   => true,
70
+				'args'       => array('trash' => true),
71
+			),
72
+			'restore_ticket'           => array(
73
+				'func'       => '_trash_or_restore_ticket',
74
+				'capability' => 'ee_delete_default_ticket',
75
+				'obj_id'     => $tkt_id,
76
+				'noheader'   => true,
77
+			),
78
+			'restore_tickets'          => array(
79
+				'func'       => '_trash_or_restore_ticket',
80
+				'capability' => 'ee_delete_default_tickets',
81
+				'noheader'   => true,
82
+			),
83
+			'delete_ticket'            => array(
84
+				'func'       => '_delete_ticket',
85
+				'capability' => 'ee_delete_default_ticket',
86
+				'obj_id'     => $tkt_id,
87
+				'noheader'   => true,
88
+			),
89
+			'delete_tickets'           => array(
90
+				'func'       => '_delete_ticket',
91
+				'capability' => 'ee_delete_default_tickets',
92
+				'noheader'   => true,
93
+			),
94
+			'import_page'              => array(
95
+				'func'       => '_import_page',
96
+				'capability' => 'import',
97
+			),
98
+			'import'                   => array(
99
+				'func'       => '_import_events',
100
+				'capability' => 'import',
101
+				'noheader'   => true,
102
+			),
103
+			'import_events'            => array(
104
+				'func'       => '_import_events',
105
+				'capability' => 'import',
106
+				'noheader'   => true,
107
+			),
108
+			'export_events'            => array(
109
+				'func'       => '_events_export',
110
+				'capability' => 'export',
111
+				'noheader'   => true,
112
+			),
113
+			'export_categories'        => array(
114
+				'func'       => '_categories_export',
115
+				'capability' => 'export',
116
+				'noheader'   => true,
117
+			),
118
+			'sample_export_file'       => array(
119
+				'func'       => '_sample_export_file',
120
+				'capability' => 'export',
121
+				'noheader'   => true,
122
+			),
123
+			'update_template_settings' => array(
124
+				'func'       => '_update_template_settings',
125
+				'capability' => 'manage_options',
126
+				'noheader'   => true,
127
+			),
128
+		);
129
+		$this->_page_routes = array_merge($this->_page_routes, $new_page_routes);
130
+		//partial route/config override
131
+		$this->_page_config['import_events']['metaboxes'] = $this->_default_espresso_metaboxes;
132
+		$this->_page_config['create_new']['metaboxes'][]  = '_premium_event_editor_meta_boxes';
133
+		$this->_page_config['create_new']['qtips'][]      = 'EE_Event_Editor_Tips';
134
+		$this->_page_config['edit']['qtips'][]            = 'EE_Event_Editor_Tips';
135
+		$this->_page_config['edit']['metaboxes'][]        = '_premium_event_editor_meta_boxes';
136
+		$this->_page_config['default']['list_table']      = 'Extend_Events_Admin_List_Table';
137
+		//add tickets tab but only if there are more than one default ticket!
138
+		$tkt_count = EEM_Ticket::instance()->count_deleted_and_undeleted(
139
+			array(array('TKT_is_default' => 1)),
140
+			'TKT_ID',
141
+			true
142
+		);
143
+		if ($tkt_count > 1) {
144
+			$new_page_config = array(
145
+				'ticket_list_table' => array(
146
+					'nav'           => array(
147
+						'label' => esc_html__('Default Tickets', 'event_espresso'),
148
+						'order' => 60,
149
+					),
150
+					'list_table'    => 'Tickets_List_Table',
151
+					'require_nonce' => false,
152
+				),
153
+			);
154
+		}
155
+		//template settings
156
+		$new_page_config['template_settings'] = array(
157
+			'nav'           => array(
158
+				'label' => esc_html__('Templates', 'event_espresso'),
159
+				'order' => 30,
160
+			),
161
+			'metaboxes'     => array_merge($this->_default_espresso_metaboxes, array('_publish_post_box')),
162
+			'help_tabs'     => array(
163
+				'general_settings_templates_help_tab' => array(
164
+					'title'    => esc_html__('Templates', 'event_espresso'),
165
+					'filename' => 'general_settings_templates',
166
+				),
167
+			),
168
+			'help_tour'     => array('Templates_Help_Tour'),
169
+			'require_nonce' => false,
170
+		);
171
+		$this->_page_config                   = array_merge($this->_page_config, $new_page_config);
172
+		//add filters and actions
173
+		//modifying _views
174
+		add_filter(
175
+			'FHEE_event_datetime_metabox_add_additional_date_time_template',
176
+			array($this, 'add_additional_datetime_button'),
177
+			10,
178
+			2
179
+		);
180
+		add_filter(
181
+			'FHEE_event_datetime_metabox_clone_button_template',
182
+			array($this, 'add_datetime_clone_button'),
183
+			10,
184
+			2
185
+		);
186
+		add_filter(
187
+			'FHEE_event_datetime_metabox_timezones_template',
188
+			array($this, 'datetime_timezones_template'),
189
+			10,
190
+			2
191
+		);
192
+		//filters for event list table
193
+		add_filter('FHEE__Extend_Events_Admin_List_Table__filters', array($this, 'list_table_filters'), 10, 2);
194
+		add_filter(
195
+			'FHEE__Events_Admin_List_Table__column_actions__action_links',
196
+			array($this, 'extra_list_table_actions'),
197
+			10,
198
+			2
199
+		);
200
+		//legend item
201
+		add_filter('FHEE__Events_Admin_Page___event_legend_items__items', array($this, 'additional_legend_items'));
202
+		add_action('admin_init', array($this, 'admin_init'));
203
+		//heartbeat stuff
204
+		add_filter('heartbeat_received', array($this, 'heartbeat_response'), 10, 2);
205
+	}
206
+
207
+
208
+	/**
209
+	 * admin_init
210
+	 */
211
+	public function admin_init()
212
+	{
213
+		EE_Registry::$i18n_js_strings = array_merge(
214
+			EE_Registry::$i18n_js_strings,
215
+			array(
216
+				'image_confirm'          => esc_html__(
217
+					'Do you really want to delete this image? Please remember to update your event to complete the removal.',
218
+					'event_espresso'
219
+				),
220
+				'event_starts_on'        => esc_html__('Event Starts on', 'event_espresso'),
221
+				'event_ends_on'          => esc_html__('Event Ends on', 'event_espresso'),
222
+				'event_datetime_actions' => esc_html__('Actions', 'event_espresso'),
223
+				'event_clone_dt_msg'     => esc_html__('Clone this Event Date and Time', 'event_espresso'),
224
+				'remove_event_dt_msg'    => esc_html__('Remove this Event Time', 'event_espresso'),
225
+			)
226
+		);
227
+	}
228
+
229
+
230
+	/**
231
+	 * This will be used to listen for any heartbeat data packages coming via the WordPress heartbeat API and handle
232
+	 * accordingly.
233
+	 *
234
+	 * @param array $response The existing heartbeat response array.
235
+	 * @param array $data     The incoming data package.
236
+	 * @return array  possibly appended response.
237
+	 */
238
+	public function heartbeat_response($response, $data)
239
+	{
240
+		/**
241
+		 * check whether count of tickets is approaching the potential
242
+		 * limits for the server.
243
+		 */
244
+		if (! empty($data['input_count'])) {
245
+			$response['max_input_vars_check'] = EE_Registry::instance()->CFG->environment->max_input_vars_limit_check(
246
+				$data['input_count']
247
+			);
248
+		}
249
+		return $response;
250
+	}
251
+
252
+
253
+	/**
254
+	 * Add per page screen options to the default ticket list table view.
255
+	 */
256
+	protected function _add_screen_options_ticket_list_table()
257
+	{
258
+		$this->_per_page_screen_option();
259
+	}
260
+
261
+
262
+	/**
263
+	 * @param string $return
264
+	 * @param int    $id
265
+	 * @param string $new_title
266
+	 * @param string $new_slug
267
+	 * @return string
268
+	 */
269
+	public function extra_permalink_field_buttons($return, $id, $new_title, $new_slug)
270
+	{
271
+		$return = parent::extra_permalink_field_buttons($return, $id, $new_title, $new_slug);
272
+		//make sure this is only when editing
273
+		if (! empty($id)) {
274
+			$href   = EE_Admin_Page::add_query_args_and_nonce(
275
+				array('action' => 'duplicate_event', 'EVT_ID' => $id),
276
+				$this->_admin_base_url
277
+			);
278
+			$title  = esc_attr__('Duplicate Event', 'event_espresso');
279
+			$return .= '<a href="'
280
+					   . $href
281
+					   . '" title="'
282
+					   . $title
283
+					   . '" id="ee-duplicate-event-button" class="button button-small"  value="duplicate_event">'
284
+					   . $title
285
+					   . '</button>';
286
+		}
287
+		return $return;
288
+	}
289
+
290
+
291
+	/**
292
+	 * Set the list table views for the default ticket list table view.
293
+	 */
294
+	public function _set_list_table_views_ticket_list_table()
295
+	{
296
+		$this->_views = array(
297
+			'all'     => array(
298
+				'slug'        => 'all',
299
+				'label'       => esc_html__('All', 'event_espresso'),
300
+				'count'       => 0,
301
+				'bulk_action' => array(
302
+					'trash_tickets' => esc_html__('Move to Trash', 'event_espresso'),
303
+				),
304
+			),
305
+			'trashed' => array(
306
+				'slug'        => 'trashed',
307
+				'label'       => esc_html__('Trash', 'event_espresso'),
308
+				'count'       => 0,
309
+				'bulk_action' => array(
310
+					'restore_tickets' => esc_html__('Restore from Trash', 'event_espresso'),
311
+					'delete_tickets'  => esc_html__('Delete Permanently', 'event_espresso'),
312
+				),
313
+			),
314
+		);
315
+	}
316
+
317
+
318
+	/**
319
+	 * Enqueue scripts and styles for the event editor.
320
+	 */
321
+	public function load_scripts_styles_edit()
322
+	{
323
+		wp_register_script(
324
+			'ee-event-editor-heartbeat',
325
+			EVENTS_CAF_ASSETS_URL . 'event-editor-heartbeat.js',
326
+			array('ee_admin_js', 'heartbeat'),
327
+			EVENT_ESPRESSO_VERSION,
328
+			true
329
+		);
330
+		wp_enqueue_script('ee-accounting');
331
+		//styles
332
+		wp_enqueue_style('espresso-ui-theme');
333
+		wp_enqueue_script('event_editor_js');
334
+		wp_enqueue_script('ee-event-editor-heartbeat');
335
+	}
336
+
337
+
338
+	/**
339
+	 * Returns template for the additional datetime.
340
+	 * @param $template
341
+	 * @param $template_args
342
+	 * @return mixed
343
+	 * @throws DomainException
344
+	 */
345
+	public function add_additional_datetime_button($template, $template_args)
346
+	{
347
+		return EEH_Template::display_template(
348
+			EVENTS_CAF_TEMPLATE_PATH . 'event_datetime_add_additional_time.template.php',
349
+			$template_args,
350
+			true
351
+		);
352
+	}
353
+
354
+
355
+	/**
356
+	 * Returns the template for cloning a datetime.
357
+	 * @param $template
358
+	 * @param $template_args
359
+	 * @return mixed
360
+	 * @throws DomainException
361
+	 */
362
+	public function add_datetime_clone_button($template, $template_args)
363
+	{
364
+		return EEH_Template::display_template(
365
+			EVENTS_CAF_TEMPLATE_PATH . 'event_datetime_metabox_clone_button.template.php',
366
+			$template_args,
367
+			true
368
+		);
369
+	}
370
+
371
+
372
+	/**
373
+	 * Returns the template for datetime timezones.
374
+	 * @param $template
375
+	 * @param $template_args
376
+	 * @return mixed
377
+	 * @throws DomainException
378
+	 */
379
+	public function datetime_timezones_template($template, $template_args)
380
+	{
381
+		return EEH_Template::display_template(
382
+			EVENTS_CAF_TEMPLATE_PATH . 'event_datetime_timezones.template.php',
383
+			$template_args,
384
+			true
385
+		);
386
+	}
387
+
388
+
389
+	/**
390
+	 * Sets the views for the default list table view.
391
+	 */
392
+	protected function _set_list_table_views_default()
393
+	{
394
+		parent::_set_list_table_views_default();
395
+		$new_views    = array(
396
+			'today' => array(
397
+				'slug'        => 'today',
398
+				'label'       => esc_html__('Today', 'event_espresso'),
399
+				'count'       => $this->total_events_today(),
400
+				'bulk_action' => array(
401
+					'trash_events' => esc_html__('Move to Trash', 'event_espresso'),
402
+				),
403
+			),
404
+			'month' => array(
405
+				'slug'        => 'month',
406
+				'label'       => esc_html__('This Month', 'event_espresso'),
407
+				'count'       => $this->total_events_this_month(),
408
+				'bulk_action' => array(
409
+					'trash_events' => esc_html__('Move to Trash', 'event_espresso'),
410
+				),
411
+			),
412
+		);
413
+		$this->_views = array_merge($this->_views, $new_views);
414
+	}
415
+
416
+
417
+	/**
418
+	 * Returns the extra action links for the default list table view.
419
+	 * @param array     $action_links
420
+	 * @param \EE_Event $event
421
+	 * @return array
422
+	 * @throws EE_Error
423
+	 */
424
+	public function extra_list_table_actions(array $action_links, \EE_Event $event)
425
+	{
426
+		if (EE_Registry::instance()->CAP->current_user_can(
427
+			'ee_read_registrations',
428
+			'espresso_registrations_reports',
429
+			$event->ID()
430
+		)
431
+		) {
432
+			$reports_query_args = array(
433
+				'action' => 'reports',
434
+				'EVT_ID' => $event->ID(),
435
+			);
436
+			$reports_link       = EE_Admin_Page::add_query_args_and_nonce($reports_query_args, REG_ADMIN_URL);
437
+			$action_links[]     = '<a href="'
438
+								  . $reports_link
439
+								  . '" title="'
440
+								  . esc_attr__('View Report', 'event_espresso')
441
+								  . '"><div class="dashicons dashicons-chart-bar"></div></a>'
442
+								  . "\n\t";
443
+		}
444
+		if (EE_Registry::instance()->CAP->current_user_can('ee_read_global_messages', 'view_filtered_messages')) {
445
+			EE_Registry::instance()->load_helper('MSG_Template');
446
+			$action_links[] = EEH_MSG_Template::get_message_action_link(
447
+				'see_notifications_for',
448
+				null,
449
+				array('EVT_ID' => $event->ID())
450
+			);
451
+		}
452
+		return $action_links;
453
+	}
454
+
455
+
456
+	/**
457
+	 * @param $items
458
+	 * @return mixed
459
+	 */
460
+	public function additional_legend_items($items)
461
+	{
462
+		if (EE_Registry::instance()->CAP->current_user_can(
463
+			'ee_read_registrations',
464
+			'espresso_registrations_reports'
465
+		)
466
+		) {
467
+			$items['reports'] = array(
468
+				'class' => 'dashicons dashicons-chart-bar',
469
+				'desc'  => esc_html__('Event Reports', 'event_espresso'),
470
+			);
471
+		}
472
+		if (EE_Registry::instance()->CAP->current_user_can('ee_read_global_messages', 'view_filtered_messages')) {
473
+			$related_for_icon = EEH_MSG_Template::get_message_action_icon('see_notifications_for');
474
+			if (isset($related_for_icon['css_class']) && isset($related_for_icon['label'])) {
475
+				$items['view_related_messages'] = array(
476
+					'class' => $related_for_icon['css_class'],
477
+					'desc'  => $related_for_icon['label'],
478
+				);
479
+			}
480
+		}
481
+		return $items;
482
+	}
483
+
484
+
485
+	/**
486
+	 * This is the callback method for the duplicate event route
487
+	 * Method looks for 'EVT_ID' in the request and retrieves that event and its details and duplicates them
488
+	 * into a new event.  We add a hook so that any plugins that add extra event details can hook into this
489
+	 * action.  Note that the dupe will have **DUPLICATE** as its title and slug.
490
+	 * After duplication the redirect is to the new event edit page.
491
+	 *
492
+	 * @return void
493
+	 * @access protected
494
+	 * @throws EE_Error If EE_Event is not available with given ID
495
+	 */
496
+	protected function _duplicate_event()
497
+	{
498
+		// first make sure the ID for the event is in the request.
499
+		//  If it isn't then we need to bail and redirect back to overview list table (cause how did we get here?)
500
+		if (! isset($this->_req_data['EVT_ID'])) {
501
+			EE_Error::add_error(
502
+				esc_html__(
503
+					'In order to duplicate an event an Event ID is required.  None was given.',
504
+					'event_espresso'
505
+				),
506
+				__FILE__,
507
+				__FUNCTION__,
508
+				__LINE__
509
+			);
510
+			$this->_redirect_after_action(false, '', '', array(), true);
511
+			return;
512
+		}
513
+		//k we've got EVT_ID so let's use that to get the event we'll duplicate
514
+		$orig_event = EEM_Event::instance()->get_one_by_ID($this->_req_data['EVT_ID']);
515
+		if (! $orig_event instanceof EE_Event) {
516
+			throw new EE_Error(
517
+				sprintf(
518
+					esc_html__('An EE_Event object could not be retrieved for the given ID (%s)', 'event_espresso'),
519
+					$this->_req_data['EVT_ID']
520
+				)
521
+			);
522
+		}
523
+		//k now let's clone the $orig_event before getting relations
524
+		$new_event = clone $orig_event;
525
+		//original datetimes
526
+		$orig_datetimes = $orig_event->get_many_related('Datetime');
527
+		//other original relations
528
+		$orig_ven = $orig_event->get_many_related('Venue');
529
+		//reset the ID and modify other details to make it clear this is a dupe
530
+		$new_event->set('EVT_ID', 0);
531
+		$new_name = $new_event->name() . ' ' . esc_html__('**DUPLICATE**', 'event_espresso');
532
+		$new_event->set('EVT_name', $new_name);
533
+		$new_event->set(
534
+			'EVT_slug',
535
+			wp_unique_post_slug(
536
+				sanitize_title($orig_event->name()),
537
+				0,
538
+				'publish',
539
+				'espresso_events',
540
+				0
541
+			)
542
+		);
543
+		$new_event->set('status', 'draft');
544
+		//duplicate discussion settings
545
+		$new_event->set('comment_status', $orig_event->get('comment_status'));
546
+		$new_event->set('ping_status', $orig_event->get('ping_status'));
547
+		//save the new event
548
+		$new_event->save();
549
+		//venues
550
+		foreach ($orig_ven as $ven) {
551
+			$new_event->_add_relation_to($ven, 'Venue');
552
+		}
553
+		$new_event->save();
554
+		//now we need to get the question group relations and handle that
555
+		//first primary question groups
556
+		$orig_primary_qgs = $orig_event->get_many_related(
557
+			'Question_Group',
558
+			array(array('Event_Question_Group.EQG_primary' => 1))
559
+		);
560
+		if (! empty($orig_primary_qgs)) {
561
+			foreach ($orig_primary_qgs as $id => $obj) {
562
+				if ($obj instanceof EE_Question_Group) {
563
+					$new_event->_add_relation_to($obj, 'Question_Group', array('EQG_primary' => 1));
564
+				}
565
+			}
566
+		}
567
+		//next additional attendee question groups
568
+		$orig_additional_qgs = $orig_event->get_many_related(
569
+			'Question_Group',
570
+			array(array('Event_Question_Group.EQG_primary' => 0))
571
+		);
572
+		if (! empty($orig_additional_qgs)) {
573
+			foreach ($orig_additional_qgs as $id => $obj) {
574
+				if ($obj instanceof EE_Question_Group) {
575
+					$new_event->_add_relation_to($obj, 'Question_Group', array('EQG_primary' => 0));
576
+				}
577
+			}
578
+		}
579
+
580
+		$new_event->save();
581
+
582
+		//k now that we have the new event saved we can loop through the datetimes and start adding relations.
583
+		$cloned_tickets = array();
584
+		foreach ($orig_datetimes as $orig_dtt) {
585
+			if (! $orig_dtt instanceof EE_Datetime) {
586
+				continue;
587
+			}
588
+			$new_dtt   = clone $orig_dtt;
589
+			$orig_tkts = $orig_dtt->tickets();
590
+			//save new dtt then add to event
591
+			$new_dtt->set('DTT_ID', 0);
592
+			$new_dtt->set('DTT_sold', 0);
593
+			$new_dtt->set_reserved(0);
594
+			$new_dtt->save();
595
+			$new_event->_add_relation_to($new_dtt, 'Datetime');
596
+			$new_event->save();
597
+			//now let's get the ticket relations setup.
598
+			foreach ((array)$orig_tkts as $orig_tkt) {
599
+				//it's possible a datetime will have no tickets so let's verify we HAVE a ticket first.
600
+				if (! $orig_tkt instanceof EE_Ticket) {
601
+					continue;
602
+				}
603
+				//is this ticket archived?  If it is then let's skip
604
+				if ($orig_tkt->get('TKT_deleted')) {
605
+					continue;
606
+				}
607
+				// does this original ticket already exist in the clone_tickets cache?
608
+				//  If so we'll just use the new ticket from it.
609
+				if (isset($cloned_tickets[$orig_tkt->ID()])) {
610
+					$new_tkt = $cloned_tickets[$orig_tkt->ID()];
611
+				} else {
612
+					$new_tkt = clone $orig_tkt;
613
+					//get relations on the $orig_tkt that we need to setup.
614
+					$orig_prices = $orig_tkt->prices();
615
+					$new_tkt->set('TKT_ID', 0);
616
+					$new_tkt->set('TKT_sold', 0);
617
+					$new_tkt->set('TKT_reserved', 0);
618
+					$new_tkt->save(); //make sure new ticket has ID.
619
+					//price relations on new ticket need to be setup.
620
+					foreach ($orig_prices as $orig_price) {
621
+						$new_price = clone $orig_price;
622
+						$new_price->set('PRC_ID', 0);
623
+						$new_price->save();
624
+						$new_tkt->_add_relation_to($new_price, 'Price');
625
+						$new_tkt->save();
626
+					}
627
+
628
+					do_action(
629
+						'AHEE__Extend_Events_Admin_Page___duplicate_event__duplicate_ticket__after',
630
+						$orig_tkt,
631
+						$new_tkt,
632
+						$orig_prices,
633
+						$orig_event,
634
+						$orig_dtt,
635
+						$new_dtt
636
+					);
637
+				}
638
+				// k now we can add the new ticket as a relation to the new datetime
639
+				// and make sure its added to our cached $cloned_tickets array
640
+				// for use with later datetimes that have the same ticket.
641
+				$new_dtt->_add_relation_to($new_tkt, 'Ticket');
642
+				$new_dtt->save();
643
+				$cloned_tickets[$orig_tkt->ID()] = $new_tkt;
644
+			}
645
+		}
646
+		//clone taxonomy information
647
+		$taxonomies_to_clone_with = apply_filters(
648
+			'FHEE__Extend_Events_Admin_Page___duplicate_event__taxonomies_to_clone',
649
+			array('espresso_event_categories', 'espresso_event_type', 'post_tag')
650
+		);
651
+		//get terms for original event (notice)
652
+		$orig_terms = wp_get_object_terms($orig_event->ID(), $taxonomies_to_clone_with);
653
+		//loop through terms and add them to new event.
654
+		foreach ($orig_terms as $term) {
655
+			wp_set_object_terms($new_event->ID(), $term->term_id, $term->taxonomy, true);
656
+		}
657
+
658
+		//duplicate other core WP_Post items for this event.
659
+		//post thumbnail (feature image).
660
+		$feature_image_id = get_post_thumbnail_id($orig_event->ID());
661
+		if ($feature_image_id) {
662
+			update_post_meta($new_event->ID(), '_thumbnail_id', $feature_image_id);
663
+		}
664
+
665
+		//duplicate page_template setting
666
+		$page_template = get_post_meta($orig_event->ID(), '_wp_page_template', true);
667
+		if ($page_template) {
668
+			update_post_meta($new_event->ID(), '_wp_page_template', $page_template);
669
+		}
670
+
671
+		do_action('AHEE__Extend_Events_Admin_Page___duplicate_event__after', $new_event, $orig_event);
672
+		//now let's redirect to the edit page for this duplicated event if we have a new event id.
673
+		if ($new_event->ID()) {
674
+			$redirect_args = array(
675
+				'post'   => $new_event->ID(),
676
+				'action' => 'edit',
677
+			);
678
+			EE_Error::add_success(
679
+				esc_html__(
680
+					'Event successfully duplicated.  Please review the details below and make any necessary edits',
681
+					'event_espresso'
682
+				)
683
+			);
684
+		} else {
685
+			$redirect_args = array(
686
+				'action' => 'default',
687
+			);
688
+			EE_Error::add_error(
689
+				esc_html__('Not able to duplicate event.  Something went wrong.', 'event_espresso'),
690
+				__FILE__,
691
+				__FUNCTION__,
692
+				__LINE__
693
+			);
694
+		}
695
+		$this->_redirect_after_action(false, '', '', $redirect_args, true);
696
+	}
697
+
698
+
699
+	/**
700
+	 * Generates output for the import page.
701
+	 * @throws DomainException
702
+	 */
703
+	protected function _import_page()
704
+	{
705
+		$title                                      = esc_html__('Import', 'event_espresso');
706
+		$intro                                      = esc_html__(
707
+			'If you have a previously exported Event Espresso 4 information in a Comma Separated Value (CSV) file format, you can upload the file here: ',
708
+			'event_espresso'
709
+		);
710
+		$form_url                                   = EVENTS_ADMIN_URL;
711
+		$action                                     = 'import_events';
712
+		$type                                       = 'csv';
713
+		$this->_template_args['form']               = EE_Import::instance()->upload_form(
714
+			$title, $intro, $form_url, $action, $type
715
+		);
716
+		$this->_template_args['sample_file_link']   = EE_Admin_Page::add_query_args_and_nonce(
717
+			array('action' => 'sample_export_file'),
718
+			$this->_admin_base_url
719
+		);
720
+		$content                                    = EEH_Template::display_template(
721
+			EVENTS_CAF_TEMPLATE_PATH . 'import_page.template.php',
722
+			$this->_template_args,
723
+			true
724
+		);
725
+		$this->_template_args['admin_page_content'] = $content;
726
+		$this->display_admin_page_with_sidebar();
727
+	}
728
+
729
+
730
+	/**
731
+	 * _import_events
732
+	 * This handles displaying the screen and running imports for importing events.
733
+	 *
734
+	 * @return void
735
+	 */
736
+	protected function _import_events()
737
+	{
738
+		require_once(EE_CLASSES . 'EE_Import.class.php');
739
+		$success = EE_Import::instance()->import();
740
+		$this->_redirect_after_action($success, 'Import File', 'ran', array('action' => 'import_page'), true);
741
+	}
742
+
743
+
744
+	/**
745
+	 * _events_export
746
+	 * Will export all (or just the given event) to a Excel compatible file.
747
+	 *
748
+	 * @access protected
749
+	 * @return void
750
+	 */
751
+	protected function _events_export()
752
+	{
753
+		if (isset($this->_req_data['EVT_ID'])) {
754
+			$event_ids = $this->_req_data['EVT_ID'];
755
+		} elseif (isset($this->_req_data['EVT_IDs'])) {
756
+			$event_ids = $this->_req_data['EVT_IDs'];
757
+		} else {
758
+			$event_ids = null;
759
+		}
760
+		//todo: I don't like doing this but it'll do until we modify EE_Export Class.
761
+		$new_request_args = array(
762
+			'export' => 'report',
763
+			'action' => 'all_event_data',
764
+			'EVT_ID' => $event_ids,
765
+		);
766
+		$this->_req_data  = array_merge($this->_req_data, $new_request_args);
767
+		if (is_readable(EE_CLASSES . 'EE_Export.class.php')) {
768
+			require_once(EE_CLASSES . 'EE_Export.class.php');
769
+			$EE_Export = EE_Export::instance($this->_req_data);
770
+			$EE_Export->export();
771
+		}
772
+	}
773
+
774
+
775
+	/**
776
+	 * handle category exports()
777
+	 *
778
+	 * @return void
779
+	 */
780
+	protected function _categories_export()
781
+	{
782
+		//todo: I don't like doing this but it'll do until we modify EE_Export Class.
783
+		$new_request_args = array(
784
+			'export'       => 'report',
785
+			'action'       => 'categories',
786
+			'category_ids' => $this->_req_data['EVT_CAT_ID'],
787
+		);
788
+		$this->_req_data  = array_merge($this->_req_data, $new_request_args);
789
+		if (is_readable(EE_CLASSES . 'EE_Export.class.php')) {
790
+			require_once(EE_CLASSES . 'EE_Export.class.php');
791
+			$EE_Export = EE_Export::instance($this->_req_data);
792
+			$EE_Export->export();
793
+		}
794
+	}
795
+
796
+
797
+	/**
798
+	 * Creates a sample CSV file for importing
799
+	 */
800
+	protected function _sample_export_file()
801
+	{
802
+		//		require_once(EE_CLASSES . 'EE_Export.class.php');
803
+		EE_Export::instance()->export_sample();
804
+	}
805
+
806
+
807
+	/*************        Template Settings        *************/
808
+	/**
809
+	 * Generates template settings page output
810
+	 * @throws DomainException
811
+	 * @throws EE_Error
812
+	 */
813
+	protected function _template_settings()
814
+	{
815
+		$this->_template_args['values'] = $this->_yes_no_values;
816
+		/**
817
+		 * Note leaving this filter in for backward compatibility this was moved in 4.6.x
818
+		 * from General_Settings_Admin_Page to here.
819
+		 */
820
+		$this->_template_args = apply_filters(
821
+			'FHEE__General_Settings_Admin_Page__template_settings__template_args',
822
+			$this->_template_args
823
+		);
824
+		$this->_set_add_edit_form_tags('update_template_settings');
825
+		$this->_set_publish_post_box_vars(null, false, false, null, false);
826
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
827
+			EVENTS_CAF_TEMPLATE_PATH . 'template_settings.template.php',
828
+			$this->_template_args,
829
+			true
830
+		);
831
+		$this->display_admin_page_with_sidebar();
832
+	}
833
+
834
+
835
+	/**
836
+	 * Handler for updating template settings.
837
+	 */
838
+	protected function _update_template_settings()
839
+	{
840
+		/**
841
+		 * Note leaving this filter in for backward compatibility this was moved in 4.6.x
842
+		 * from General_Settings_Admin_Page to here.
843
+		 */
844
+		EE_Registry::instance()->CFG->template_settings = apply_filters(
845
+			'FHEE__General_Settings_Admin_Page__update_template_settings__data',
846
+			EE_Registry::instance()->CFG->template_settings,
847
+			$this->_req_data
848
+		);
849
+		//update custom post type slugs and detect if we need to flush rewrite rules
850
+		$old_slug                                          = EE_Registry::instance()->CFG->core->event_cpt_slug;
851
+		EE_Registry::instance()->CFG->core->event_cpt_slug = empty($this->_req_data['event_cpt_slug'])
852
+			? EE_Registry::instance()->CFG->core->event_cpt_slug
853
+			: sanitize_title_with_dashes($this->_req_data['event_cpt_slug']);
854
+		$what                                              = 'Template Settings';
855
+		$success                                           = $this->_update_espresso_configuration(
856
+			$what,
857
+			EE_Registry::instance()->CFG->template_settings,
858
+			__FILE__,
859
+			__FUNCTION__,
860
+			__LINE__
861
+		);
862
+		if (EE_Registry::instance()->CFG->core->event_cpt_slug != $old_slug) {
863
+			update_option('ee_flush_rewrite_rules', true);
864
+		}
865
+		$this->_redirect_after_action($success, $what, 'updated', array('action' => 'template_settings'));
866
+	}
867
+
868
+
869
+	/**
870
+	 * _premium_event_editor_meta_boxes
871
+	 * add all metaboxes related to the event_editor
872
+	 *
873
+	 * @access protected
874
+	 * @return void
875
+	 * @throws EE_Error
876
+	 */
877
+	protected function _premium_event_editor_meta_boxes()
878
+	{
879
+		$this->verify_cpt_object();
880
+		add_meta_box(
881
+			'espresso_event_editor_event_options',
882
+			esc_html__('Event Registration Options', 'event_espresso'),
883
+			array($this, 'registration_options_meta_box'),
884
+			$this->page_slug,
885
+			'side',
886
+			'core'
887
+		);
888
+	}
889
+
890
+
891
+	/**
892
+	 * override caf metabox
893
+	 *
894
+	 * @return void
895
+	 * @throws DomainException
896
+	 */
897
+	public function registration_options_meta_box()
898
+	{
899
+		$yes_no_values                                    = array(
900
+			array('id' => true, 'text' => esc_html__('Yes', 'event_espresso')),
901
+			array('id' => false, 'text' => esc_html__('No', 'event_espresso')),
902
+		);
903
+		$default_reg_status_values                        = EEM_Registration::reg_status_array(
904
+			array(
905
+				EEM_Registration::status_id_cancelled,
906
+				EEM_Registration::status_id_declined,
907
+				EEM_Registration::status_id_incomplete,
908
+				EEM_Registration::status_id_wait_list,
909
+			),
910
+			true
911
+		);
912
+		$template_args['active_status']                   = $this->_cpt_model_obj->pretty_active_status(false);
913
+		$template_args['_event']                          = $this->_cpt_model_obj;
914
+		$template_args['additional_limit']                = $this->_cpt_model_obj->additional_limit();
915
+		$template_args['default_registration_status']     = EEH_Form_Fields::select_input(
916
+			'default_reg_status',
917
+			$default_reg_status_values,
918
+			$this->_cpt_model_obj->default_registration_status()
919
+		);
920
+		$template_args['display_description']             = EEH_Form_Fields::select_input(
921
+			'display_desc',
922
+			$yes_no_values,
923
+			$this->_cpt_model_obj->display_description()
924
+		);
925
+		$template_args['display_ticket_selector']         = EEH_Form_Fields::select_input(
926
+			'display_ticket_selector',
927
+			$yes_no_values,
928
+			$this->_cpt_model_obj->display_ticket_selector(),
929
+			'',
930
+			'',
931
+			false
932
+		);
933
+		$template_args['EVT_default_registration_status'] = EEH_Form_Fields::select_input(
934
+			'EVT_default_registration_status',
935
+			$default_reg_status_values,
936
+			$this->_cpt_model_obj->default_registration_status()
937
+		);
938
+		$template_args['additional_registration_options'] = apply_filters(
939
+			'FHEE__Events_Admin_Page__registration_options_meta_box__additional_registration_options',
940
+			'',
941
+			$template_args,
942
+			$yes_no_values,
943
+			$default_reg_status_values
944
+		);
945
+		EEH_Template::display_template(
946
+			EVENTS_CAF_TEMPLATE_PATH . 'event_registration_options.template.php',
947
+			$template_args
948
+		);
949
+	}
950
+
951
+
952
+
953
+	/**
954
+	 * wp_list_table_mods for caf
955
+	 * ============================
956
+	 */
957
+	/**
958
+	 * hook into list table filters and provide filters for caffeinated list table
959
+	 *
960
+	 * @param  array $old_filters    any existing filters present
961
+	 * @param  array $list_table_obj the list table object
962
+	 * @return array                  new filters
963
+	 */
964
+	public function list_table_filters($old_filters, $list_table_obj)
965
+	{
966
+		$filters = array();
967
+		//first month/year filters
968
+		$filters[] = $this->espresso_event_months_dropdown();
969
+		$status    = isset($this->_req_data['status']) ? $this->_req_data['status'] : null;
970
+		//active status dropdown
971
+		if ($status !== 'draft') {
972
+			$filters[] = $this->active_status_dropdown(
973
+				isset($this->_req_data['active_status']) ? $this->_req_data['active_status'] : ''
974
+			);
975
+		}
976
+		//category filter
977
+		$filters[] = $this->category_dropdown();
978
+		return array_merge($old_filters, $filters);
979
+	}
980
+
981
+
982
+	/**
983
+	 * espresso_event_months_dropdown
984
+	 *
985
+	 * @access public
986
+	 * @return string                dropdown listing month/year selections for events.
987
+	 */
988
+	public function espresso_event_months_dropdown()
989
+	{
990
+		// what we need to do is get all PRIMARY datetimes for all events to filter on.
991
+		// Note we need to include any other filters that are set!
992
+		$status = isset($this->_req_data['status']) ? $this->_req_data['status'] : null;
993
+		//categories?
994
+		$category = isset($this->_req_data['EVT_CAT']) && $this->_req_data['EVT_CAT'] > 0
995
+			? $this->_req_data['EVT_CAT']
996
+			: null;
997
+		//active status?
998
+		$active_status = isset($this->_req_data['active_status']) ? $this->_req_data['active_status'] : null;
999
+		$cur_date      = isset($this->_req_data['month_range']) ? $this->_req_data['month_range'] : '';
1000
+		return EEH_Form_Fields::generate_event_months_dropdown($cur_date, $status, $category, $active_status);
1001
+	}
1002
+
1003
+
1004
+	/**
1005
+	 * returns a list of "active" statuses on the event
1006
+	 *
1007
+	 * @param  string $current_value whatever the current active status is
1008
+	 * @return string
1009
+	 */
1010
+	public function active_status_dropdown($current_value = '')
1011
+	{
1012
+		$select_name = 'active_status';
1013
+		$values      = array(
1014
+			'none'     => esc_html__('Show Active/Inactive', 'event_espresso'),
1015
+			'active'   => esc_html__('Active', 'event_espresso'),
1016
+			'upcoming' => esc_html__('Upcoming', 'event_espresso'),
1017
+			'expired'  => esc_html__('Expired', 'event_espresso'),
1018
+			'inactive' => esc_html__('Inactive', 'event_espresso'),
1019
+		);
1020
+		$id          = 'id="espresso-active-status-dropdown-filter"';
1021
+		$class       = 'wide';
1022
+		return EEH_Form_Fields::select_input($select_name, $values, $current_value, $id, $class);
1023
+	}
1024
+
1025
+
1026
+	/**
1027
+	 * output a dropdown of the categories for the category filter on the event admin list table
1028
+	 *
1029
+	 * @access  public
1030
+	 * @return string html
1031
+	 */
1032
+	public function category_dropdown()
1033
+	{
1034
+		$cur_cat = isset($this->_req_data['EVT_CAT']) ? $this->_req_data['EVT_CAT'] : -1;
1035
+		return EEH_Form_Fields::generate_event_category_dropdown($cur_cat);
1036
+	}
1037
+
1038
+
1039
+	/**
1040
+	 * get total number of events today
1041
+	 *
1042
+	 * @access public
1043
+	 * @return int
1044
+	 * @throws EE_Error
1045
+	 */
1046
+	public function total_events_today()
1047
+	{
1048
+		$start = EEM_Datetime::instance()->convert_datetime_for_query(
1049
+			'DTT_EVT_start',
1050
+			date('Y-m-d') . ' 00:00:00',
1051
+			'Y-m-d H:i:s',
1052
+			'UTC'
1053
+		);
1054
+		$end   = EEM_Datetime::instance()->convert_datetime_for_query(
1055
+			'DTT_EVT_start',
1056
+			date('Y-m-d') . ' 23:59:59',
1057
+			'Y-m-d H:i:s',
1058
+			'UTC'
1059
+		);
1060
+		$where = array(
1061
+			'Datetime.DTT_EVT_start' => array('BETWEEN', array($start, $end)),
1062
+		);
1063
+		$count = EEM_Event::instance()->count(array($where, 'caps' => 'read_admin'), 'EVT_ID', true);
1064
+		return $count;
1065
+	}
1066
+
1067
+
1068
+	/**
1069
+	 * get total number of events this month
1070
+	 *
1071
+	 * @access public
1072
+	 * @return int
1073
+	 * @throws EE_Error
1074
+	 */
1075
+	public function total_events_this_month()
1076
+	{
1077
+		//Dates
1078
+		$this_year_r     = date('Y');
1079
+		$this_month_r    = date('m');
1080
+		$days_this_month = date('t');
1081
+		$start           = EEM_Datetime::instance()->convert_datetime_for_query(
1082
+			'DTT_EVT_start',
1083
+			$this_year_r . '-' . $this_month_r . '-01 00:00:00',
1084
+			'Y-m-d H:i:s',
1085
+			'UTC'
1086
+		);
1087
+		$end             = EEM_Datetime::instance()->convert_datetime_for_query(
1088
+			'DTT_EVT_start',
1089
+			$this_year_r . '-' . $this_month_r . '-' . $days_this_month . ' 23:59:59',
1090
+			'Y-m-d H:i:s',
1091
+			'UTC'
1092
+		);
1093
+		$where           = array(
1094
+			'Datetime.DTT_EVT_start' => array('BETWEEN', array($start, $end)),
1095
+		);
1096
+		$count           = EEM_Event::instance()->count(array($where, 'caps' => 'read_admin'), 'EVT_ID', true);
1097
+		return $count;
1098
+	}
1099
+
1100
+
1101
+	/** DEFAULT TICKETS STUFF **/
1102
+
1103
+	/**
1104
+	 * Output default tickets list table view.
1105
+	 */
1106
+	public function _tickets_overview_list_table()
1107
+	{
1108
+		$this->_search_btn_label = esc_html__('Tickets', 'event_espresso');
1109
+		$this->display_admin_list_table_page_with_no_sidebar();
1110
+	}
1111
+
1112
+
1113
+	/**
1114
+	 * @param int  $per_page
1115
+	 * @param bool $count
1116
+	 * @param bool $trashed
1117
+	 * @return \EE_Soft_Delete_Base_Class[]|int
1118
+	 */
1119
+	public function get_default_tickets($per_page = 10, $count = false, $trashed = false)
1120
+	{
1121
+		$orderby = empty($this->_req_data['orderby']) ? 'TKT_name' : $this->_req_data['orderby'];
1122
+		$order   = empty($this->_req_data['order']) ? 'ASC' : $this->_req_data['order'];
1123
+		switch ($orderby) {
1124
+			case 'TKT_name':
1125
+				$orderby = array('TKT_name' => $order);
1126
+				break;
1127
+			case 'TKT_price':
1128
+				$orderby = array('TKT_price' => $order);
1129
+				break;
1130
+			case 'TKT_uses':
1131
+				$orderby = array('TKT_uses' => $order);
1132
+				break;
1133
+			case 'TKT_min':
1134
+				$orderby = array('TKT_min' => $order);
1135
+				break;
1136
+			case 'TKT_max':
1137
+				$orderby = array('TKT_max' => $order);
1138
+				break;
1139
+			case 'TKT_qty':
1140
+				$orderby = array('TKT_qty' => $order);
1141
+				break;
1142
+		}
1143
+		$current_page = isset($this->_req_data['paged']) && ! empty($this->_req_data['paged'])
1144
+			? $this->_req_data['paged']
1145
+			: 1;
1146
+		$per_page     = isset($this->_req_data['perpage']) && ! empty($this->_req_data['perpage'])
1147
+			? $this->_req_data['perpage']
1148
+			: $per_page;
1149
+		$_where       = array(
1150
+			'TKT_is_default' => 1,
1151
+			'TKT_deleted'    => $trashed,
1152
+		);
1153
+		$offset       = ($current_page - 1) * $per_page;
1154
+		$limit        = array($offset, $per_page);
1155
+		if (isset($this->_req_data['s'])) {
1156
+			$sstr         = '%' . $this->_req_data['s'] . '%';
1157
+			$_where['OR'] = array(
1158
+				'TKT_name'        => array('LIKE', $sstr),
1159
+				'TKT_description' => array('LIKE', $sstr),
1160
+			);
1161
+		}
1162
+		$query_params = array(
1163
+			$_where,
1164
+			'order_by' => $orderby,
1165
+			'limit'    => $limit,
1166
+			'group_by' => 'TKT_ID',
1167
+		);
1168
+		if ($count) {
1169
+			return EEM_Ticket::instance()->count_deleted_and_undeleted(array($_where));
1170
+		} else {
1171
+			return EEM_Ticket::instance()->get_all_deleted_and_undeleted($query_params);
1172
+		}
1173
+	}
1174
+
1175
+
1176
+	/**
1177
+	 * @param bool $trash
1178
+	 * @throws EE_Error
1179
+	 */
1180
+	protected function _trash_or_restore_ticket($trash = false)
1181
+	{
1182
+		$success = 1;
1183
+		$TKT     = EEM_Ticket::instance();
1184
+		//checkboxes?
1185
+		if (! empty($this->_req_data['checkbox']) && is_array($this->_req_data['checkbox'])) {
1186
+			//if array has more than one element then success message should be plural
1187
+			$success = count($this->_req_data['checkbox']) > 1 ? 2 : 1;
1188
+			//cycle thru the boxes
1189
+			while (list($TKT_ID, $value) = each($this->_req_data['checkbox'])) {
1190
+				if ($trash) {
1191
+					if (! $TKT->delete_by_ID($TKT_ID)) {
1192
+						$success = 0;
1193
+					}
1194
+				} else {
1195
+					if (! $TKT->restore_by_ID($TKT_ID)) {
1196
+						$success = 0;
1197
+					}
1198
+				}
1199
+			}
1200
+		} else {
1201
+			//grab single id and trash
1202
+			$TKT_ID = absint($this->_req_data['TKT_ID']);
1203
+			if ($trash) {
1204
+				if (! $TKT->delete_by_ID($TKT_ID)) {
1205
+					$success = 0;
1206
+				}
1207
+			} else {
1208
+				if (! $TKT->restore_by_ID($TKT_ID)) {
1209
+					$success = 0;
1210
+				}
1211
+			}
1212
+		}
1213
+		$action_desc = $trash ? 'moved to the trash' : 'restored';
1214
+		$query_args  = array(
1215
+			'action' => 'ticket_list_table',
1216
+			'status' => $trash ? '' : 'trashed',
1217
+		);
1218
+		$this->_redirect_after_action($success, 'Tickets', $action_desc, $query_args);
1219
+	}
1220
+
1221
+
1222
+	/**
1223
+	 * Handles trashing default ticket.
1224
+	 */
1225
+	protected function _delete_ticket()
1226
+	{
1227
+		$success = 1;
1228
+		//checkboxes?
1229
+		if (! empty($this->_req_data['checkbox']) && is_array($this->_req_data['checkbox'])) {
1230
+			//if array has more than one element then success message should be plural
1231
+			$success = count($this->_req_data['checkbox']) > 1 ? 2 : 1;
1232
+			//cycle thru the boxes
1233
+			while (list($TKT_ID, $value) = each($this->_req_data['checkbox'])) {
1234
+				//delete
1235
+				if (! $this->_delete_the_ticket($TKT_ID)) {
1236
+					$success = 0;
1237
+				}
1238
+			}
1239
+		} else {
1240
+			//grab single id and trash
1241
+			$TKT_ID = absint($this->_req_data['TKT_ID']);
1242
+			if (! $this->_delete_the_ticket($TKT_ID)) {
1243
+				$success = 0;
1244
+			}
1245
+		}
1246
+		$action_desc = 'deleted';
1247
+		$query_args  = array(
1248
+			'action' => 'ticket_list_table',
1249
+			'status' => 'trashed',
1250
+		);
1251
+		//fail safe.  If the default ticket count === 1 then we need to redirect to event overview.
1252
+		if (EEM_Ticket::instance()->count_deleted_and_undeleted(
1253
+			array(array('TKT_is_default' => 1)),
1254
+			'TKT_ID',
1255
+			true
1256
+		)
1257
+		) {
1258
+			$query_args = array();
1259
+		}
1260
+		$this->_redirect_after_action($success, 'Tickets', $action_desc, $query_args);
1261
+	}
1262
+
1263
+
1264
+	/**
1265
+	 * @param int $TKT_ID
1266
+	 * @return bool|int
1267
+	 * @throws EE_Error
1268
+	 */
1269
+	protected function _delete_the_ticket($TKT_ID)
1270
+	{
1271
+		$tkt = EEM_Ticket::instance()->get_one_by_ID($TKT_ID);
1272
+		$tkt->_remove_relations('Datetime');
1273
+		//delete all related prices first
1274
+		$tkt->delete_related_permanently('Price');
1275
+		return $tkt->delete_permanently();
1276
+	}
1277 1277
 }
Please login to merge, or discard this patch.