Completed
Branch dev (605279)
by
unknown
15:30 queued 07:59
created
core/admin/EE_Admin_Hooks.core.php 2 patches
Indentation   +763 added lines, -763 removed lines patch added patch discarded remove patch
@@ -14,767 +14,767 @@
 block discarded – undo
14 14
  */
15 15
 abstract class EE_Admin_Hooks extends EE_Base
16 16
 {
17
-    /**
18
-     * we're just going to use this to hold the name of the caller class (child class name)
19
-     *
20
-     * @var string
21
-     */
22
-    public $caller;
23
-
24
-
25
-    /**
26
-     * this is just a flag set automatically to indicate whether we've got an extended hook class running (i.e.
27
-     * espresso_events_Registration_Form_Hooks_Extend extends espresso_events_Registration_Form_Hooks).  This flag is
28
-     * used later to make sure we require the needed files.
29
-     *
30
-     * @var bool
31
-     */
32
-    protected $_extend;
33
-
34
-
35
-    /**
36
-     * child classes MUST set this property so that the page object can be loaded correctly
37
-     *
38
-     * @var string
39
-     */
40
-    protected $_name;
41
-
42
-
43
-    /**
44
-     * This is set by child classes and is an associative array of ajax hooks in the format:
45
-     * array(
46
-     *    'ajax_action_ref' => 'executing_method'; //must be public
47
-     * )
48
-     *
49
-     * @var array
50
-     */
51
-    protected $_ajax_func;
52
-
53
-
54
-    /**
55
-     * This is an array of methods that get executed on a page routes admin_init hook. Use the following format:
56
-     * array(
57
-     *    'page_route' => 'executing_method' //must be public
58
-     * )
59
-     *
60
-     * @var array
61
-     */
62
-    protected $_init_func;
63
-
64
-
65
-    /**
66
-     * This is an array of methods that output metabox content for the given page route.  Use the following format:
67
-     * [
68
-     *      0 => [
69
-     *          'page_route' => 'string_for_page_route',    must correspond to a page route in the class being connected
70
-     *                                                      with (i.e. "edit_event") If this is in an array then the
71
-     *                                                      same params below will be used but the metabox will be
72
-     *                                                      added to each route.
73
-     *          'func' =>  'executing_method',              must be public (i.e. public function executing_method
74
-     *                                                      ($post, $callback_args){} ).
75
-     *                                                      Note if you include callback args in the array then you
76
-     *                                                      need to declare them in the method arguments.
77
-     *          'id' => 'identifier_for_metabox',           so it can be removed by addons
78
-     *                                                      (optional, class will set it automatically)
79
-     *          'priority' => 'default',                    default 'default' (optional)
80
-     *          'label' => esc_html__('Localized Title', 'event_espresso'),
81
-     *          'context' => 'advanced'                     advanced is default (optional),
82
-     *      ]
83
-     *      'callback_args' => array() //any callback args to include (optional)
84
-     * ]
85
-     * Why are we indexing numerically?  Because it's possible there may be more than one metabox per page_route.
86
-     *
87
-     * @var array
88
-     */
89
-    protected $_metaboxes;
90
-
91
-
92
-    /**
93
-     * This is an array of values that indicate any metaboxes we want removed from a given page route.  Usually this is
94
-     * used when caffeinated functionality is replacing decaffeinated functionality.  Use the following format for the
95
-     * array: array(
96
-     *    0 => array(
97
-     *        'page_route' => 'string_for_page_route' //can be string or array of strings that match a page_route(s)
98
-     *        that are in the class being connected with (i.e. 'edit', or 'create_new').
99
-     *        'id' => 'identifier_for_metabox', //what the id is of the metabox being removed
100
-     *        'context' => 'normal', //the context for the metabox being removed (has to match)
101
-     *        'screen' => 'screen_id', //(optional), if not included then this class will attempt to remove the metabox
102
-     *        using the currently loaded screen object->id  however, there may be cases where you have to specify the
103
-     *        id for the screen the metabox is on.
104
-     *    )
105
-     * )
106
-     *
107
-     * @var array
108
-     */
109
-    protected $_remove_metaboxes;
110
-
111
-
112
-    /**
113
-     * This parent class takes care of loading the scripts and styles if the child class has set the properties for
114
-     * them in the following format.  Note, the first array index ('register') is for defining all the registers.  The
115
-     * second array index is for indicating what routes each script/style loads on. array(
116
-     * 'registers' => array(
117
-     *        'script_ref' => array( // if more than one script is to be loaded its best to use the 'dependency'
118
-     *        argument to link scripts together.
119
-     *            'type' => 'js' // 'js' or 'css' (defaults to js).  This tells us what type of wp_function to use
120
-     *            'url' => 'http://urltoscript.css.js',
121
-     *            'depends' => array('jquery'), //an array of dependencies for the scripts. REMEMBER, if a script has
122
-     *            already been registered elsewhere in the system.  You can just use the depends array to make sure it
123
-     *            gets loaded before the one you are setting here.
124
-     *            'footer' => TRUE //defaults to true (styles don't use this parameter)
125
-     *        ),
126
-     *    'enqueues' => array( //this time each key corresponds to the script ref followed by an array of page routes
127
-     *    the script gets enqueued on.
128
-     *        'script_ref' => array('route_one', 'route_two')
129
-     *    ),
130
-     *    'localize' => array( //this allows you to set a localized object.  Indicate which script the object is being
131
-     *    attached to and then include an array indexed by the name of the object and the array of key/value pairs for
132
-     *    the object.
133
-     *        'scrip_ref' => array(
134
-     *            'NAME_OF_JS_OBJECT' => array(
135
-     *                'translate_ref' => esc_html__('localized_string', 'event_espresso'),
136
-     *                'some_data' => 5
137
-     *            )
138
-     *        )
139
-     *    )
140
-     * )
141
-     *
142
-     * @var array
143
-     */
144
-    protected $_scripts_styles;
145
-
146
-
147
-    /**
148
-     * This is a property that will contain the current route.
149
-     *
150
-     * @var string;
151
-     */
152
-    protected $_current_route;
153
-
154
-
155
-    /**
156
-     * this optional property can be set by child classes to override the priority for the automatic action/filter hook
157
-     * loading in the `_load_routed_hooks()` method.  Please follow this format: array(
158
-     *    'wp_hook_reference' => 1
159
-     *    )
160
-     * )
161
-     *
162
-     * @var array
163
-     */
164
-    protected $_wp_action_filters_priority;
165
-
166
-
167
-    /**
168
-     * This just holds a merged array of the request vars
169
-     *
170
-     * @var array
171
-     */
172
-    protected $_req_data;
173
-
174
-    /**
175
-     * @var array
176
-     */
177
-    protected $_scripts;
178
-
179
-    /**
180
-     * @var array
181
-     */
182
-    protected $_styles;
183
-
184
-    /**
185
-     * This just holds an instance of the page object for this hook
186
-     *
187
-     * @var EE_Admin_Page
188
-     */
189
-    protected $_page_object;
190
-
191
-
192
-    /**
193
-     * This holds the EE_Admin_Page object from the calling admin page that this object hooks into.
194
-     *
195
-     * @var EE_Admin_Page|EE_Admin_Page_CPT
196
-     */
197
-    protected $_adminpage_obj;
198
-
199
-
200
-    /**
201
-     * Holds EE_Registry object
202
-     *
203
-     * @var EE_Registry
204
-     */
205
-    protected $EE = null;
206
-
207
-    /**
208
-     * @var RequestInterface
209
-     */
210
-    protected $request;
211
-
212
-
213
-    /**
214
-     * constructor
215
-     *
216
-     * @param EE_Admin_Page $admin_page
217
-     * @throws EE_Error
218
-     */
219
-    public function __construct(EE_Admin_Page $admin_page)
220
-    {
221
-        $this->_adminpage_obj = $admin_page;
222
-        $this->request        = LoaderFactory::getLoader()->getShared(RequestInterface::class);
223
-        $this->_req_data      = $this->request->requestParams();
224
-        $page = $this->request->getRequestParam('page');
225
-        $current_page = $this->request->getRequestParam('current_page', $page);
226
-        // first let's verify we're on the right page
227
-        if ($current_page !== $this->_adminpage_obj->page_slug) {
228
-            return;
229
-        }
230
-        $this->_set_defaults();
231
-        $this->_set_hooks_properties();
232
-        // get out nothing more to be done here.
233
-        // allow for extends to modify properties
234
-        if (method_exists($this, '_extend_properties')) {
235
-            $this->_extend_properties();
236
-        }
237
-        $this->_set_page_object();
238
-        $this->_init_hooks();
239
-        $this->_load_custom_methods();
240
-        $this->_load_routed_hooks();
241
-        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts_styles']);
242
-        add_action('admin_enqueue_scripts', [$this, 'add_metaboxes'], 20);
243
-        add_action('admin_enqueue_scripts', [$this, 'remove_metaboxes'], 15);
244
-        $this->_ajax_hooks();
245
-    }
246
-
247
-
248
-    /**
249
-     * used by child classes to set the following properties:
250
-     * $_ajax_func (optional)
251
-     * $_init_func (optional)
252
-     * $_metaboxes (optional)
253
-     * $_scripts (optional)
254
-     * $_styles (optional)
255
-     * $_name (required)
256
-     * Also in this method will be registered any scripts or styles loaded on the targeted page (as indicated in the
257
-     * _scripts/_styles properties) Also children should place in this method any filters/actions that have to happen
258
-     * really early on page load (just after admin_init) if they want to have them registered for handling early.
259
-     *
260
-     * @abstract
261
-     * @return void
262
-     */
263
-    abstract protected function _set_hooks_properties();
264
-
265
-
266
-    /**
267
-     * The hooks for enqueue_scripts and enqueue_styles will be run in here.  Child classes need to define their
268
-     * scripts and styles in the relevant $_scripts and $_styles properties.  Child classes must have also already
269
-     * registered the scripts and styles using wp_register_script and wp_register_style functions.
270
-     *
271
-     * @return void
272
-     * @throws EE_Error
273
-     */
274
-    public function enqueue_scripts_styles()
275
-    {
276
-        if (! empty($this->_scripts_styles)) {
277
-            // first let's do all the registrations
278
-            if (! isset($this->_scripts_styles['registers'])) {
279
-                $msg[] = esc_html__(
280
-                    'There is no "registers" index in the <code>$this->_scripts_styles</code> property.',
281
-                    'event_espresso'
282
-                );
283
-                $msg[] = sprintf(
284
-                    esc_html__(
285
-                        'Make sure you read the phpdoc comments above the definition of the $_scripts_styles property in the <code>EE_Admin_Hooks</code> class and modify according in the %s child',
286
-                        'event_espresso'
287
-                    ),
288
-                    '<strong>' . $this->caller . '</strong>'
289
-                );
290
-                throw new EE_Error(implode('||', $msg));
291
-            }
292
-            $defaults = [
293
-                'type'    => 'js',
294
-                'url'     => '',
295
-                'depends' => [],
296
-                'version' => EVENT_ESPRESSO_VERSION,
297
-                'footer'  => true,
298
-            ];
299
-            foreach ($this->_scripts_styles['registers'] as $ref => $details) {
300
-                $details = wp_parse_args($details, $defaults);
301
-                $type    = $details['type'];
302
-                $url     = $details['url'];
303
-                $depends = $details['depends'];
304
-                $version = $details['version'];
305
-                $footer  = $details['footer'];
306
-                // let's make sure that we set the 'registers' type if it's not set!
307
-                // We need it later to determine which enqueue we do
308
-                $this->_scripts_styles['registers'][ $ref ]['type'] = $type;
309
-                // let's make sure we're not missing any REQUIRED parameters
310
-                if (empty($url)) {
311
-                    $msg[] = sprintf(
312
-                        esc_html__('Missing the url for the requested %s', 'event_espresso'),
313
-                        $type == 'js' ? 'script' : 'stylesheet'
314
-                    );
315
-                    $msg[] = sprintf(
316
-                        esc_html__(
317
-                            'Doublecheck your <code>$this->_scripts_styles</code> array in %s and make sure that there is a "url" set for the %s ref',
318
-                            'event_espresso'
319
-                        ),
320
-                        '<strong>' . $this->caller . '</strong>',
321
-                        $ref
322
-                    );
323
-                    throw new EE_Error(implode('||', $msg));
324
-                }
325
-                // made it here so let's do the appropriate registration
326
-                $type == 'js'
327
-                    ? wp_register_script($ref, $url, $depends, $version, $footer)
328
-                    : wp_register_style(
329
-                        $ref,
330
-                        $url,
331
-                        $depends,
332
-                        $version
333
-                    );
334
-            }
335
-            // k now let's do the enqueues
336
-            if (! isset($this->_scripts_styles['enqueues'])) {
337
-                return;
338
-            }  //not sure if we should throw an error here or not.
339
-
340
-            foreach ($this->_scripts_styles['enqueues'] as $ref => $routes) {
341
-                // make sure $routes is an array
342
-                $routes = (array) $routes;
343
-                if (in_array($this->_current_route, $routes)) {
344
-                    $this->_scripts_styles['registers'][ $ref ]['type'] == 'js' ? wp_enqueue_script($ref)
345
-                        : wp_enqueue_style($ref);
346
-                    // if we have a localization for the script let's do that too.
347
-                    if (isset($this->_scripts_styles['localize'][ $ref ])) {
348
-                        foreach ($this->_scripts_styles['localize'][ $ref ] as $object_name => $indexes) {
349
-                            wp_localize_script(
350
-                                $ref,
351
-                                $object_name,
352
-                                $this->_scripts_styles['localize'][ $ref ][ $object_name ]
353
-                            );
354
-                        }
355
-                    }
356
-                }
357
-            }
358
-            // let's do the deregisters
359
-            if (! isset($this->_scripts_styles['deregisters'])) {
360
-                return;
361
-            }
362
-            foreach ($this->_scripts_styles['deregisters'] as $ref => $details) {
363
-                $defaults = ['type' => 'js'];
364
-                $details  = wp_parse_args($details, $defaults);
365
-                $details['type'] === 'js' ? wp_deregister_script($ref) : wp_deregister_style($ref);
366
-            }
367
-        }
368
-    }
369
-
370
-
371
-    /**
372
-     * just set the defaults for the hooks properties.
373
-     *
374
-     * @return void
375
-     */
376
-    private function _set_defaults()
377
-    {
378
-        $this->_ajax_func                  = [];
379
-        $this->_init_func                  = [];
380
-        $this->_metaboxes                  = [];
381
-        $this->_scripts                    = [];
382
-        $this->_styles                     = [];
383
-        $this->_wp_action_filters_priority = [];
384
-        $this->_current_route              = $this->getCurrentRoute();
385
-        $this->caller                      = get_class($this);
386
-        $this->_extend                     = (bool) stripos($this->caller, 'Extend');
387
-    }
388
-
389
-
390
-    /**
391
-     * A helper for determining the current route.
392
-     *
393
-     * @return string
394
-     */
395
-    private function getCurrentRoute()
396
-    {
397
-        $action = $this->request->getRequestParam('action');
398
-        // list tables do something else with 'action' for bulk actions.
399
-        $action = $action !== '-1' && $action !== '' ? $action : 'default';
400
-        $route  = $this->request->getRequestParam('route');
401
-        // we set a 'route' variable in some cases where action is being used by something else.
402
-        return $action === 'default' && $route !== '' ? $route : $action;
403
-    }
404
-
405
-
406
-    /**
407
-     * this sets the _page_object property
408
-     *
409
-     * @return void
410
-     * @throws EE_Error
411
-     */
412
-    protected function _set_page_object()
413
-    {
414
-        if ($this->_page_object instanceof EE_Admin_Page) {
415
-            return;
416
-        }
417
-        // first make sure $this->_name is set
418
-        if (empty($this->_name)) {
419
-            $msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
420
-            $msg[] = sprintf(
421
-                esc_html__("This is because the %s child class has not set the '_name' property", 'event_espresso'),
422
-                $this->caller
423
-            );
424
-            throw new EE_Error(implode('||', $msg));
425
-        }
426
-        // change "the_message" to "the message"
427
-        $class_name = str_replace('_', ' ', $this->_name);
428
-        // change "the message" to "The_Message_Admin_Page"
429
-        $class_name = str_replace(' ', '_', ucwords($class_name)) . '_Admin_Page';
430
-        // first default file (if exists)
431
-        $decaf_file = EE_ADMIN_PAGES . $this->_name . '/' . $class_name . '.core.php';
432
-        if (is_readable($decaf_file)) {
433
-            require_once($decaf_file);
434
-        }
435
-        // now we have to do require for extended file (if needed)
436
-        if ($this->_extend) {
437
-            require_once(EE_CORE_CAF_ADMIN_EXTEND . $this->_name . '/Extend_' . $class_name . '.core.php');
438
-            // and extend the class name as well
439
-            $class_name = 'Extend_' . $class_name;
440
-        }
441
-        // let's make sure the class exists
442
-        if (! class_exists($class_name)) {
443
-            $msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
444
-            $msg[] = sprintf(
445
-                esc_html__(
446
-                    'The class name that was given is %s. Check the spelling and make sure its correct, also there needs to be an autoloader setup for the class',
447
-                    'event_espresso'
448
-                ),
449
-                $class_name
450
-            );
451
-            throw new EE_Error(implode('||', $msg));
452
-        }
453
-        $this->_page_object = LoaderFactory::getLoader()->getShared($class_name, [false]);
454
-        $this->_page_object->initializePage();
455
-    }
456
-
457
-
458
-    /**
459
-     * Child "hook" classes can declare any methods that they want executed when a specific page route is loaded.  The
460
-     * advantage of this is when doing things like running our own db interactions on saves etc.  Remember that
461
-     * $this->_req_data (all the _POST and _GET data) is available to your methods.
462
-     *
463
-     * @return void
464
-     */
465
-    private function _load_custom_methods()
466
-    {
467
-        /**
468
-         * method cannot be named 'default' (@see http://us3.php
469
-         * .net/manual/en/reserved.keywords.php) so need to
470
-         * handle routes that are "default"
471
-         *
472
-         * @since 4.3.0
473
-         */
474
-        $method_callback = $this->_current_route == 'default' ? 'default_callback' : $this->_current_route;
475
-        // these run before the Admin_Page route executes.
476
-        if (method_exists($this, $method_callback)) {
477
-            call_user_func([$this, $method_callback]);
478
-        }
479
-        // these run via the _redirect_after_action method in EE_Admin_Page which usually happens after non_UI methods in EE_Admin_Page classes.  There are two redirect actions, the first fires before $query_args might be manipulated by "save and close" actions and the seond fires right before the actual redirect happens.
480
-        // first the actions
481
-        // note that these action hooks will have the $query_args value available.
482
-        $admin_class_name = get_class($this->_adminpage_obj);
483
-        if (method_exists($this, '_redirect_action_early_' . $this->_current_route)) {
484
-            add_action(
485
-                'AHEE__'
486
-                . $admin_class_name
487
-                . '___redirect_after_action__before_redirect_modification_'
488
-                . $this->_current_route,
489
-                [$this, '_redirect_action_early_' . $this->_current_route],
490
-                10
491
-            );
492
-        }
493
-        if (method_exists($this, '_redirect_action_' . $this->_current_route)) {
494
-            add_action(
495
-                'AHEE_redirect_' . $admin_class_name . $this->_current_route,
496
-                [$this, '_redirect_action_' . $this->_current_route],
497
-                10
498
-            );
499
-        }
500
-        // let's hook into the _redirect itself and allow for changing where the user goes after redirect.  This will have $query_args and $redirect_url available.
501
-        if (method_exists($this, '_redirect_filter_' . $this->_current_route)) {
502
-            add_filter(
503
-                'FHEE_redirect_' . $admin_class_name . $this->_current_route,
504
-                [$this, '_redirect_filter_' . $this->_current_route],
505
-                10,
506
-                2
507
-            );
508
-        }
509
-    }
510
-
511
-
512
-    /**
513
-     * This method will search for a corresponding method with a name matching the route and the wp_hook to run.  This
514
-     * allows child hook classes to target hooking into a specific wp action or filter hook ONLY on a certain route.
515
-     * just remember, methods MUST be public Future hooks should be added in here to be access by child classes.
516
-     *
517
-     * @return void
518
-     */
519
-    private function _load_routed_hooks()
520
-    {
521
-
522
-        // this array provides the hook action names that will be referenced.  Key is the action. Value is an array with the type (action or filter) and the number of parameters for the hook.  We'll default all priorities for automatic hooks to 10.
523
-        $hook_filter_array = [
524
-            'admin_footer'                                                                            => [
525
-                'type'     => 'action',
526
-                'argnum'   => 1,
527
-                'priority' => 10,
528
-            ],
529
-            'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug . '_' . $this->_current_route => [
530
-                'type'     => 'filter',
531
-                'argnum'   => 1,
532
-                'priority' => 10,
533
-            ],
534
-            'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug                               => [
535
-                'type'     => 'filter',
536
-                'argnum'   => 1,
537
-                'priority' => 10,
538
-            ],
539
-            'FHEE_list_table_views'                                                                   => [
540
-                'type'     => 'filter',
541
-                'argnum'   => 1,
542
-                'priority' => 10,
543
-            ],
544
-            'AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes'                              => [
545
-                'type'     => 'action',
546
-                'argnum'   => 1,
547
-                'priority' => 10,
548
-            ],
549
-        ];
550
-        foreach ($hook_filter_array as $hook => $args) {
551
-            if (method_exists($this, $this->_current_route . '_' . $hook)) {
552
-                if (isset($this->_wp_action_filters_priority[ $hook ])) {
553
-                    $args['priority'] = $this->_wp_action_filters_priority[ $hook ];
554
-                }
555
-                if ($args['type'] == 'action') {
556
-                    add_action(
557
-                        $hook,
558
-                        [$this, $this->_current_route . '_' . $hook],
559
-                        $args['priority'],
560
-                        $args['argnum']
561
-                    );
562
-                } else {
563
-                    add_filter(
564
-                        $hook,
565
-                        [$this, $this->_current_route . '_' . $hook],
566
-                        $args['priority'],
567
-                        $args['argnum']
568
-                    );
569
-                }
570
-            }
571
-        }
572
-    }
573
-
574
-
575
-    /**
576
-     * Loop throught the $_ajax_func array and add_actions for the array.
577
-     *
578
-     * @return void
579
-     * @throws EE_Error
580
-     */
581
-    private function _ajax_hooks()
582
-    {
583
-        if (empty($this->_ajax_func)) {
584
-            return;
585
-        } //get out there's nothing to take care of.
586
-        foreach ($this->_ajax_func as $action => $method) {
587
-            // make sure method exists
588
-            if (! method_exists($this, $method)) {
589
-                $msg[] = esc_html__(
590
-                    'There is no corresponding method for the hook labeled in the _ajax_func array',
591
-                    'event_espresso'
592
-                ) . '<br />';
593
-                $msg[] = sprintf(
594
-                    esc_html__(
595
-                        'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
596
-                        'event_espresso'
597
-                    ),
598
-                    $method,
599
-                    $this->caller
600
-                );
601
-                throw new EE_Error(implode('||', $msg));
602
-            }
603
-            add_action('wp_ajax_' . $action, [$this, $method]);
604
-        }
605
-    }
606
-
607
-
608
-    /**
609
-     * Loop throught the $_init_func array and add_actions for the array.
610
-     *
611
-     * @return void
612
-     * @throws EE_Error
613
-     */
614
-    protected function _init_hooks()
615
-    {
616
-        if (empty($this->_init_func)) {
617
-            return;
618
-        }
619
-        // get out there's nothing to take care of.
620
-        // We need to determine what page_route we are on!
621
-        foreach ($this->_init_func as $route => $method) {
622
-            // make sure method exists
623
-            if (! method_exists($this, $method)) {
624
-                $msg[] = esc_html__(
625
-                    'There is no corresponding method for the hook labeled in the _init_func array',
626
-                    'event_espresso'
627
-                ) . '<br />';
628
-                $msg[] = sprintf(
629
-                    esc_html__(
630
-                        'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
631
-                        'event_espresso'
632
-                    ),
633
-                    $method,
634
-                    $this->caller
635
-                );
636
-                throw new EE_Error(implode('||', $msg));
637
-            }
638
-            if ($route == $this->_current_route) {
639
-                add_action('admin_init', [$this, $method]);
640
-            }
641
-        }
642
-    }
643
-
644
-
645
-    /**
646
-     * Loop through the _metaboxes property and add_metaboxes accordingly
647
-     * //todo we could eventually make this a config component class (i.e. new EE_Metabox);
648
-     *
649
-     * @return void
650
-     * @throws EE_Error
651
-     */
652
-    public function add_metaboxes()
653
-    {
654
-        if (empty($this->_metaboxes)) {
655
-            return;
656
-        } //get out we don't have any metaboxes to set for this connection
657
-        $this->_handle_metabox_array($this->_metaboxes);
658
-    }
659
-
660
-
661
-    /**
662
-     * @param array $boxes
663
-     * @param bool  $add
664
-     * @throws EE_Error
665
-     */
666
-    private function _handle_metabox_array(array $boxes, $add = true)
667
-    {
668
-        foreach ($boxes as $box) {
669
-            if (! isset($box['page_route'])) {
670
-                continue;
671
-            }
672
-            // we don't have a valid array
673
-            // let's make sure $box['page_route'] is an array so the "foreach" will work.
674
-            $box['page_route'] = (array) $box['page_route'];
675
-            foreach ($box['page_route'] as $route) {
676
-                if ($route != $this->_current_route) {
677
-                    continue;
678
-                } //get out we only add metaboxes for set route.
679
-                if ($add) {
680
-                    $this->_add_metabox($box);
681
-                } else {
682
-                    $this->_remove_metabox($box);
683
-                }
684
-            }
685
-        }
686
-    }
687
-
688
-
689
-    /**
690
-     * Loop through the _remove_metaboxes property and remove metaboxes accordingly.
691
-     *
692
-     * @return void
693
-     * @throws EE_Error
694
-     */
695
-    public function remove_metaboxes()
696
-    {
697
-        if (empty($this->_remove_metaboxes)) {
698
-            return;
699
-        } //get out there are no metaboxes to remove
700
-        $this->_handle_metabox_array($this->_remove_metaboxes, false);
701
-    }
702
-
703
-
704
-    /**
705
-     * This just handles adding a metabox
706
-     *
707
-     * @param array $args an array of args that have been set for this metabox by the child class
708
-     * @throws EE_Error
709
-     */
710
-    private function _add_metabox($args)
711
-    {
712
-        $current_screen = get_current_screen();
713
-        $screen_id      = is_object($current_screen) ? $current_screen->id : null;
714
-        $callback       = $args['func'] ?? 'some_invalid_callback';
715
-        $callback_function = is_array($callback) ? end($callback) : $callback;
716
-        // set defaults
717
-        $defaults      = [
718
-            'callback_args' => [],
719
-            'context'       => 'advanced',
720
-            'func'          => $callback,
721
-            'id'            => $this->caller . '_' . $callback_function . '_metabox',
722
-            'label'         => $this->caller,
723
-            'page'          => isset($args['page']) ? $args['page'] : $screen_id,
724
-            'priority'      => 'default',
725
-        ];
726
-        $args          = wp_parse_args($args, $defaults);
727
-        $callback_args = $args['callback_args'];
728
-        $context       = $args['context'];
729
-        $id            = $args['id'];
730
-        $label         = $args['label'];
731
-        $page          = $args['page'];
732
-        $priority      = $args['priority'];
733
-        // make sure method exists
734
-        if (! method_exists($this, $callback_function)) {
735
-            $msg[] =
736
-                esc_html__('There is no corresponding method to display the metabox content', 'event_espresso')
737
-                . '<br />';
738
-            $msg[] = sprintf(
739
-                esc_html__(
740
-                    'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
741
-                    'event_espresso'
742
-                ),
743
-                $callback_function,
744
-                $this->caller
745
-            );
746
-            throw new EE_Error(implode('||', $msg));
747
-        }
748
-        // everything checks out so let's add the metabox
749
-        add_meta_box($id, $label, [$this, $callback_function], $page, $context, $priority, $callback_args);
750
-        add_filter(
751
-            "postbox_classes_{$page}_{$id}",
752
-            function ($classes) {
753
-                array_push($classes, 'ee-admin-container');
754
-                return $classes;
755
-            }
756
-        );
757
-    }
758
-
759
-
760
-    private function _remove_metabox($args)
761
-    {
762
-        $current_screen = get_current_screen();
763
-        $screen_id      = is_object($current_screen) ? $current_screen->id : null;
764
-        $func           = isset($args['func']) ? $args['func'] : 'some_invalid_callback';
765
-        // set defaults
766
-        $defaults = [
767
-            'context' => 'default',
768
-            'id'      => isset($args['id'])
769
-                ? $args['id']
770
-                : $this->_current_route . '_' . $this->caller . '_' . $func . '_metabox',
771
-            'screen'  => isset($args['screen']) ? $args['screen'] : $screen_id,
772
-        ];
773
-        $args     = wp_parse_args($args, $defaults);
774
-        $context  = $args['context'];
775
-        $id       = $args['id'];
776
-        $screen   = $args['screen'];
777
-        // everything checks out so lets remove the box!
778
-        remove_meta_box($id, $screen, $context);
779
-    }
17
+	/**
18
+	 * we're just going to use this to hold the name of the caller class (child class name)
19
+	 *
20
+	 * @var string
21
+	 */
22
+	public $caller;
23
+
24
+
25
+	/**
26
+	 * this is just a flag set automatically to indicate whether we've got an extended hook class running (i.e.
27
+	 * espresso_events_Registration_Form_Hooks_Extend extends espresso_events_Registration_Form_Hooks).  This flag is
28
+	 * used later to make sure we require the needed files.
29
+	 *
30
+	 * @var bool
31
+	 */
32
+	protected $_extend;
33
+
34
+
35
+	/**
36
+	 * child classes MUST set this property so that the page object can be loaded correctly
37
+	 *
38
+	 * @var string
39
+	 */
40
+	protected $_name;
41
+
42
+
43
+	/**
44
+	 * This is set by child classes and is an associative array of ajax hooks in the format:
45
+	 * array(
46
+	 *    'ajax_action_ref' => 'executing_method'; //must be public
47
+	 * )
48
+	 *
49
+	 * @var array
50
+	 */
51
+	protected $_ajax_func;
52
+
53
+
54
+	/**
55
+	 * This is an array of methods that get executed on a page routes admin_init hook. Use the following format:
56
+	 * array(
57
+	 *    'page_route' => 'executing_method' //must be public
58
+	 * )
59
+	 *
60
+	 * @var array
61
+	 */
62
+	protected $_init_func;
63
+
64
+
65
+	/**
66
+	 * This is an array of methods that output metabox content for the given page route.  Use the following format:
67
+	 * [
68
+	 *      0 => [
69
+	 *          'page_route' => 'string_for_page_route',    must correspond to a page route in the class being connected
70
+	 *                                                      with (i.e. "edit_event") If this is in an array then the
71
+	 *                                                      same params below will be used but the metabox will be
72
+	 *                                                      added to each route.
73
+	 *          'func' =>  'executing_method',              must be public (i.e. public function executing_method
74
+	 *                                                      ($post, $callback_args){} ).
75
+	 *                                                      Note if you include callback args in the array then you
76
+	 *                                                      need to declare them in the method arguments.
77
+	 *          'id' => 'identifier_for_metabox',           so it can be removed by addons
78
+	 *                                                      (optional, class will set it automatically)
79
+	 *          'priority' => 'default',                    default 'default' (optional)
80
+	 *          'label' => esc_html__('Localized Title', 'event_espresso'),
81
+	 *          'context' => 'advanced'                     advanced is default (optional),
82
+	 *      ]
83
+	 *      'callback_args' => array() //any callback args to include (optional)
84
+	 * ]
85
+	 * Why are we indexing numerically?  Because it's possible there may be more than one metabox per page_route.
86
+	 *
87
+	 * @var array
88
+	 */
89
+	protected $_metaboxes;
90
+
91
+
92
+	/**
93
+	 * This is an array of values that indicate any metaboxes we want removed from a given page route.  Usually this is
94
+	 * used when caffeinated functionality is replacing decaffeinated functionality.  Use the following format for the
95
+	 * array: array(
96
+	 *    0 => array(
97
+	 *        'page_route' => 'string_for_page_route' //can be string or array of strings that match a page_route(s)
98
+	 *        that are in the class being connected with (i.e. 'edit', or 'create_new').
99
+	 *        'id' => 'identifier_for_metabox', //what the id is of the metabox being removed
100
+	 *        'context' => 'normal', //the context for the metabox being removed (has to match)
101
+	 *        'screen' => 'screen_id', //(optional), if not included then this class will attempt to remove the metabox
102
+	 *        using the currently loaded screen object->id  however, there may be cases where you have to specify the
103
+	 *        id for the screen the metabox is on.
104
+	 *    )
105
+	 * )
106
+	 *
107
+	 * @var array
108
+	 */
109
+	protected $_remove_metaboxes;
110
+
111
+
112
+	/**
113
+	 * This parent class takes care of loading the scripts and styles if the child class has set the properties for
114
+	 * them in the following format.  Note, the first array index ('register') is for defining all the registers.  The
115
+	 * second array index is for indicating what routes each script/style loads on. array(
116
+	 * 'registers' => array(
117
+	 *        'script_ref' => array( // if more than one script is to be loaded its best to use the 'dependency'
118
+	 *        argument to link scripts together.
119
+	 *            'type' => 'js' // 'js' or 'css' (defaults to js).  This tells us what type of wp_function to use
120
+	 *            'url' => 'http://urltoscript.css.js',
121
+	 *            'depends' => array('jquery'), //an array of dependencies for the scripts. REMEMBER, if a script has
122
+	 *            already been registered elsewhere in the system.  You can just use the depends array to make sure it
123
+	 *            gets loaded before the one you are setting here.
124
+	 *            'footer' => TRUE //defaults to true (styles don't use this parameter)
125
+	 *        ),
126
+	 *    'enqueues' => array( //this time each key corresponds to the script ref followed by an array of page routes
127
+	 *    the script gets enqueued on.
128
+	 *        'script_ref' => array('route_one', 'route_two')
129
+	 *    ),
130
+	 *    'localize' => array( //this allows you to set a localized object.  Indicate which script the object is being
131
+	 *    attached to and then include an array indexed by the name of the object and the array of key/value pairs for
132
+	 *    the object.
133
+	 *        'scrip_ref' => array(
134
+	 *            'NAME_OF_JS_OBJECT' => array(
135
+	 *                'translate_ref' => esc_html__('localized_string', 'event_espresso'),
136
+	 *                'some_data' => 5
137
+	 *            )
138
+	 *        )
139
+	 *    )
140
+	 * )
141
+	 *
142
+	 * @var array
143
+	 */
144
+	protected $_scripts_styles;
145
+
146
+
147
+	/**
148
+	 * This is a property that will contain the current route.
149
+	 *
150
+	 * @var string;
151
+	 */
152
+	protected $_current_route;
153
+
154
+
155
+	/**
156
+	 * this optional property can be set by child classes to override the priority for the automatic action/filter hook
157
+	 * loading in the `_load_routed_hooks()` method.  Please follow this format: array(
158
+	 *    'wp_hook_reference' => 1
159
+	 *    )
160
+	 * )
161
+	 *
162
+	 * @var array
163
+	 */
164
+	protected $_wp_action_filters_priority;
165
+
166
+
167
+	/**
168
+	 * This just holds a merged array of the request vars
169
+	 *
170
+	 * @var array
171
+	 */
172
+	protected $_req_data;
173
+
174
+	/**
175
+	 * @var array
176
+	 */
177
+	protected $_scripts;
178
+
179
+	/**
180
+	 * @var array
181
+	 */
182
+	protected $_styles;
183
+
184
+	/**
185
+	 * This just holds an instance of the page object for this hook
186
+	 *
187
+	 * @var EE_Admin_Page
188
+	 */
189
+	protected $_page_object;
190
+
191
+
192
+	/**
193
+	 * This holds the EE_Admin_Page object from the calling admin page that this object hooks into.
194
+	 *
195
+	 * @var EE_Admin_Page|EE_Admin_Page_CPT
196
+	 */
197
+	protected $_adminpage_obj;
198
+
199
+
200
+	/**
201
+	 * Holds EE_Registry object
202
+	 *
203
+	 * @var EE_Registry
204
+	 */
205
+	protected $EE = null;
206
+
207
+	/**
208
+	 * @var RequestInterface
209
+	 */
210
+	protected $request;
211
+
212
+
213
+	/**
214
+	 * constructor
215
+	 *
216
+	 * @param EE_Admin_Page $admin_page
217
+	 * @throws EE_Error
218
+	 */
219
+	public function __construct(EE_Admin_Page $admin_page)
220
+	{
221
+		$this->_adminpage_obj = $admin_page;
222
+		$this->request        = LoaderFactory::getLoader()->getShared(RequestInterface::class);
223
+		$this->_req_data      = $this->request->requestParams();
224
+		$page = $this->request->getRequestParam('page');
225
+		$current_page = $this->request->getRequestParam('current_page', $page);
226
+		// first let's verify we're on the right page
227
+		if ($current_page !== $this->_adminpage_obj->page_slug) {
228
+			return;
229
+		}
230
+		$this->_set_defaults();
231
+		$this->_set_hooks_properties();
232
+		// get out nothing more to be done here.
233
+		// allow for extends to modify properties
234
+		if (method_exists($this, '_extend_properties')) {
235
+			$this->_extend_properties();
236
+		}
237
+		$this->_set_page_object();
238
+		$this->_init_hooks();
239
+		$this->_load_custom_methods();
240
+		$this->_load_routed_hooks();
241
+		add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts_styles']);
242
+		add_action('admin_enqueue_scripts', [$this, 'add_metaboxes'], 20);
243
+		add_action('admin_enqueue_scripts', [$this, 'remove_metaboxes'], 15);
244
+		$this->_ajax_hooks();
245
+	}
246
+
247
+
248
+	/**
249
+	 * used by child classes to set the following properties:
250
+	 * $_ajax_func (optional)
251
+	 * $_init_func (optional)
252
+	 * $_metaboxes (optional)
253
+	 * $_scripts (optional)
254
+	 * $_styles (optional)
255
+	 * $_name (required)
256
+	 * Also in this method will be registered any scripts or styles loaded on the targeted page (as indicated in the
257
+	 * _scripts/_styles properties) Also children should place in this method any filters/actions that have to happen
258
+	 * really early on page load (just after admin_init) if they want to have them registered for handling early.
259
+	 *
260
+	 * @abstract
261
+	 * @return void
262
+	 */
263
+	abstract protected function _set_hooks_properties();
264
+
265
+
266
+	/**
267
+	 * The hooks for enqueue_scripts and enqueue_styles will be run in here.  Child classes need to define their
268
+	 * scripts and styles in the relevant $_scripts and $_styles properties.  Child classes must have also already
269
+	 * registered the scripts and styles using wp_register_script and wp_register_style functions.
270
+	 *
271
+	 * @return void
272
+	 * @throws EE_Error
273
+	 */
274
+	public function enqueue_scripts_styles()
275
+	{
276
+		if (! empty($this->_scripts_styles)) {
277
+			// first let's do all the registrations
278
+			if (! isset($this->_scripts_styles['registers'])) {
279
+				$msg[] = esc_html__(
280
+					'There is no "registers" index in the <code>$this->_scripts_styles</code> property.',
281
+					'event_espresso'
282
+				);
283
+				$msg[] = sprintf(
284
+					esc_html__(
285
+						'Make sure you read the phpdoc comments above the definition of the $_scripts_styles property in the <code>EE_Admin_Hooks</code> class and modify according in the %s child',
286
+						'event_espresso'
287
+					),
288
+					'<strong>' . $this->caller . '</strong>'
289
+				);
290
+				throw new EE_Error(implode('||', $msg));
291
+			}
292
+			$defaults = [
293
+				'type'    => 'js',
294
+				'url'     => '',
295
+				'depends' => [],
296
+				'version' => EVENT_ESPRESSO_VERSION,
297
+				'footer'  => true,
298
+			];
299
+			foreach ($this->_scripts_styles['registers'] as $ref => $details) {
300
+				$details = wp_parse_args($details, $defaults);
301
+				$type    = $details['type'];
302
+				$url     = $details['url'];
303
+				$depends = $details['depends'];
304
+				$version = $details['version'];
305
+				$footer  = $details['footer'];
306
+				// let's make sure that we set the 'registers' type if it's not set!
307
+				// We need it later to determine which enqueue we do
308
+				$this->_scripts_styles['registers'][ $ref ]['type'] = $type;
309
+				// let's make sure we're not missing any REQUIRED parameters
310
+				if (empty($url)) {
311
+					$msg[] = sprintf(
312
+						esc_html__('Missing the url for the requested %s', 'event_espresso'),
313
+						$type == 'js' ? 'script' : 'stylesheet'
314
+					);
315
+					$msg[] = sprintf(
316
+						esc_html__(
317
+							'Doublecheck your <code>$this->_scripts_styles</code> array in %s and make sure that there is a "url" set for the %s ref',
318
+							'event_espresso'
319
+						),
320
+						'<strong>' . $this->caller . '</strong>',
321
+						$ref
322
+					);
323
+					throw new EE_Error(implode('||', $msg));
324
+				}
325
+				// made it here so let's do the appropriate registration
326
+				$type == 'js'
327
+					? wp_register_script($ref, $url, $depends, $version, $footer)
328
+					: wp_register_style(
329
+						$ref,
330
+						$url,
331
+						$depends,
332
+						$version
333
+					);
334
+			}
335
+			// k now let's do the enqueues
336
+			if (! isset($this->_scripts_styles['enqueues'])) {
337
+				return;
338
+			}  //not sure if we should throw an error here or not.
339
+
340
+			foreach ($this->_scripts_styles['enqueues'] as $ref => $routes) {
341
+				// make sure $routes is an array
342
+				$routes = (array) $routes;
343
+				if (in_array($this->_current_route, $routes)) {
344
+					$this->_scripts_styles['registers'][ $ref ]['type'] == 'js' ? wp_enqueue_script($ref)
345
+						: wp_enqueue_style($ref);
346
+					// if we have a localization for the script let's do that too.
347
+					if (isset($this->_scripts_styles['localize'][ $ref ])) {
348
+						foreach ($this->_scripts_styles['localize'][ $ref ] as $object_name => $indexes) {
349
+							wp_localize_script(
350
+								$ref,
351
+								$object_name,
352
+								$this->_scripts_styles['localize'][ $ref ][ $object_name ]
353
+							);
354
+						}
355
+					}
356
+				}
357
+			}
358
+			// let's do the deregisters
359
+			if (! isset($this->_scripts_styles['deregisters'])) {
360
+				return;
361
+			}
362
+			foreach ($this->_scripts_styles['deregisters'] as $ref => $details) {
363
+				$defaults = ['type' => 'js'];
364
+				$details  = wp_parse_args($details, $defaults);
365
+				$details['type'] === 'js' ? wp_deregister_script($ref) : wp_deregister_style($ref);
366
+			}
367
+		}
368
+	}
369
+
370
+
371
+	/**
372
+	 * just set the defaults for the hooks properties.
373
+	 *
374
+	 * @return void
375
+	 */
376
+	private function _set_defaults()
377
+	{
378
+		$this->_ajax_func                  = [];
379
+		$this->_init_func                  = [];
380
+		$this->_metaboxes                  = [];
381
+		$this->_scripts                    = [];
382
+		$this->_styles                     = [];
383
+		$this->_wp_action_filters_priority = [];
384
+		$this->_current_route              = $this->getCurrentRoute();
385
+		$this->caller                      = get_class($this);
386
+		$this->_extend                     = (bool) stripos($this->caller, 'Extend');
387
+	}
388
+
389
+
390
+	/**
391
+	 * A helper for determining the current route.
392
+	 *
393
+	 * @return string
394
+	 */
395
+	private function getCurrentRoute()
396
+	{
397
+		$action = $this->request->getRequestParam('action');
398
+		// list tables do something else with 'action' for bulk actions.
399
+		$action = $action !== '-1' && $action !== '' ? $action : 'default';
400
+		$route  = $this->request->getRequestParam('route');
401
+		// we set a 'route' variable in some cases where action is being used by something else.
402
+		return $action === 'default' && $route !== '' ? $route : $action;
403
+	}
404
+
405
+
406
+	/**
407
+	 * this sets the _page_object property
408
+	 *
409
+	 * @return void
410
+	 * @throws EE_Error
411
+	 */
412
+	protected function _set_page_object()
413
+	{
414
+		if ($this->_page_object instanceof EE_Admin_Page) {
415
+			return;
416
+		}
417
+		// first make sure $this->_name is set
418
+		if (empty($this->_name)) {
419
+			$msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
420
+			$msg[] = sprintf(
421
+				esc_html__("This is because the %s child class has not set the '_name' property", 'event_espresso'),
422
+				$this->caller
423
+			);
424
+			throw new EE_Error(implode('||', $msg));
425
+		}
426
+		// change "the_message" to "the message"
427
+		$class_name = str_replace('_', ' ', $this->_name);
428
+		// change "the message" to "The_Message_Admin_Page"
429
+		$class_name = str_replace(' ', '_', ucwords($class_name)) . '_Admin_Page';
430
+		// first default file (if exists)
431
+		$decaf_file = EE_ADMIN_PAGES . $this->_name . '/' . $class_name . '.core.php';
432
+		if (is_readable($decaf_file)) {
433
+			require_once($decaf_file);
434
+		}
435
+		// now we have to do require for extended file (if needed)
436
+		if ($this->_extend) {
437
+			require_once(EE_CORE_CAF_ADMIN_EXTEND . $this->_name . '/Extend_' . $class_name . '.core.php');
438
+			// and extend the class name as well
439
+			$class_name = 'Extend_' . $class_name;
440
+		}
441
+		// let's make sure the class exists
442
+		if (! class_exists($class_name)) {
443
+			$msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
444
+			$msg[] = sprintf(
445
+				esc_html__(
446
+					'The class name that was given is %s. Check the spelling and make sure its correct, also there needs to be an autoloader setup for the class',
447
+					'event_espresso'
448
+				),
449
+				$class_name
450
+			);
451
+			throw new EE_Error(implode('||', $msg));
452
+		}
453
+		$this->_page_object = LoaderFactory::getLoader()->getShared($class_name, [false]);
454
+		$this->_page_object->initializePage();
455
+	}
456
+
457
+
458
+	/**
459
+	 * Child "hook" classes can declare any methods that they want executed when a specific page route is loaded.  The
460
+	 * advantage of this is when doing things like running our own db interactions on saves etc.  Remember that
461
+	 * $this->_req_data (all the _POST and _GET data) is available to your methods.
462
+	 *
463
+	 * @return void
464
+	 */
465
+	private function _load_custom_methods()
466
+	{
467
+		/**
468
+		 * method cannot be named 'default' (@see http://us3.php
469
+		 * .net/manual/en/reserved.keywords.php) so need to
470
+		 * handle routes that are "default"
471
+		 *
472
+		 * @since 4.3.0
473
+		 */
474
+		$method_callback = $this->_current_route == 'default' ? 'default_callback' : $this->_current_route;
475
+		// these run before the Admin_Page route executes.
476
+		if (method_exists($this, $method_callback)) {
477
+			call_user_func([$this, $method_callback]);
478
+		}
479
+		// these run via the _redirect_after_action method in EE_Admin_Page which usually happens after non_UI methods in EE_Admin_Page classes.  There are two redirect actions, the first fires before $query_args might be manipulated by "save and close" actions and the seond fires right before the actual redirect happens.
480
+		// first the actions
481
+		// note that these action hooks will have the $query_args value available.
482
+		$admin_class_name = get_class($this->_adminpage_obj);
483
+		if (method_exists($this, '_redirect_action_early_' . $this->_current_route)) {
484
+			add_action(
485
+				'AHEE__'
486
+				. $admin_class_name
487
+				. '___redirect_after_action__before_redirect_modification_'
488
+				. $this->_current_route,
489
+				[$this, '_redirect_action_early_' . $this->_current_route],
490
+				10
491
+			);
492
+		}
493
+		if (method_exists($this, '_redirect_action_' . $this->_current_route)) {
494
+			add_action(
495
+				'AHEE_redirect_' . $admin_class_name . $this->_current_route,
496
+				[$this, '_redirect_action_' . $this->_current_route],
497
+				10
498
+			);
499
+		}
500
+		// let's hook into the _redirect itself and allow for changing where the user goes after redirect.  This will have $query_args and $redirect_url available.
501
+		if (method_exists($this, '_redirect_filter_' . $this->_current_route)) {
502
+			add_filter(
503
+				'FHEE_redirect_' . $admin_class_name . $this->_current_route,
504
+				[$this, '_redirect_filter_' . $this->_current_route],
505
+				10,
506
+				2
507
+			);
508
+		}
509
+	}
510
+
511
+
512
+	/**
513
+	 * This method will search for a corresponding method with a name matching the route and the wp_hook to run.  This
514
+	 * allows child hook classes to target hooking into a specific wp action or filter hook ONLY on a certain route.
515
+	 * just remember, methods MUST be public Future hooks should be added in here to be access by child classes.
516
+	 *
517
+	 * @return void
518
+	 */
519
+	private function _load_routed_hooks()
520
+	{
521
+
522
+		// this array provides the hook action names that will be referenced.  Key is the action. Value is an array with the type (action or filter) and the number of parameters for the hook.  We'll default all priorities for automatic hooks to 10.
523
+		$hook_filter_array = [
524
+			'admin_footer'                                                                            => [
525
+				'type'     => 'action',
526
+				'argnum'   => 1,
527
+				'priority' => 10,
528
+			],
529
+			'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug . '_' . $this->_current_route => [
530
+				'type'     => 'filter',
531
+				'argnum'   => 1,
532
+				'priority' => 10,
533
+			],
534
+			'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug                               => [
535
+				'type'     => 'filter',
536
+				'argnum'   => 1,
537
+				'priority' => 10,
538
+			],
539
+			'FHEE_list_table_views'                                                                   => [
540
+				'type'     => 'filter',
541
+				'argnum'   => 1,
542
+				'priority' => 10,
543
+			],
544
+			'AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes'                              => [
545
+				'type'     => 'action',
546
+				'argnum'   => 1,
547
+				'priority' => 10,
548
+			],
549
+		];
550
+		foreach ($hook_filter_array as $hook => $args) {
551
+			if (method_exists($this, $this->_current_route . '_' . $hook)) {
552
+				if (isset($this->_wp_action_filters_priority[ $hook ])) {
553
+					$args['priority'] = $this->_wp_action_filters_priority[ $hook ];
554
+				}
555
+				if ($args['type'] == 'action') {
556
+					add_action(
557
+						$hook,
558
+						[$this, $this->_current_route . '_' . $hook],
559
+						$args['priority'],
560
+						$args['argnum']
561
+					);
562
+				} else {
563
+					add_filter(
564
+						$hook,
565
+						[$this, $this->_current_route . '_' . $hook],
566
+						$args['priority'],
567
+						$args['argnum']
568
+					);
569
+				}
570
+			}
571
+		}
572
+	}
573
+
574
+
575
+	/**
576
+	 * Loop throught the $_ajax_func array and add_actions for the array.
577
+	 *
578
+	 * @return void
579
+	 * @throws EE_Error
580
+	 */
581
+	private function _ajax_hooks()
582
+	{
583
+		if (empty($this->_ajax_func)) {
584
+			return;
585
+		} //get out there's nothing to take care of.
586
+		foreach ($this->_ajax_func as $action => $method) {
587
+			// make sure method exists
588
+			if (! method_exists($this, $method)) {
589
+				$msg[] = esc_html__(
590
+					'There is no corresponding method for the hook labeled in the _ajax_func array',
591
+					'event_espresso'
592
+				) . '<br />';
593
+				$msg[] = sprintf(
594
+					esc_html__(
595
+						'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
596
+						'event_espresso'
597
+					),
598
+					$method,
599
+					$this->caller
600
+				);
601
+				throw new EE_Error(implode('||', $msg));
602
+			}
603
+			add_action('wp_ajax_' . $action, [$this, $method]);
604
+		}
605
+	}
606
+
607
+
608
+	/**
609
+	 * Loop throught the $_init_func array and add_actions for the array.
610
+	 *
611
+	 * @return void
612
+	 * @throws EE_Error
613
+	 */
614
+	protected function _init_hooks()
615
+	{
616
+		if (empty($this->_init_func)) {
617
+			return;
618
+		}
619
+		// get out there's nothing to take care of.
620
+		// We need to determine what page_route we are on!
621
+		foreach ($this->_init_func as $route => $method) {
622
+			// make sure method exists
623
+			if (! method_exists($this, $method)) {
624
+				$msg[] = esc_html__(
625
+					'There is no corresponding method for the hook labeled in the _init_func array',
626
+					'event_espresso'
627
+				) . '<br />';
628
+				$msg[] = sprintf(
629
+					esc_html__(
630
+						'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
631
+						'event_espresso'
632
+					),
633
+					$method,
634
+					$this->caller
635
+				);
636
+				throw new EE_Error(implode('||', $msg));
637
+			}
638
+			if ($route == $this->_current_route) {
639
+				add_action('admin_init', [$this, $method]);
640
+			}
641
+		}
642
+	}
643
+
644
+
645
+	/**
646
+	 * Loop through the _metaboxes property and add_metaboxes accordingly
647
+	 * //todo we could eventually make this a config component class (i.e. new EE_Metabox);
648
+	 *
649
+	 * @return void
650
+	 * @throws EE_Error
651
+	 */
652
+	public function add_metaboxes()
653
+	{
654
+		if (empty($this->_metaboxes)) {
655
+			return;
656
+		} //get out we don't have any metaboxes to set for this connection
657
+		$this->_handle_metabox_array($this->_metaboxes);
658
+	}
659
+
660
+
661
+	/**
662
+	 * @param array $boxes
663
+	 * @param bool  $add
664
+	 * @throws EE_Error
665
+	 */
666
+	private function _handle_metabox_array(array $boxes, $add = true)
667
+	{
668
+		foreach ($boxes as $box) {
669
+			if (! isset($box['page_route'])) {
670
+				continue;
671
+			}
672
+			// we don't have a valid array
673
+			// let's make sure $box['page_route'] is an array so the "foreach" will work.
674
+			$box['page_route'] = (array) $box['page_route'];
675
+			foreach ($box['page_route'] as $route) {
676
+				if ($route != $this->_current_route) {
677
+					continue;
678
+				} //get out we only add metaboxes for set route.
679
+				if ($add) {
680
+					$this->_add_metabox($box);
681
+				} else {
682
+					$this->_remove_metabox($box);
683
+				}
684
+			}
685
+		}
686
+	}
687
+
688
+
689
+	/**
690
+	 * Loop through the _remove_metaboxes property and remove metaboxes accordingly.
691
+	 *
692
+	 * @return void
693
+	 * @throws EE_Error
694
+	 */
695
+	public function remove_metaboxes()
696
+	{
697
+		if (empty($this->_remove_metaboxes)) {
698
+			return;
699
+		} //get out there are no metaboxes to remove
700
+		$this->_handle_metabox_array($this->_remove_metaboxes, false);
701
+	}
702
+
703
+
704
+	/**
705
+	 * This just handles adding a metabox
706
+	 *
707
+	 * @param array $args an array of args that have been set for this metabox by the child class
708
+	 * @throws EE_Error
709
+	 */
710
+	private function _add_metabox($args)
711
+	{
712
+		$current_screen = get_current_screen();
713
+		$screen_id      = is_object($current_screen) ? $current_screen->id : null;
714
+		$callback       = $args['func'] ?? 'some_invalid_callback';
715
+		$callback_function = is_array($callback) ? end($callback) : $callback;
716
+		// set defaults
717
+		$defaults      = [
718
+			'callback_args' => [],
719
+			'context'       => 'advanced',
720
+			'func'          => $callback,
721
+			'id'            => $this->caller . '_' . $callback_function . '_metabox',
722
+			'label'         => $this->caller,
723
+			'page'          => isset($args['page']) ? $args['page'] : $screen_id,
724
+			'priority'      => 'default',
725
+		];
726
+		$args          = wp_parse_args($args, $defaults);
727
+		$callback_args = $args['callback_args'];
728
+		$context       = $args['context'];
729
+		$id            = $args['id'];
730
+		$label         = $args['label'];
731
+		$page          = $args['page'];
732
+		$priority      = $args['priority'];
733
+		// make sure method exists
734
+		if (! method_exists($this, $callback_function)) {
735
+			$msg[] =
736
+				esc_html__('There is no corresponding method to display the metabox content', 'event_espresso')
737
+				. '<br />';
738
+			$msg[] = sprintf(
739
+				esc_html__(
740
+					'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
741
+					'event_espresso'
742
+				),
743
+				$callback_function,
744
+				$this->caller
745
+			);
746
+			throw new EE_Error(implode('||', $msg));
747
+		}
748
+		// everything checks out so let's add the metabox
749
+		add_meta_box($id, $label, [$this, $callback_function], $page, $context, $priority, $callback_args);
750
+		add_filter(
751
+			"postbox_classes_{$page}_{$id}",
752
+			function ($classes) {
753
+				array_push($classes, 'ee-admin-container');
754
+				return $classes;
755
+			}
756
+		);
757
+	}
758
+
759
+
760
+	private function _remove_metabox($args)
761
+	{
762
+		$current_screen = get_current_screen();
763
+		$screen_id      = is_object($current_screen) ? $current_screen->id : null;
764
+		$func           = isset($args['func']) ? $args['func'] : 'some_invalid_callback';
765
+		// set defaults
766
+		$defaults = [
767
+			'context' => 'default',
768
+			'id'      => isset($args['id'])
769
+				? $args['id']
770
+				: $this->_current_route . '_' . $this->caller . '_' . $func . '_metabox',
771
+			'screen'  => isset($args['screen']) ? $args['screen'] : $screen_id,
772
+		];
773
+		$args     = wp_parse_args($args, $defaults);
774
+		$context  = $args['context'];
775
+		$id       = $args['id'];
776
+		$screen   = $args['screen'];
777
+		// everything checks out so lets remove the box!
778
+		remove_meta_box($id, $screen, $context);
779
+	}
780 780
 }
Please login to merge, or discard this patch.
Spacing   +42 added lines, -42 removed lines patch added patch discarded remove patch
@@ -273,9 +273,9 @@  discard block
 block discarded – undo
273 273
      */
274 274
     public function enqueue_scripts_styles()
275 275
     {
276
-        if (! empty($this->_scripts_styles)) {
276
+        if ( ! empty($this->_scripts_styles)) {
277 277
             // first let's do all the registrations
278
-            if (! isset($this->_scripts_styles['registers'])) {
278
+            if ( ! isset($this->_scripts_styles['registers'])) {
279 279
                 $msg[] = esc_html__(
280 280
                     'There is no "registers" index in the <code>$this->_scripts_styles</code> property.',
281 281
                     'event_espresso'
@@ -285,7 +285,7 @@  discard block
 block discarded – undo
285 285
                         'Make sure you read the phpdoc comments above the definition of the $_scripts_styles property in the <code>EE_Admin_Hooks</code> class and modify according in the %s child',
286 286
                         'event_espresso'
287 287
                     ),
288
-                    '<strong>' . $this->caller . '</strong>'
288
+                    '<strong>'.$this->caller.'</strong>'
289 289
                 );
290 290
                 throw new EE_Error(implode('||', $msg));
291 291
             }
@@ -305,7 +305,7 @@  discard block
 block discarded – undo
305 305
                 $footer  = $details['footer'];
306 306
                 // let's make sure that we set the 'registers' type if it's not set!
307 307
                 // We need it later to determine which enqueue we do
308
-                $this->_scripts_styles['registers'][ $ref ]['type'] = $type;
308
+                $this->_scripts_styles['registers'][$ref]['type'] = $type;
309 309
                 // let's make sure we're not missing any REQUIRED parameters
310 310
                 if (empty($url)) {
311 311
                     $msg[] = sprintf(
@@ -317,7 +317,7 @@  discard block
 block discarded – undo
317 317
                             'Doublecheck your <code>$this->_scripts_styles</code> array in %s and make sure that there is a "url" set for the %s ref',
318 318
                             'event_espresso'
319 319
                         ),
320
-                        '<strong>' . $this->caller . '</strong>',
320
+                        '<strong>'.$this->caller.'</strong>',
321 321
                         $ref
322 322
                     );
323 323
                     throw new EE_Error(implode('||', $msg));
@@ -333,7 +333,7 @@  discard block
 block discarded – undo
333 333
                     );
334 334
             }
335 335
             // k now let's do the enqueues
336
-            if (! isset($this->_scripts_styles['enqueues'])) {
336
+            if ( ! isset($this->_scripts_styles['enqueues'])) {
337 337
                 return;
338 338
             }  //not sure if we should throw an error here or not.
339 339
 
@@ -341,22 +341,22 @@  discard block
 block discarded – undo
341 341
                 // make sure $routes is an array
342 342
                 $routes = (array) $routes;
343 343
                 if (in_array($this->_current_route, $routes)) {
344
-                    $this->_scripts_styles['registers'][ $ref ]['type'] == 'js' ? wp_enqueue_script($ref)
344
+                    $this->_scripts_styles['registers'][$ref]['type'] == 'js' ? wp_enqueue_script($ref)
345 345
                         : wp_enqueue_style($ref);
346 346
                     // if we have a localization for the script let's do that too.
347
-                    if (isset($this->_scripts_styles['localize'][ $ref ])) {
348
-                        foreach ($this->_scripts_styles['localize'][ $ref ] as $object_name => $indexes) {
347
+                    if (isset($this->_scripts_styles['localize'][$ref])) {
348
+                        foreach ($this->_scripts_styles['localize'][$ref] as $object_name => $indexes) {
349 349
                             wp_localize_script(
350 350
                                 $ref,
351 351
                                 $object_name,
352
-                                $this->_scripts_styles['localize'][ $ref ][ $object_name ]
352
+                                $this->_scripts_styles['localize'][$ref][$object_name]
353 353
                             );
354 354
                         }
355 355
                     }
356 356
                 }
357 357
             }
358 358
             // let's do the deregisters
359
-            if (! isset($this->_scripts_styles['deregisters'])) {
359
+            if ( ! isset($this->_scripts_styles['deregisters'])) {
360 360
                 return;
361 361
             }
362 362
             foreach ($this->_scripts_styles['deregisters'] as $ref => $details) {
@@ -426,20 +426,20 @@  discard block
 block discarded – undo
426 426
         // change "the_message" to "the message"
427 427
         $class_name = str_replace('_', ' ', $this->_name);
428 428
         // change "the message" to "The_Message_Admin_Page"
429
-        $class_name = str_replace(' ', '_', ucwords($class_name)) . '_Admin_Page';
429
+        $class_name = str_replace(' ', '_', ucwords($class_name)).'_Admin_Page';
430 430
         // first default file (if exists)
431
-        $decaf_file = EE_ADMIN_PAGES . $this->_name . '/' . $class_name . '.core.php';
431
+        $decaf_file = EE_ADMIN_PAGES.$this->_name.'/'.$class_name.'.core.php';
432 432
         if (is_readable($decaf_file)) {
433 433
             require_once($decaf_file);
434 434
         }
435 435
         // now we have to do require for extended file (if needed)
436 436
         if ($this->_extend) {
437
-            require_once(EE_CORE_CAF_ADMIN_EXTEND . $this->_name . '/Extend_' . $class_name . '.core.php');
437
+            require_once(EE_CORE_CAF_ADMIN_EXTEND.$this->_name.'/Extend_'.$class_name.'.core.php');
438 438
             // and extend the class name as well
439
-            $class_name = 'Extend_' . $class_name;
439
+            $class_name = 'Extend_'.$class_name;
440 440
         }
441 441
         // let's make sure the class exists
442
-        if (! class_exists($class_name)) {
442
+        if ( ! class_exists($class_name)) {
443 443
             $msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
444 444
             $msg[] = sprintf(
445 445
                 esc_html__(
@@ -480,28 +480,28 @@  discard block
 block discarded – undo
480 480
         // first the actions
481 481
         // note that these action hooks will have the $query_args value available.
482 482
         $admin_class_name = get_class($this->_adminpage_obj);
483
-        if (method_exists($this, '_redirect_action_early_' . $this->_current_route)) {
483
+        if (method_exists($this, '_redirect_action_early_'.$this->_current_route)) {
484 484
             add_action(
485 485
                 'AHEE__'
486 486
                 . $admin_class_name
487 487
                 . '___redirect_after_action__before_redirect_modification_'
488 488
                 . $this->_current_route,
489
-                [$this, '_redirect_action_early_' . $this->_current_route],
489
+                [$this, '_redirect_action_early_'.$this->_current_route],
490 490
                 10
491 491
             );
492 492
         }
493
-        if (method_exists($this, '_redirect_action_' . $this->_current_route)) {
493
+        if (method_exists($this, '_redirect_action_'.$this->_current_route)) {
494 494
             add_action(
495
-                'AHEE_redirect_' . $admin_class_name . $this->_current_route,
496
-                [$this, '_redirect_action_' . $this->_current_route],
495
+                'AHEE_redirect_'.$admin_class_name.$this->_current_route,
496
+                [$this, '_redirect_action_'.$this->_current_route],
497 497
                 10
498 498
             );
499 499
         }
500 500
         // let's hook into the _redirect itself and allow for changing where the user goes after redirect.  This will have $query_args and $redirect_url available.
501
-        if (method_exists($this, '_redirect_filter_' . $this->_current_route)) {
501
+        if (method_exists($this, '_redirect_filter_'.$this->_current_route)) {
502 502
             add_filter(
503
-                'FHEE_redirect_' . $admin_class_name . $this->_current_route,
504
-                [$this, '_redirect_filter_' . $this->_current_route],
503
+                'FHEE_redirect_'.$admin_class_name.$this->_current_route,
504
+                [$this, '_redirect_filter_'.$this->_current_route],
505 505
                 10,
506 506
                 2
507 507
             );
@@ -526,12 +526,12 @@  discard block
 block discarded – undo
526 526
                 'argnum'   => 1,
527 527
                 'priority' => 10,
528 528
             ],
529
-            'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug . '_' . $this->_current_route => [
529
+            'FHEE_list_table_views_'.$this->_adminpage_obj->page_slug.'_'.$this->_current_route => [
530 530
                 'type'     => 'filter',
531 531
                 'argnum'   => 1,
532 532
                 'priority' => 10,
533 533
             ],
534
-            'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug                               => [
534
+            'FHEE_list_table_views_'.$this->_adminpage_obj->page_slug                               => [
535 535
                 'type'     => 'filter',
536 536
                 'argnum'   => 1,
537 537
                 'priority' => 10,
@@ -548,21 +548,21 @@  discard block
 block discarded – undo
548 548
             ],
549 549
         ];
550 550
         foreach ($hook_filter_array as $hook => $args) {
551
-            if (method_exists($this, $this->_current_route . '_' . $hook)) {
552
-                if (isset($this->_wp_action_filters_priority[ $hook ])) {
553
-                    $args['priority'] = $this->_wp_action_filters_priority[ $hook ];
551
+            if (method_exists($this, $this->_current_route.'_'.$hook)) {
552
+                if (isset($this->_wp_action_filters_priority[$hook])) {
553
+                    $args['priority'] = $this->_wp_action_filters_priority[$hook];
554 554
                 }
555 555
                 if ($args['type'] == 'action') {
556 556
                     add_action(
557 557
                         $hook,
558
-                        [$this, $this->_current_route . '_' . $hook],
558
+                        [$this, $this->_current_route.'_'.$hook],
559 559
                         $args['priority'],
560 560
                         $args['argnum']
561 561
                     );
562 562
                 } else {
563 563
                     add_filter(
564 564
                         $hook,
565
-                        [$this, $this->_current_route . '_' . $hook],
565
+                        [$this, $this->_current_route.'_'.$hook],
566 566
                         $args['priority'],
567 567
                         $args['argnum']
568 568
                     );
@@ -585,11 +585,11 @@  discard block
 block discarded – undo
585 585
         } //get out there's nothing to take care of.
586 586
         foreach ($this->_ajax_func as $action => $method) {
587 587
             // make sure method exists
588
-            if (! method_exists($this, $method)) {
588
+            if ( ! method_exists($this, $method)) {
589 589
                 $msg[] = esc_html__(
590 590
                     'There is no corresponding method for the hook labeled in the _ajax_func array',
591 591
                     'event_espresso'
592
-                ) . '<br />';
592
+                ).'<br />';
593 593
                 $msg[] = sprintf(
594 594
                     esc_html__(
595 595
                         'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
@@ -600,7 +600,7 @@  discard block
 block discarded – undo
600 600
                 );
601 601
                 throw new EE_Error(implode('||', $msg));
602 602
             }
603
-            add_action('wp_ajax_' . $action, [$this, $method]);
603
+            add_action('wp_ajax_'.$action, [$this, $method]);
604 604
         }
605 605
     }
606 606
 
@@ -620,11 +620,11 @@  discard block
 block discarded – undo
620 620
         // We need to determine what page_route we are on!
621 621
         foreach ($this->_init_func as $route => $method) {
622 622
             // make sure method exists
623
-            if (! method_exists($this, $method)) {
623
+            if ( ! method_exists($this, $method)) {
624 624
                 $msg[] = esc_html__(
625 625
                     'There is no corresponding method for the hook labeled in the _init_func array',
626 626
                     'event_espresso'
627
-                ) . '<br />';
627
+                ).'<br />';
628 628
                 $msg[] = sprintf(
629 629
                     esc_html__(
630 630
                         'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
@@ -666,7 +666,7 @@  discard block
 block discarded – undo
666 666
     private function _handle_metabox_array(array $boxes, $add = true)
667 667
     {
668 668
         foreach ($boxes as $box) {
669
-            if (! isset($box['page_route'])) {
669
+            if ( ! isset($box['page_route'])) {
670 670
                 continue;
671 671
             }
672 672
             // we don't have a valid array
@@ -714,11 +714,11 @@  discard block
 block discarded – undo
714 714
         $callback       = $args['func'] ?? 'some_invalid_callback';
715 715
         $callback_function = is_array($callback) ? end($callback) : $callback;
716 716
         // set defaults
717
-        $defaults      = [
717
+        $defaults = [
718 718
             'callback_args' => [],
719 719
             'context'       => 'advanced',
720 720
             'func'          => $callback,
721
-            'id'            => $this->caller . '_' . $callback_function . '_metabox',
721
+            'id'            => $this->caller.'_'.$callback_function.'_metabox',
722 722
             'label'         => $this->caller,
723 723
             'page'          => isset($args['page']) ? $args['page'] : $screen_id,
724 724
             'priority'      => 'default',
@@ -731,7 +731,7 @@  discard block
 block discarded – undo
731 731
         $page          = $args['page'];
732 732
         $priority      = $args['priority'];
733 733
         // make sure method exists
734
-        if (! method_exists($this, $callback_function)) {
734
+        if ( ! method_exists($this, $callback_function)) {
735 735
             $msg[] =
736 736
                 esc_html__('There is no corresponding method to display the metabox content', 'event_espresso')
737 737
                 . '<br />';
@@ -749,7 +749,7 @@  discard block
 block discarded – undo
749 749
         add_meta_box($id, $label, [$this, $callback_function], $page, $context, $priority, $callback_args);
750 750
         add_filter(
751 751
             "postbox_classes_{$page}_{$id}",
752
-            function ($classes) {
752
+            function($classes) {
753 753
                 array_push($classes, 'ee-admin-container');
754 754
                 return $classes;
755 755
             }
@@ -767,7 +767,7 @@  discard block
 block discarded – undo
767 767
             'context' => 'default',
768 768
             'id'      => isset($args['id'])
769 769
                 ? $args['id']
770
-                : $this->_current_route . '_' . $this->caller . '_' . $func . '_metabox',
770
+                : $this->_current_route.'_'.$this->caller.'_'.$func.'_metabox',
771 771
             'screen'  => isset($args['screen']) ? $args['screen'] : $screen_id,
772 772
         ];
773 773
         $args     = wp_parse_args($args, $defaults);
Please login to merge, or discard this patch.
caffeinated/admin/new/pricing/espresso_events_Pricing_Hooks.class.php 1 patch
Indentation   +2170 added lines, -2170 removed lines patch added patch discarded remove patch
@@ -15,2188 +15,2188 @@
 block discarded – undo
15 15
  */
16 16
 class espresso_events_Pricing_Hooks extends EE_Admin_Hooks
17 17
 {
18
-    /**
19
-     * This property is just used to hold the status of whether an event is currently being
20
-     * created (true) or edited (false)
21
-     *
22
-     * @access protected
23
-     * @var bool
24
-     */
25
-    protected $_is_creating_event;
26
-
27
-    /**
28
-     * Used to contain the format strings for date and time that will be used for php date and
29
-     * time.
30
-     * Is set in the _set_hooks_properties() method.
31
-     *
32
-     * @var array
33
-     */
34
-    protected $_date_format_strings;
35
-
36
-    /**
37
-     * @var string $_date_time_format
38
-     */
39
-    protected $_date_time_format;
40
-
41
-
42
-    /**
43
-     * @throws InvalidArgumentException
44
-     * @throws InvalidInterfaceException
45
-     * @throws InvalidDataTypeException
46
-     */
47
-    protected function _set_hooks_properties()
48
-    {
49
-        $this->_name = 'pricing';
50
-        // capability check
51
-        if (
52
-            $this->_adminpage_obj->adminConfig()->useAdvancedEditor()
53
-            || ! EE_Registry::instance()->CAP->current_user_can(
54
-                'ee_read_default_prices',
55
-                'advanced_ticket_datetime_metabox'
56
-            )
57
-        ) {
58
-            $this->_metaboxes      = [];
59
-            $this->_scripts_styles = [];
60
-            return;
61
-        }
62
-        $this->_setup_metaboxes();
63
-        $this->_set_date_time_formats();
64
-        $this->_validate_format_strings();
65
-        $this->_set_scripts_styles();
66
-        add_filter(
67
-            'FHEE__Events_Admin_Page___insert_update_cpt_item__event_update_callbacks',
68
-            [$this, 'caf_updates']
69
-        );
70
-    }
71
-
72
-
73
-    /**
74
-     * @return void
75
-     */
76
-    protected function _setup_metaboxes()
77
-    {
78
-        // if we were going to add our own metaboxes we'd use the below.
79
-        $this->_metaboxes        = [
80
-            0 => [
81
-                'page_route' => ['edit', 'create_new'],
82
-                'func'       => [$this, 'pricing_metabox'],
83
-                'label'      => esc_html__('Event Tickets & Datetimes', 'event_espresso'),
84
-                'priority'   => 'high',
85
-                'context'    => 'normal',
86
-            ],
87
-        ];
88
-        $this->_remove_metaboxes = [
89
-            0 => [
90
-                'page_route' => ['edit', 'create_new'],
91
-                'id'         => 'espresso_event_editor_tickets',
92
-                'context'    => 'normal',
93
-            ],
94
-        ];
95
-    }
96
-
97
-
98
-    /**
99
-     * @return void
100
-     */
101
-    protected function _set_date_time_formats()
102
-    {
103
-        /**
104
-         * Format strings for date and time.  Defaults are existing behaviour from 4.1.
105
-         * Note, that if you return null as the value for 'date', and 'time' in the array, then
106
-         * EE will automatically use the set wp_options, 'date_format', and 'time_format'.
107
-         *
108
-         * @since 4.6.7
109
-         * @var array  Expected an array returned with 'date' and 'time' keys.
110
-         */
111
-        $this->_date_format_strings = apply_filters(
112
-            'FHEE__espresso_events_Pricing_Hooks___set_hooks_properties__date_format_strings',
113
-            [
114
-                'date' => 'Y-m-d',
115
-                'time' => 'h:i a',
116
-            ]
117
-        );
118
-        // validate
119
-        $this->_date_format_strings['date'] = $this->_date_format_strings['date'] ?? '';
120
-        $this->_date_format_strings['time'] = $this->_date_format_strings['time'] ?? '';
121
-
122
-        $this->_date_time_format = $this->_date_format_strings['date'] . ' ' . $this->_date_format_strings['time'];
123
-    }
124
-
125
-
126
-    /**
127
-     * @return void
128
-     */
129
-    protected function _validate_format_strings()
130
-    {
131
-        // validate format strings
132
-        $format_validation = EEH_DTT_Helper::validate_format_string(
133
-            $this->_date_time_format
134
-        );
135
-        if (is_array($format_validation)) {
136
-            $msg = '<p>';
137
-            $msg .= sprintf(
138
-                esc_html__(
139
-                    'The format "%s" was likely added via a filter and is invalid for the following reasons:',
140
-                    'event_espresso'
141
-                ),
142
-                $this->_date_time_format
143
-            );
144
-            $msg .= '</p><ul>';
145
-            foreach ($format_validation as $error) {
146
-                $msg .= '<li>' . $error . '</li>';
147
-            }
148
-            $msg .= '</ul><p>';
149
-            $msg .= sprintf(
150
-                esc_html__(
151
-                    '%sPlease note that your date and time formats have been reset to "Y-m-d" and "h:i a" respectively.%s',
152
-                    'event_espresso'
153
-                ),
154
-                '<span style="color:#D54E21;">',
155
-                '</span>'
156
-            );
157
-            $msg .= '</p>';
158
-            EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
159
-            $this->_date_format_strings = [
160
-                'date' => 'Y-m-d',
161
-                'time' => 'h:i a',
162
-            ];
163
-        }
164
-    }
165
-
166
-
167
-    /**
168
-     * @return void
169
-     */
170
-    protected function _set_scripts_styles()
171
-    {
172
-        $this->_scripts_styles = [
173
-            'registers'   => [
174
-                'ee-tickets-datetimes-css' => [
175
-                    'url'  => PRICING_ASSETS_URL . 'event-tickets-datetimes.css',
176
-                    'type' => 'css',
177
-                ],
178
-                'ee-dtt-ticket-metabox'    => [
179
-                    'url'     => PRICING_ASSETS_URL . 'ee-datetime-ticket-metabox.js',
180
-                    'depends' => ['ee-datepicker', 'ee-dialog', 'underscore'],
181
-                ],
182
-            ],
183
-            'deregisters' => [
184
-                'event-editor-css'       => ['type' => 'css'],
185
-                'event-datetime-metabox' => ['type' => 'js'],
186
-            ],
187
-            'enqueues'    => [
188
-                'ee-tickets-datetimes-css' => ['edit', 'create_new'],
189
-                'ee-dtt-ticket-metabox'    => ['edit', 'create_new'],
190
-            ],
191
-            'localize'    => [
192
-                'ee-dtt-ticket-metabox' => [
193
-                    'DTT_TRASH_BLOCK'       => [
194
-                        'main_warning'            => esc_html__(
195
-                            'The Datetime you are attempting to trash is the only datetime selected for the following ticket(s):',
196
-                            'event_espresso'
197
-                        ),
198
-                        'after_warning'           => esc_html__(
199
-                            'In order to trash this datetime you must first make sure the above ticket(s) are assigned to other datetimes.',
200
-                            'event_espresso'
201
-                        ),
202
-                        'cancel_button'           => '
18
+	/**
19
+	 * This property is just used to hold the status of whether an event is currently being
20
+	 * created (true) or edited (false)
21
+	 *
22
+	 * @access protected
23
+	 * @var bool
24
+	 */
25
+	protected $_is_creating_event;
26
+
27
+	/**
28
+	 * Used to contain the format strings for date and time that will be used for php date and
29
+	 * time.
30
+	 * Is set in the _set_hooks_properties() method.
31
+	 *
32
+	 * @var array
33
+	 */
34
+	protected $_date_format_strings;
35
+
36
+	/**
37
+	 * @var string $_date_time_format
38
+	 */
39
+	protected $_date_time_format;
40
+
41
+
42
+	/**
43
+	 * @throws InvalidArgumentException
44
+	 * @throws InvalidInterfaceException
45
+	 * @throws InvalidDataTypeException
46
+	 */
47
+	protected function _set_hooks_properties()
48
+	{
49
+		$this->_name = 'pricing';
50
+		// capability check
51
+		if (
52
+			$this->_adminpage_obj->adminConfig()->useAdvancedEditor()
53
+			|| ! EE_Registry::instance()->CAP->current_user_can(
54
+				'ee_read_default_prices',
55
+				'advanced_ticket_datetime_metabox'
56
+			)
57
+		) {
58
+			$this->_metaboxes      = [];
59
+			$this->_scripts_styles = [];
60
+			return;
61
+		}
62
+		$this->_setup_metaboxes();
63
+		$this->_set_date_time_formats();
64
+		$this->_validate_format_strings();
65
+		$this->_set_scripts_styles();
66
+		add_filter(
67
+			'FHEE__Events_Admin_Page___insert_update_cpt_item__event_update_callbacks',
68
+			[$this, 'caf_updates']
69
+		);
70
+	}
71
+
72
+
73
+	/**
74
+	 * @return void
75
+	 */
76
+	protected function _setup_metaboxes()
77
+	{
78
+		// if we were going to add our own metaboxes we'd use the below.
79
+		$this->_metaboxes        = [
80
+			0 => [
81
+				'page_route' => ['edit', 'create_new'],
82
+				'func'       => [$this, 'pricing_metabox'],
83
+				'label'      => esc_html__('Event Tickets & Datetimes', 'event_espresso'),
84
+				'priority'   => 'high',
85
+				'context'    => 'normal',
86
+			],
87
+		];
88
+		$this->_remove_metaboxes = [
89
+			0 => [
90
+				'page_route' => ['edit', 'create_new'],
91
+				'id'         => 'espresso_event_editor_tickets',
92
+				'context'    => 'normal',
93
+			],
94
+		];
95
+	}
96
+
97
+
98
+	/**
99
+	 * @return void
100
+	 */
101
+	protected function _set_date_time_formats()
102
+	{
103
+		/**
104
+		 * Format strings for date and time.  Defaults are existing behaviour from 4.1.
105
+		 * Note, that if you return null as the value for 'date', and 'time' in the array, then
106
+		 * EE will automatically use the set wp_options, 'date_format', and 'time_format'.
107
+		 *
108
+		 * @since 4.6.7
109
+		 * @var array  Expected an array returned with 'date' and 'time' keys.
110
+		 */
111
+		$this->_date_format_strings = apply_filters(
112
+			'FHEE__espresso_events_Pricing_Hooks___set_hooks_properties__date_format_strings',
113
+			[
114
+				'date' => 'Y-m-d',
115
+				'time' => 'h:i a',
116
+			]
117
+		);
118
+		// validate
119
+		$this->_date_format_strings['date'] = $this->_date_format_strings['date'] ?? '';
120
+		$this->_date_format_strings['time'] = $this->_date_format_strings['time'] ?? '';
121
+
122
+		$this->_date_time_format = $this->_date_format_strings['date'] . ' ' . $this->_date_format_strings['time'];
123
+	}
124
+
125
+
126
+	/**
127
+	 * @return void
128
+	 */
129
+	protected function _validate_format_strings()
130
+	{
131
+		// validate format strings
132
+		$format_validation = EEH_DTT_Helper::validate_format_string(
133
+			$this->_date_time_format
134
+		);
135
+		if (is_array($format_validation)) {
136
+			$msg = '<p>';
137
+			$msg .= sprintf(
138
+				esc_html__(
139
+					'The format "%s" was likely added via a filter and is invalid for the following reasons:',
140
+					'event_espresso'
141
+				),
142
+				$this->_date_time_format
143
+			);
144
+			$msg .= '</p><ul>';
145
+			foreach ($format_validation as $error) {
146
+				$msg .= '<li>' . $error . '</li>';
147
+			}
148
+			$msg .= '</ul><p>';
149
+			$msg .= sprintf(
150
+				esc_html__(
151
+					'%sPlease note that your date and time formats have been reset to "Y-m-d" and "h:i a" respectively.%s',
152
+					'event_espresso'
153
+				),
154
+				'<span style="color:#D54E21;">',
155
+				'</span>'
156
+			);
157
+			$msg .= '</p>';
158
+			EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
159
+			$this->_date_format_strings = [
160
+				'date' => 'Y-m-d',
161
+				'time' => 'h:i a',
162
+			];
163
+		}
164
+	}
165
+
166
+
167
+	/**
168
+	 * @return void
169
+	 */
170
+	protected function _set_scripts_styles()
171
+	{
172
+		$this->_scripts_styles = [
173
+			'registers'   => [
174
+				'ee-tickets-datetimes-css' => [
175
+					'url'  => PRICING_ASSETS_URL . 'event-tickets-datetimes.css',
176
+					'type' => 'css',
177
+				],
178
+				'ee-dtt-ticket-metabox'    => [
179
+					'url'     => PRICING_ASSETS_URL . 'ee-datetime-ticket-metabox.js',
180
+					'depends' => ['ee-datepicker', 'ee-dialog', 'underscore'],
181
+				],
182
+			],
183
+			'deregisters' => [
184
+				'event-editor-css'       => ['type' => 'css'],
185
+				'event-datetime-metabox' => ['type' => 'js'],
186
+			],
187
+			'enqueues'    => [
188
+				'ee-tickets-datetimes-css' => ['edit', 'create_new'],
189
+				'ee-dtt-ticket-metabox'    => ['edit', 'create_new'],
190
+			],
191
+			'localize'    => [
192
+				'ee-dtt-ticket-metabox' => [
193
+					'DTT_TRASH_BLOCK'       => [
194
+						'main_warning'            => esc_html__(
195
+							'The Datetime you are attempting to trash is the only datetime selected for the following ticket(s):',
196
+							'event_espresso'
197
+						),
198
+						'after_warning'           => esc_html__(
199
+							'In order to trash this datetime you must first make sure the above ticket(s) are assigned to other datetimes.',
200
+							'event_espresso'
201
+						),
202
+						'cancel_button'           => '
203 203
                             <button class="button--secondary ee-modal-cancel">
204 204
                                 ' . esc_html__('Cancel', 'event_espresso') . '
205 205
                             </button>',
206
-                        'close_button'            => '
206
+						'close_button'            => '
207 207
                             <button class="button--secondary ee-modal-cancel">
208 208
                                 ' . esc_html__('Close', 'event_espresso') . '
209 209
                             </button>',
210
-                        'single_warning_from_tkt' => esc_html__(
211
-                            'The Datetime you are attempting to unassign from this ticket is the only remaining datetime for this ticket. Tickets must always have at least one datetime assigned to them.',
212
-                            'event_espresso'
213
-                        ),
214
-                        'single_warning_from_dtt' => esc_html__(
215
-                            'The ticket you are attempting to unassign from this datetime cannot be unassigned because the datetime is the only remaining datetime for the ticket.  Tickets must always have at least one datetime assigned to them.',
216
-                            'event_espresso'
217
-                        ),
218
-                        'dismiss_button'          => '
210
+						'single_warning_from_tkt' => esc_html__(
211
+							'The Datetime you are attempting to unassign from this ticket is the only remaining datetime for this ticket. Tickets must always have at least one datetime assigned to them.',
212
+							'event_espresso'
213
+						),
214
+						'single_warning_from_dtt' => esc_html__(
215
+							'The ticket you are attempting to unassign from this datetime cannot be unassigned because the datetime is the only remaining datetime for the ticket.  Tickets must always have at least one datetime assigned to them.',
216
+							'event_espresso'
217
+						),
218
+						'dismiss_button'          => '
219 219
                             <button class="button--secondary ee-modal-cancel">
220 220
                                 ' . esc_html__('Dismiss', 'event_espresso') . '
221 221
                             </button>',
222
-                    ],
223
-                    'DTT_ERROR_MSG'         => [
224
-                        'no_ticket_name' => esc_html__('General Admission', 'event_espresso'),
225
-                        'dismiss_button' => '
222
+					],
223
+					'DTT_ERROR_MSG'         => [
224
+						'no_ticket_name' => esc_html__('General Admission', 'event_espresso'),
225
+						'dismiss_button' => '
226 226
                             <div class="save-cancel-button-container">
227 227
                                 <button class="button--secondary ee-modal-cancel">
228 228
                                     ' . esc_html__('Dismiss', 'event_espresso') . '
229 229
                                 </button>
230 230
                             </div>',
231
-                    ],
232
-                    'DTT_OVERSELL_WARNING'  => [
233
-                        'datetime_ticket' => esc_html__(
234
-                            'You cannot add this ticket to this datetime because it has a sold amount that is greater than the amount of spots remaining for this datetime.',
235
-                            'event_espresso'
236
-                        ),
237
-                        'ticket_datetime' => esc_html__(
238
-                            'You cannot add this datetime to this ticket because the ticket has a sold amount that is greater than the amount of spots remaining on the datetime.',
239
-                            'event_espresso'
240
-                        ),
241
-                    ],
242
-                    'DTT_CONVERTED_FORMATS' => EEH_DTT_Helper::convert_php_to_js_and_moment_date_formats(
243
-                        $this->_date_format_strings['date'],
244
-                        $this->_date_format_strings['time']
245
-                    ),
246
-                    'DTT_START_OF_WEEK'     => ['dayValue' => (int) get_option('start_of_week')],
247
-                ],
248
-            ],
249
-        ];
250
-    }
251
-
252
-
253
-    /**
254
-     * @param array $update_callbacks
255
-     * @return array
256
-     */
257
-    public function caf_updates(array $update_callbacks): array
258
-    {
259
-        unset($update_callbacks['_default_tickets_update']);
260
-        $update_callbacks['datetime_and_tickets_caf_update'] = [$this, 'datetime_and_tickets_caf_update'];
261
-        return $update_callbacks;
262
-    }
263
-
264
-
265
-    /**
266
-     * Handles saving everything related to Tickets (datetimes, tickets, prices)
267
-     *
268
-     * @param EE_Event $event The Event object we're attaching data to
269
-     * @param array    $data  The request data from the form
270
-     * @throws ReflectionException
271
-     * @throws Exception
272
-     * @throws InvalidInterfaceException
273
-     * @throws InvalidDataTypeException
274
-     * @throws EE_Error
275
-     * @throws InvalidArgumentException
276
-     */
277
-    public function datetime_and_tickets_caf_update(EE_Event $event, array $data)
278
-    {
279
-        // first we need to start with datetimes cause they are the "root" items attached to events.
280
-        $saved_datetimes = $this->_update_datetimes($event, $data);
281
-        // next tackle the tickets (and prices?)
282
-        $this->_update_tickets($event, $saved_datetimes, $data);
283
-    }
284
-
285
-
286
-    /**
287
-     * update event_datetimes
288
-     *
289
-     * @param EE_Event $event Event being updated
290
-     * @param array    $data  the request data from the form
291
-     * @return EE_Datetime[]
292
-     * @throws Exception
293
-     * @throws ReflectionException
294
-     * @throws InvalidInterfaceException
295
-     * @throws InvalidDataTypeException
296
-     * @throws InvalidArgumentException
297
-     * @throws EE_Error
298
-     */
299
-    protected function _update_datetimes(EE_Event $event, array $data): array
300
-    {
301
-        $timezone            = $data['timezone_string'] ?? null;
302
-        $saved_datetime_ids  = [];
303
-        $saved_datetime_objs = [];
304
-        if (empty($data['edit_event_datetimes']) || ! is_array($data['edit_event_datetimes'])) {
305
-            throw new InvalidArgumentException(
306
-                esc_html__(
307
-                    'The "edit_event_datetimes" array is invalid therefore the event can not be updated.',
308
-                    'event_espresso'
309
-                )
310
-            );
311
-        }
312
-        foreach ($data['edit_event_datetimes'] as $row => $datetime_data) {
313
-            // trim all values to ensure any excess whitespace is removed.
314
-            $datetime_data                = array_map(
315
-                function ($datetime_data) {
316
-                    return is_array($datetime_data) ? $datetime_data : trim($datetime_data);
317
-                },
318
-                $datetime_data
319
-            );
320
-
321
-            $datetime_data['DTT_EVT_end'] = isset($datetime_data['DTT_EVT_end'])
322
-                                            && ! empty($datetime_data['DTT_EVT_end'])
323
-                ? $datetime_data['DTT_EVT_end']
324
-                : $datetime_data['DTT_EVT_start'];
325
-
326
-            $datetime_values              = [
327
-                'DTT_ID'          => ! empty($datetime_data['DTT_ID'])
328
-                    ? $datetime_data['DTT_ID']
329
-                    : null,
330
-                'DTT_name'        => ! empty($datetime_data['DTT_name'])
331
-                    ? $datetime_data['DTT_name']
332
-                    : '',
333
-                'DTT_description' => ! empty($datetime_data['DTT_description'])
334
-                    ? $datetime_data['DTT_description']
335
-                    : '',
336
-                'DTT_EVT_start'   => $datetime_data['DTT_EVT_start'],
337
-                'DTT_EVT_end'     => $datetime_data['DTT_EVT_end'],
338
-                'DTT_reg_limit'   => empty($datetime_data['DTT_reg_limit'])
339
-                    ? EE_INF
340
-                    : $datetime_data['DTT_reg_limit'],
341
-                'DTT_order'       => ! isset($datetime_data['DTT_order'])
342
-                    ? $row
343
-                    : $datetime_data['DTT_order'],
344
-            ];
345
-
346
-            // if we have an id then let's get existing object first and then set the new values.
347
-            // Otherwise we instantiate a new object for save.
348
-            if (! empty($datetime_data['DTT_ID'])) {
349
-                $datetime = EEM_Datetime::instance($timezone)->get_one_by_ID($datetime_data['DTT_ID']);
350
-                // set date and time format according to what is set in this class.
351
-                $datetime->set_date_format($this->_date_format_strings['date']);
352
-                $datetime->set_time_format($this->_date_format_strings['time']);
353
-                foreach ($datetime_values as $field => $value) {
354
-                    $datetime->set($field, $value);
355
-                }
356
-
357
-                // make sure the $datetime_id here is saved just in case
358
-                // after the add_relation_to() the autosave replaces it.
359
-                // We need to do this so we dont' TRASH the parent DTT.
360
-                // (save the ID for both key and value to avoid duplications)
361
-                $saved_datetime_ids[ $datetime->ID() ] = $datetime->ID();
362
-            } else {
363
-                $datetime = EE_Datetime::new_instance(
364
-                    $datetime_values,
365
-                    $timezone,
366
-                    [$this->_date_format_strings['date'], $this->_date_format_strings['time']]
367
-                );
368
-                foreach ($datetime_values as $field => $value) {
369
-                    $datetime->set($field, $value);
370
-                }
371
-            }
372
-            $datetime->save();
373
-            do_action(
374
-                'AHEE__espresso_events_Pricing_Hooks___update_datetimes_after_save',
375
-                $datetime,
376
-                $row,
377
-                $datetime_data,
378
-                $data
379
-            );
380
-            $datetime = $event->_add_relation_to($datetime, 'Datetime');
381
-            // before going any further make sure our dates are setup correctly
382
-            // so that the end date is always equal or greater than the start date.
383
-            if ($datetime->get_raw('DTT_EVT_start') > $datetime->get_raw('DTT_EVT_end')) {
384
-                $datetime->set('DTT_EVT_end', $datetime->get('DTT_EVT_start'));
385
-                $datetime = EEH_DTT_Helper::date_time_add($datetime, 'DTT_EVT_end', 'days');
386
-                $datetime->save();
387
-            }
388
-            // now we have to make sure we add the new DTT_ID to the $saved_datetime_ids array
389
-            // because it is possible there was a new one created for the autosave.
390
-            // (save the ID for both key and value to avoid duplications)
391
-            $DTT_ID                        = $datetime->ID();
392
-            $saved_datetime_ids[ $DTT_ID ] = $DTT_ID;
393
-            $saved_datetime_objs[ $row ]   = $datetime;
394
-            // @todo if ANY of these updates fail then we want the appropriate global error message.
395
-        }
396
-        $event->save();
397
-        // now we need to REMOVE any datetimes that got deleted.
398
-        // Keep in mind that this process will only kick in for datetimes that don't have any DTT_sold on them.
399
-        // So its safe to permanently delete at this point.
400
-        $old_datetimes = explode(',', $data['datetime_IDs']);
401
-        $old_datetimes = $old_datetimes[0] === '' ? [] : $old_datetimes;
402
-        if (is_array($old_datetimes)) {
403
-            $datetimes_to_delete = array_diff($old_datetimes, $saved_datetime_ids);
404
-            foreach ($datetimes_to_delete as $id) {
405
-                $id = absint($id);
406
-                if (empty($id)) {
407
-                    continue;
408
-                }
409
-                $datetime_to_remove = EE_Registry::instance()->load_model('Datetime')->get_one_by_ID($id);
410
-                // remove ticket relationships.
411
-                $related_tickets = $datetime_to_remove->get_many_related('Ticket');
412
-                foreach ($related_tickets as $ticket) {
413
-                    $datetime_to_remove->_remove_relation_to($ticket, 'Ticket');
414
-                }
415
-                $event->_remove_relation_to($id, 'Datetime');
416
-                $datetime_to_remove->refresh_cache_of_related_objects();
417
-            }
418
-        }
419
-        return $saved_datetime_objs;
420
-    }
421
-
422
-
423
-    /**
424
-     * update tickets
425
-     *
426
-     * @param EE_Event      $event           Event object being updated
427
-     * @param EE_Datetime[] $saved_datetimes an array of datetime ids being updated
428
-     * @param array         $data            incoming request data
429
-     * @return EE_Ticket[]
430
-     * @throws Exception
431
-     * @throws ReflectionException
432
-     * @throws InvalidInterfaceException
433
-     * @throws InvalidDataTypeException
434
-     * @throws InvalidArgumentException
435
-     * @throws EE_Error
436
-     */
437
-    protected function _update_tickets(EE_Event $event, array $saved_datetimes, array $data): array
438
-    {
439
-        $new_ticket = null;
440
-        // stripslashes because WP filtered the $_POST ($data) array to add slashes
441
-        $data          = stripslashes_deep($data);
442
-        $timezone      = $data['timezone_string'] ?? null;
443
-        $saved_tickets = [];
444
-        $old_tickets   = isset($data['ticket_IDs']) ? explode(',', $data['ticket_IDs']) : [];
445
-        if (empty($data['edit_tickets']) || ! is_array($data['edit_tickets'])) {
446
-            throw new InvalidArgumentException(
447
-                esc_html__(
448
-                    'The "edit_tickets" array is invalid therefore the event can not be updated.',
449
-                    'event_espresso'
450
-                )
451
-            );
452
-        }
453
-        foreach ($data['edit_tickets'] as $row => $ticket_data) {
454
-            $update_prices = $create_new_TKT = false;
455
-            // figure out what datetimes were added to the ticket
456
-            // and what datetimes were removed from the ticket in the session.
457
-            $starting_ticket_datetime_rows = explode(',', $data['starting_ticket_datetime_rows'][ $row ]);
458
-            $ticket_datetime_rows          = explode(',', $data['ticket_datetime_rows'][ $row ]);
459
-            $datetimes_added               = array_diff($ticket_datetime_rows, $starting_ticket_datetime_rows);
460
-            $datetimes_removed             = array_diff($starting_ticket_datetime_rows, $ticket_datetime_rows);
461
-            // trim inputs to ensure any excess whitespace is removed.
462
-            $ticket_data = array_map(
463
-                function ($ticket_data) {
464
-                    return is_array($ticket_data) ? $ticket_data : trim($ticket_data);
465
-                },
466
-                $ticket_data
467
-            );
468
-            // note we are doing conversions to floats here instead of allowing EE_Money_Field to handle
469
-            // because we're doing calculations prior to using the models.
470
-            // note incoming ['TKT_price'] value is already in standard notation (via js).
471
-            $ticket_price = isset($ticket_data['TKT_price'])
472
-                ? round((float) $ticket_data['TKT_price'], 3)
473
-                : 0;
474
-            // note incoming base price needs converted from localized value.
475
-            $base_price = isset($ticket_data['TKT_base_price'])
476
-                ? EEH_Money::convert_to_float_from_localized_money($ticket_data['TKT_base_price'])
477
-                : 0;
478
-            // if ticket price == 0 and $base_price != 0 then ticket price == base_price
479
-            $ticket_price  = $ticket_price === 0 && $base_price !== 0
480
-                ? $base_price
481
-                : $ticket_price;
482
-            $base_price_id = $ticket_data['TKT_base_price_ID'] ?? 0;
483
-            $price_rows    = is_array($data['edit_prices']) && isset($data['edit_prices'][ $row ])
484
-                ? $data['edit_prices'][ $row ]
485
-                : [];
486
-            $now           = null;
487
-            if (empty($ticket_data['TKT_start_date'])) {
488
-                // lets' use now in the set timezone.
489
-                $now                           = new DateTime('now', new DateTimeZone($event->get_timezone()));
490
-                $ticket_data['TKT_start_date'] = $now->format($this->_date_time_format);
491
-            }
492
-            if (empty($ticket_data['TKT_end_date'])) {
493
-                /**
494
-                 * set the TKT_end_date to the first datetime attached to the ticket.
495
-                 */
496
-                $first_datetime              = $saved_datetimes[ reset($ticket_datetime_rows) ];
497
-                $ticket_data['TKT_end_date'] = $first_datetime->start_date_and_time($this->_date_time_format);
498
-            }
499
-            $TKT_values = [
500
-                'TKT_ID'          => ! empty($ticket_data['TKT_ID']) ? $ticket_data['TKT_ID'] : null,
501
-                'TTM_ID'          => ! empty($ticket_data['TTM_ID']) ? $ticket_data['TTM_ID'] : 0,
502
-                'TKT_name'        => ! empty($ticket_data['TKT_name']) ? $ticket_data['TKT_name'] : '',
503
-                'TKT_description' => ! empty($ticket_data['TKT_description'])
504
-                                     && $ticket_data['TKT_description'] !== esc_html__(
505
-                                         'You can modify this description',
506
-                                         'event_espresso'
507
-                                     )
508
-                    ? $ticket_data['TKT_description']
509
-                    : '',
510
-                'TKT_start_date'  => $ticket_data['TKT_start_date'],
511
-                'TKT_end_date'    => $ticket_data['TKT_end_date'],
512
-                'TKT_qty'         => ! isset($ticket_data['TKT_qty']) || $ticket_data['TKT_qty'] === ''
513
-                    ? EE_INF
514
-                    : $ticket_data['TKT_qty'],
515
-                'TKT_uses'        => ! isset($ticket_data['TKT_uses']) || $ticket_data['TKT_uses'] === ''
516
-                    ? EE_INF
517
-                    : $ticket_data['TKT_uses'],
518
-                'TKT_min'         => empty($ticket_data['TKT_min']) ? 0 : $ticket_data['TKT_min'],
519
-                'TKT_max'         => empty($ticket_data['TKT_max']) ? EE_INF : $ticket_data['TKT_max'],
520
-                'TKT_row'         => $row,
521
-                'TKT_order'       => $ticket_data['TKT_order'] ?? 0,
522
-                'TKT_taxable'     => ! empty($ticket_data['TKT_taxable']) ? 1 : 0,
523
-                'TKT_required'    => ! empty($ticket_data['TKT_required']) ? 1 : 0,
524
-                'TKT_price'       => $ticket_price,
525
-            ];
526
-            // if this is a default TKT, then we need to set the TKT_ID to 0 and update accordingly,
527
-            // which means in turn that the prices will become new prices as well.
528
-            if (isset($ticket_data['TKT_is_default']) && $ticket_data['TKT_is_default']) {
529
-                $TKT_values['TKT_ID']         = 0;
530
-                $TKT_values['TKT_is_default'] = 0;
531
-                $update_prices                = true;
532
-            }
533
-            // if we have a TKT_ID then we need to get that existing TKT_obj and update it
534
-            // we actually do our saves ahead of doing any add_relations to
535
-            // because its entirely possible that this ticket wasn't removed or added to any datetime in the session
536
-            // but DID have it's items modified.
537
-            // keep in mind that if the TKT has been sold (and we have changed pricing information),
538
-            // then we won't be updating the ticket but instead a new ticket will be created and the old one archived.
539
-            if (absint($TKT_values['TKT_ID'])) {
540
-                $ticket = EEM_Ticket::instance($timezone)->get_one_by_ID($ticket_data['TKT_ID']);
541
-                if ($ticket instanceof EE_Ticket) {
542
-                    $ticket = $this->_update_ticket_datetimes(
543
-                        $ticket,
544
-                        $saved_datetimes,
545
-                        $datetimes_added,
546
-                        $datetimes_removed
547
-                    );
548
-                    // are there any registrations using this ticket ?
549
-                    $tickets_sold = $ticket->count_related(
550
-                        'Registration',
551
-                        [
552
-                            [
553
-                                'STS_ID' => ['NOT IN', [EEM_Registration::status_id_incomplete]],
554
-                            ],
555
-                        ]
556
-                    );
557
-                    // set ticket formats
558
-                    $ticket->set_date_format($this->_date_format_strings['date']);
559
-                    $ticket->set_time_format($this->_date_format_strings['time']);
560
-                    // let's just check the total price for the existing ticket
561
-                    // and determine if it matches the new total price.
562
-                    // if they are different then we create a new ticket (if tickets sold)
563
-                    // if they aren't different then we go ahead and modify existing ticket.
564
-                    $create_new_TKT = $tickets_sold > 0 && $ticket_price !== $ticket->price() && ! $ticket->deleted();
565
-                    // set new values
566
-                    foreach ($TKT_values as $field => $value) {
567
-                        if ($field === 'TKT_qty') {
568
-                            $ticket->set_qty($value);
569
-                        } else {
570
-                            $ticket->set($field, $value);
571
-                        }
572
-                    }
573
-                    // if $create_new_TKT is false then we can safely update the existing ticket.
574
-                    // Otherwise we have to create a new ticket.
575
-                    if ($create_new_TKT) {
576
-                        $new_ticket = $this->_duplicate_ticket(
577
-                            $ticket,
578
-                            $price_rows,
579
-                            $ticket_price,
580
-                            $base_price,
581
-                            $base_price_id
582
-                        );
583
-                    }
584
-                }
585
-            } else {
586
-                // no TKT_id so a new TKT
587
-                $ticket = EE_Ticket::new_instance(
588
-                    $TKT_values,
589
-                    $timezone,
590
-                    [$this->_date_format_strings['date'], $this->_date_format_strings['time']]
591
-                );
592
-                if ($ticket instanceof EE_Ticket) {
593
-                    // make sure ticket has an ID of setting relations won't work
594
-                    $ticket->save();
595
-                    $ticket        = $this->_update_ticket_datetimes(
596
-                        $ticket,
597
-                        $saved_datetimes,
598
-                        $datetimes_added,
599
-                        $datetimes_removed
600
-                    );
601
-                    $update_prices = true;
602
-                }
603
-            }
604
-            // make sure any current values have been saved.
605
-            // $ticket->save();
606
-            // before going any further make sure our dates are setup correctly
607
-            // so that the end date is always equal or greater than the start date.
608
-            if ($ticket->get_raw('TKT_start_date') > $ticket->get_raw('TKT_end_date')) {
609
-                $ticket->set('TKT_end_date', $ticket->get('TKT_start_date'));
610
-                $ticket = EEH_DTT_Helper::date_time_add($ticket, 'TKT_end_date', 'days');
611
-            }
612
-            // let's make sure the base price is handled
613
-            $ticket = ! $create_new_TKT
614
-                ? $this->_add_prices_to_ticket(
615
-                    [],
616
-                    $ticket,
617
-                    $update_prices,
618
-                    $base_price,
619
-                    $base_price_id
620
-                )
621
-                : $ticket;
622
-            // add/update price_modifiers
623
-            $ticket = ! $create_new_TKT
624
-                ? $this->_add_prices_to_ticket($price_rows, $ticket, $update_prices)
625
-                : $ticket;
626
-            // need to make sue that the TKT_price is accurate after saving the prices.
627
-            $ticket->ensure_TKT_Price_correct();
628
-            // handle CREATING a default ticket from the incoming ticket but ONLY if this isn't an autosave.
629
-            if (! defined('DOING_AUTOSAVE') && ! empty($ticket_data['TKT_is_default_selector'])) {
630
-                $new_default = clone $ticket;
631
-                $new_default->set('TKT_ID', 0);
632
-                $new_default->set('TKT_is_default', 1);
633
-                $new_default->set('TKT_row', 1);
634
-                $new_default->set('TKT_price', $ticket_price);
635
-                // remove any datetime relations cause we DON'T want datetime relations attached
636
-                // (note this is just removing the cached relations in the object)
637
-                $new_default->_remove_relations('Datetime');
638
-                // @todo we need to add the current attached prices as new prices to the new default ticket.
639
-                $new_default = $this->_add_prices_to_ticket(
640
-                    $price_rows,
641
-                    $new_default,
642
-                    true
643
-                );
644
-                // don't forget the base price!
645
-                $new_default = $this->_add_prices_to_ticket(
646
-                    [],
647
-                    $new_default,
648
-                    true,
649
-                    $base_price,
650
-                    $base_price_id
651
-                );
652
-                $new_default->save();
653
-                do_action(
654
-                    'AHEE__espresso_events_Pricing_Hooks___update_tkts_new_default_ticket',
655
-                    $new_default,
656
-                    $row,
657
-                    $ticket,
658
-                    $data
659
-                );
660
-            }
661
-            // DO ALL datetime relationships for both current tickets and any archived tickets
662
-            // for the given datetime that are related to the current ticket.
663
-            // TODO... not sure exactly how we're going to do this considering we don't know
664
-            // what current ticket the archived tickets are related to
665
-            // (and TKT_parent is used for autosaves so that's not a field we can reliably use).
666
-            // let's assign any tickets that have been setup to the saved_tickets tracker
667
-            // save existing TKT
668
-            $ticket->save();
669
-            if ($create_new_TKT && $new_ticket instanceof EE_Ticket) {
670
-                // save new TKT
671
-                $new_ticket->save();
672
-                // add new ticket to array
673
-                $saved_tickets[ $new_ticket->ID() ] = $new_ticket;
674
-                do_action(
675
-                    'AHEE__espresso_events_Pricing_Hooks___update_tkts_new_ticket',
676
-                    $new_ticket,
677
-                    $row,
678
-                    $ticket_data,
679
-                    $data
680
-                );
681
-            } else {
682
-                // add ticket to saved tickets
683
-                $saved_tickets[ $ticket->ID() ] = $ticket;
684
-                do_action(
685
-                    'AHEE__espresso_events_Pricing_Hooks___update_tkts_update_ticket',
686
-                    $ticket,
687
-                    $row,
688
-                    $ticket_data,
689
-                    $data
690
-                );
691
-            }
692
-        }
693
-        // now we need to handle tickets actually "deleted permanently".
694
-        // There are cases where we'd want this to happen
695
-        // (i.e. autosaves are happening and then in between autosaves the user trashes a ticket).
696
-        // Or a draft event was saved and in the process of editing a ticket is trashed.
697
-        // No sense in keeping all the related data in the db!
698
-        $old_tickets     = isset($old_tickets[0]) && $old_tickets[0] === '' ? [] : $old_tickets;
699
-        $tickets_removed = array_diff($old_tickets, array_keys($saved_tickets));
700
-        foreach ($tickets_removed as $id) {
701
-            $id = absint($id);
702
-            // get the ticket for this id
703
-            $ticket_to_remove = EE_Registry::instance()->load_model('Ticket')->get_one_by_ID($id);
704
-            // if this ticket is a default ticket we leave it alone cause it won't be attached to the datetime
705
-            if ($ticket_to_remove->get('TKT_is_default')) {
706
-                continue;
707
-            }
708
-            // if this ticket has any registrations attached so then we just ARCHIVE
709
-            // because we don't actually permanently delete these tickets.
710
-            if ($ticket_to_remove->count_related('Registration') > 0) {
711
-                $ticket_to_remove->delete();
712
-                continue;
713
-            }
714
-            // need to get all the related datetimes on this ticket and remove from every single one of them
715
-            // (remember this process can ONLY kick off if there are NO tickets_sold)
716
-            $datetimes = $ticket_to_remove->get_many_related('Datetime');
717
-            foreach ($datetimes as $datetime) {
718
-                $ticket_to_remove->_remove_relation_to($datetime, 'Datetime');
719
-            }
720
-            // need to do the same for prices (except these prices can also be deleted because again,
721
-            // tickets can only be trashed if they don't have any TKTs sold (otherwise they are just archived))
722
-            $ticket_to_remove->delete_related_permanently('Price');
723
-            do_action('AHEE__espresso_events_Pricing_Hooks___update_tkts_delete_ticket', $ticket_to_remove);
724
-            // finally let's delete this ticket
725
-            // (which should not be blocked at this point b/c we've removed all our relationships)
726
-            $ticket_to_remove->delete_permanently();
727
-        }
728
-        return $saved_tickets;
729
-    }
730
-
731
-
732
-    /**
733
-     * @access  protected
734
-     * @param EE_Ticket     $ticket
735
-     * @param EE_Datetime[] $saved_datetimes
736
-     * @param int[]         $added_datetimes
737
-     * @param int[]         $removed_datetimes
738
-     * @return EE_Ticket
739
-     * @throws EE_Error
740
-     * @throws ReflectionException
741
-     */
742
-    protected function _update_ticket_datetimes(
743
-        EE_Ticket $ticket,
744
-        array $saved_datetimes = [],
745
-        array $added_datetimes = [],
746
-        array $removed_datetimes = []
747
-    ): EE_Ticket {
748
-        // to start we have to add the ticket to all the datetimes its supposed to be with,
749
-        // and removing the ticket from datetimes it got removed from.
750
-        // first let's add datetimes
751
-        if (! empty($added_datetimes) && is_array($added_datetimes)) {
752
-            foreach ($added_datetimes as $row_id) {
753
-                if (isset($saved_datetimes[ $row_id ]) && $saved_datetimes[ $row_id ] instanceof EE_Datetime) {
754
-                    $ticket->_add_relation_to($saved_datetimes[ $row_id ], 'Datetime');
755
-                    // Is this an existing ticket (has an ID) and does it have any sold?
756
-                    // If so, then we need to add that to the DTT sold because this DTT is getting added.
757
-                    if ($ticket->ID() && $ticket->sold() > 0) {
758
-                        $saved_datetimes[ $row_id ]->increaseSold($ticket->sold(), false);
759
-                    }
760
-                }
761
-            }
762
-        }
763
-        // then remove datetimes
764
-        if (! empty($removed_datetimes) && is_array($removed_datetimes)) {
765
-            foreach ($removed_datetimes as $row_id) {
766
-                // its entirely possible that a datetime got deleted (instead of just removed from relationship.
767
-                // So make sure we skip over this if the datetime isn't in the $saved_datetimes array)
768
-                if (isset($saved_datetimes[ $row_id ]) && $saved_datetimes[ $row_id ] instanceof EE_Datetime) {
769
-                    $ticket->_remove_relation_to($saved_datetimes[ $row_id ], 'Datetime');
770
-                }
771
-            }
772
-        }
773
-        // cap ticket qty by datetime reg limits
774
-        $ticket->set_qty(min($ticket->qty(), $ticket->qty('reg_limit')));
775
-        return $ticket;
776
-    }
777
-
778
-
779
-    /**
780
-     * @access  protected
781
-     * @param EE_Ticket $ticket
782
-     * @param array     $price_rows
783
-     * @param int|float $ticket_price
784
-     * @param int|float $base_price
785
-     * @param int       $base_price_id
786
-     * @return EE_Ticket
787
-     * @throws ReflectionException
788
-     * @throws InvalidArgumentException
789
-     * @throws InvalidInterfaceException
790
-     * @throws InvalidDataTypeException
791
-     * @throws EE_Error
792
-     */
793
-    protected function _duplicate_ticket(
794
-        EE_Ticket $ticket,
795
-        array $price_rows = [],
796
-        $ticket_price = 0,
797
-        $base_price = 0,
798
-        int $base_price_id = 0
799
-    ): EE_Ticket {
800
-        // create new ticket that's a copy of the existing
801
-        // except a new id of course (and not archived)
802
-        // AND has the new TKT_price associated with it.
803
-        $new_ticket = clone $ticket;
804
-        $new_ticket->set('TKT_ID', 0);
805
-        $new_ticket->set_deleted(0);
806
-        $new_ticket->set_price($ticket_price);
807
-        $new_ticket->set_sold(0);
808
-        // let's get a new ID for this ticket
809
-        $new_ticket->save();
810
-        // we also need to make sure this new ticket gets the same datetime attachments as the archived ticket
811
-        $datetimes_on_existing = $ticket->datetimes();
812
-        $new_ticket            = $this->_update_ticket_datetimes(
813
-            $new_ticket,
814
-            $datetimes_on_existing,
815
-            array_keys($datetimes_on_existing)
816
-        );
817
-        // $ticket will get archived later b/c we are NOT adding it to the saved_tickets array.
818
-        // if existing $ticket has sold amount, then we need to adjust the qty for the new TKT to = the remaining
819
-        // available.
820
-        if ($ticket->sold() > 0) {
821
-            $new_qty = $ticket->qty() - $ticket->sold();
822
-            $new_ticket->set_qty($new_qty);
823
-        }
824
-        // now we update the prices just for this ticket
825
-        $new_ticket = $this->_add_prices_to_ticket($price_rows, $new_ticket, true);
826
-        // and we update the base price
827
-        return $this->_add_prices_to_ticket(
828
-            [],
829
-            $new_ticket,
830
-            true,
831
-            $base_price,
832
-            $base_price_id
833
-        );
834
-    }
835
-
836
-
837
-    /**
838
-     * This attaches a list of given prices to a ticket.
839
-     * Note we dont' have to worry about ever removing relationships (or archiving prices) because if there is a change
840
-     * in price information on a ticket, a new ticket is created anyways so the archived ticket will retain the old
841
-     * price info and prices are automatically "archived" via the ticket.
842
-     *
843
-     * @access  private
844
-     * @param array     $prices        Array of prices from the form.
845
-     * @param EE_Ticket $ticket        EE_Ticket object that prices are being attached to.
846
-     * @param bool      $new_prices    Whether attach existing incoming prices or create new ones.
847
-     * @param int|bool  $base_price    if FALSE then NOT doing a base price add.
848
-     * @param int|bool  $base_price_id if present then this is the base_price_id being updated.
849
-     * @return EE_Ticket
850
-     * @throws ReflectionException
851
-     * @throws InvalidArgumentException
852
-     * @throws InvalidInterfaceException
853
-     * @throws InvalidDataTypeException
854
-     * @throws EE_Error
855
-     */
856
-    protected function _add_prices_to_ticket(
857
-        array $prices,
858
-        EE_Ticket $ticket,
859
-        bool $new_prices = false,
860
-        $base_price = false,
861
-        $base_price_id = false
862
-    ): EE_Ticket {
863
-        // let's just get any current prices that may exist on the given ticket
864
-        // so we can remove any prices that got trashed in this session.
865
-        $current_prices_on_ticket = $base_price !== false
866
-            ? $ticket->base_price(true)
867
-            : $ticket->price_modifiers();
868
-        $updated_prices           = [];
869
-        // if $base_price ! FALSE then updating a base price.
870
-        if ($base_price !== false) {
871
-            $prices[1] = [
872
-                'PRC_ID'     => $new_prices || $base_price_id === 1 ? null : $base_price_id,
873
-                'PRT_ID'     => 1,
874
-                'PRC_amount' => $base_price,
875
-                'PRC_name'   => $ticket->get('TKT_name'),
876
-                'PRC_desc'   => $ticket->get('TKT_description'),
877
-            ];
878
-        }
879
-        // possibly need to save ticket
880
-        if (! $ticket->ID()) {
881
-            $ticket->save();
882
-        }
883
-        foreach ($prices as $row => $prc) {
884
-            $prt_id = ! empty($prc['PRT_ID']) ? $prc['PRT_ID'] : null;
885
-            if (empty($prt_id)) {
886
-                continue;
887
-            } //prices MUST have a price type id.
888
-            $PRC_values = [
889
-                'PRC_ID'         => ! empty($prc['PRC_ID']) ? $prc['PRC_ID'] : null,
890
-                'PRT_ID'         => $prt_id,
891
-                'PRC_amount'     => ! empty($prc['PRC_amount']) ? $prc['PRC_amount'] : 0,
892
-                'PRC_name'       => ! empty($prc['PRC_name']) ? $prc['PRC_name'] : '',
893
-                'PRC_desc'       => ! empty($prc['PRC_desc']) ? $prc['PRC_desc'] : '',
894
-                'PRC_is_default' => false,
895
-                // make sure we set PRC_is_default to false for all ticket saves from event_editor
896
-                'PRC_order'      => $row,
897
-            ];
898
-            if ($new_prices || empty($PRC_values['PRC_ID'])) {
899
-                $PRC_values['PRC_ID'] = 0;
900
-                $price                = EE_Registry::instance()->load_class(
901
-                    'Price',
902
-                    [$PRC_values],
903
-                    false,
904
-                    false
905
-                );
906
-            } else {
907
-                $price = EE_Registry::instance()->load_model('Price')->get_one_by_ID($prc['PRC_ID']);
908
-                // update this price with new values
909
-                foreach ($PRC_values as $field => $value) {
910
-                    $price->set($field, $value);
911
-                }
912
-            }
913
-            $price->save();
914
-            $updated_prices[ $price->ID() ] = $price;
915
-            $ticket->_add_relation_to($price, 'Price');
916
-        }
917
-        // now let's remove any prices that got removed from the ticket
918
-        if (! empty($current_prices_on_ticket)) {
919
-            $current          = array_keys($current_prices_on_ticket);
920
-            $updated          = array_keys($updated_prices);
921
-            $prices_to_remove = array_diff($current, $updated);
922
-            if (! empty($prices_to_remove)) {
923
-                foreach ($prices_to_remove as $prc_id) {
924
-                    $p = $current_prices_on_ticket[ $prc_id ];
925
-                    $ticket->_remove_relation_to($p, 'Price');
926
-                    // delete permanently the price
927
-                    $p->delete_permanently();
928
-                }
929
-            }
930
-        }
931
-        return $ticket;
932
-    }
933
-
934
-
935
-    /**
936
-     * @throws ReflectionException
937
-     * @throws InvalidArgumentException
938
-     * @throws InvalidInterfaceException
939
-     * @throws InvalidDataTypeException
940
-     * @throws DomainException
941
-     * @throws EE_Error
942
-     */
943
-    public function pricing_metabox()
944
-    {
945
-        $existing_datetime_ids = $existing_ticket_ids = $datetime_tickets = $ticket_datetimes = [];
946
-        $event                 = $this->_adminpage_obj->get_cpt_model_obj();
947
-
948
-        // set is_creating_event property.
949
-        $EVT_ID                   = $event->ID();
950
-        $this->_is_creating_event = empty($this->_req_data['post']);
951
-
952
-        // default main template args
953
-        $main_template_args = [
954
-            'event_datetime_help_link' => EEH_Template::get_help_tab_link(
955
-                'event_editor_event_datetimes_help_tab',
956
-                $this->_adminpage_obj->page_slug,
957
-                $this->_adminpage_obj->get_req_action()
958
-            ),
959
-
960
-            // todo need to add a filter to the template for the help text
961
-            // in the Events_Admin_Page core file so we can add further help
962
-            'add_new_dtt_help_link'    => EEH_Template::get_help_tab_link(
963
-                'add_new_dtt_info',
964
-                $this->_adminpage_obj->page_slug,
965
-                $this->_adminpage_obj->get_req_action()
966
-            ),
967
-            // todo need to add this help info id to the Events_Admin_Page core file so we can access it here.
968
-            'datetime_rows'            => '',
969
-            'show_tickets_container'   => '',
970
-            'ticket_rows'              => '',
971
-            'ee_collapsible_status'    => ' ee-collapsible-open',
972
-        ];
973
-        $timezone           = $event instanceof EE_Event ? $event->timezone_string() : null;
974
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
975
-
976
-        /**
977
-         * 1. Start with retrieving Datetimes
978
-         * 2. For each datetime get related tickets
979
-         * 3. For each ticket get related prices
980
-         */
981
-        /** @var EEM_Datetime $datetime_model */
982
-        $datetime_model                       = EE_Registry::instance()->load_model('Datetime', [$timezone]);
983
-        $datetimes                            = $datetime_model->get_all_event_dates($EVT_ID);
984
-        $main_template_args['total_dtt_rows'] = count($datetimes);
985
-
986
-        /**
987
-         * @see https://events.codebasehq.com/projects/event-espresso/tickets/9486
988
-         * for why we are counting $datetime_row and then setting that on the Datetime object
989
-         */
990
-        $datetime_row = 1;
991
-        foreach ($datetimes as $datetime) {
992
-            $DTT_ID = $datetime->get('DTT_ID');
993
-            $datetime->set('DTT_order', $datetime_row);
994
-            $existing_datetime_ids[] = $DTT_ID;
995
-            // tickets attached
996
-            $related_tickets = $datetime->ID() > 0
997
-                ? $datetime->get_many_related(
998
-                    'Ticket',
999
-                    [
1000
-                        [
1001
-                            'OR' => ['TKT_deleted' => 1, 'TKT_deleted*' => 0],
1002
-                        ],
1003
-                        'default_where_conditions' => 'none',
1004
-                        'order_by'                 => ['TKT_order' => 'ASC'],
1005
-                    ]
1006
-                )
1007
-                : [];
1008
-            // if there are no related tickets this is likely a new event OR auto-draft
1009
-            // event so we need to generate the default tickets because datetimes
1010
-            // ALWAYS have at least one related ticket!!.  EXCEPT, we dont' do this if there is already more than one
1011
-            // datetime on the event.
1012
-            if (empty($related_tickets) && count($datetimes) < 2) {
1013
-                /** @var EEM_Ticket $ticket_model */
1014
-                $ticket_model    = EE_Registry::instance()->load_model('Ticket');
1015
-                $related_tickets = $ticket_model->get_all_default_tickets();
1016
-                // this should be ordered by TKT_ID, so let's grab the first default ticket
1017
-                // (which will be the main default) and ensure it has any default prices added to it (but do NOT save).
1018
-                $default_prices      = EEM_Price::instance()->get_all_default_prices();
1019
-                $main_default_ticket = reset($related_tickets);
1020
-                if ($main_default_ticket instanceof EE_Ticket) {
1021
-                    foreach ($default_prices as $default_price) {
1022
-                        if ($default_price instanceof EE_Price && $default_price->is_base_price()) {
1023
-                            continue;
1024
-                        }
1025
-                        $main_default_ticket->cache('Price', $default_price);
1026
-                    }
1027
-                }
1028
-            }
1029
-            // we can't actually setup rows in this loop yet cause we don't know all
1030
-            // the unique tickets for this event yet (tickets are linked through all datetimes).
1031
-            // So we're going to temporarily cache some of that information.
1032
-            // loop through and setup the ticket rows and make sure the order is set.
1033
-            foreach ($related_tickets as $ticket) {
1034
-                $TKT_ID     = $ticket->get('TKT_ID');
1035
-                $ticket_row = $ticket->get('TKT_row');
1036
-                // we only want unique tickets in our final display!!
1037
-                if (! in_array($TKT_ID, $existing_ticket_ids, true)) {
1038
-                    $existing_ticket_ids[] = $TKT_ID;
1039
-                    $all_tickets[]         = $ticket;
1040
-                }
1041
-                // temporary cache of this ticket info for this datetime for later processing of datetime rows.
1042
-                $datetime_tickets[ $DTT_ID ][] = $ticket_row;
1043
-                // temporary cache of this datetime info for this ticket for later processing of ticket rows.
1044
-                if (
1045
-                    ! isset($ticket_datetimes[ $TKT_ID ])
1046
-                    || ! in_array($datetime_row, $ticket_datetimes[ $TKT_ID ], true)
1047
-                ) {
1048
-                    $ticket_datetimes[ $TKT_ID ][] = $datetime_row;
1049
-                }
1050
-            }
1051
-            $datetime_row++;
1052
-        }
1053
-        $main_template_args['total_ticket_rows']     = count($existing_ticket_ids);
1054
-        $main_template_args['existing_ticket_ids']   = implode(',', $existing_ticket_ids);
1055
-        $main_template_args['existing_datetime_ids'] = implode(',', $existing_datetime_ids);
1056
-        // sort $all_tickets by order
1057
-        usort(
1058
-            $all_tickets,
1059
-            function (EE_Ticket $a, EE_Ticket $b) {
1060
-                $a_order = (int) $a->get('TKT_order');
1061
-                $b_order = (int) $b->get('TKT_order');
1062
-                if ($a_order === $b_order) {
1063
-                    return 0;
1064
-                }
1065
-                return ($a_order < $b_order) ? -1 : 1;
1066
-            }
1067
-        );
1068
-        // k NOW we have all the data we need for setting up the datetime rows
1069
-        // and ticket rows so we start our datetime loop again.
1070
-        $datetime_row = 1;
1071
-        foreach ($datetimes as $datetime) {
1072
-            $main_template_args['datetime_rows'] .= $this->_get_datetime_row(
1073
-                $datetime_row,
1074
-                $datetime,
1075
-                $datetime_tickets,
1076
-                $all_tickets,
1077
-                false,
1078
-                $datetimes
1079
-            );
1080
-            $datetime_row++;
1081
-        }
1082
-        // then loop through all tickets for the ticket rows.
1083
-        $ticket_row = 1;
1084
-        foreach ($all_tickets as $ticket) {
1085
-            $main_template_args['ticket_rows'] .= $this->_get_ticket_row(
1086
-                $ticket_row,
1087
-                $ticket,
1088
-                $ticket_datetimes,
1089
-                $datetimes,
1090
-                false,
1091
-                $all_tickets
1092
-            );
1093
-            $ticket_row++;
1094
-        }
1095
-        $main_template_args['ticket_js_structure'] = $this->_get_ticket_js_structure($datetimes, $all_tickets);
1096
-
1097
-        $status_change_notice = LoaderFactory::getLoader()->getShared(
1098
-            'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
1099
-        );
1100
-
1101
-        $main_template_args['status_change_notice'] = $status_change_notice->display(
1102
-            '__event-editor',
1103
-            'espresso-events'
1104
-        );
1105
-
1106
-        EEH_Template::display_template(
1107
-            PRICING_TEMPLATE_PATH . 'event_tickets_metabox_main.template.php',
1108
-            $main_template_args
1109
-        );
1110
-    }
1111
-
1112
-
1113
-    /**
1114
-     * @param int|string  $datetime_row
1115
-     * @param EE_Datetime $datetime
1116
-     * @param array       $datetime_tickets
1117
-     * @param array       $all_tickets
1118
-     * @param bool        $default
1119
-     * @param array       $all_datetimes
1120
-     * @return string
1121
-     * @throws DomainException
1122
-     * @throws EE_Error
1123
-     * @throws ReflectionException
1124
-     */
1125
-    protected function _get_datetime_row(
1126
-        $datetime_row,
1127
-        EE_Datetime $datetime,
1128
-        array $datetime_tickets = [],
1129
-        array $all_tickets = [],
1130
-        bool $default = false,
1131
-        array $all_datetimes = []
1132
-    ): string {
1133
-        return EEH_Template::display_template(
1134
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_row_wrapper.template.php',
1135
-            [
1136
-                'dtt_edit_row'             => $this->_get_dtt_edit_row(
1137
-                    $datetime_row,
1138
-                    $datetime,
1139
-                    $default,
1140
-                    $all_datetimes
1141
-                ),
1142
-                'dtt_attached_tickets_row' => $this->_get_dtt_attached_tickets_row(
1143
-                    $datetime_row,
1144
-                    $datetime,
1145
-                    $datetime_tickets,
1146
-                    $all_tickets,
1147
-                    $default
1148
-                ),
1149
-                'dtt_row'                  => $default ? 'DTTNUM' : $datetime_row,
1150
-            ],
1151
-            true
1152
-        );
1153
-    }
1154
-
1155
-
1156
-    /**
1157
-     * This method is used to generate a datetime fields  edit row.
1158
-     * The same row is used to generate a row with valid DTT objects
1159
-     * and the default row that is used as the skeleton by the js.
1160
-     *
1161
-     * @param int|string       $datetime_row  The row number for the row being generated.
1162
-     * @param EE_Datetime|null $datetime
1163
-     * @param bool             $default       Whether a default row is being generated or not.
1164
-     * @param EE_Datetime[]    $all_datetimes This is the array of all datetimes used in the editor.
1165
-     * @return string
1166
-     * @throws EE_Error
1167
-     * @throws ReflectionException
1168
-     */
1169
-    protected function _get_dtt_edit_row(
1170
-        $datetime_row,
1171
-        ?EE_Datetime $datetime,
1172
-        bool $default,
1173
-        array $all_datetimes
1174
-    ): string {
1175
-        // if the incoming $datetime object is NOT an instance of EE_Datetime then force default to true.
1176
-        $default                     = ! $datetime instanceof EE_Datetime ? true : $default;
1177
-        $template_args               = [
1178
-            'dtt_row'              => $default ? 'DTTNUM' : $datetime_row,
1179
-            'event_datetimes_name' => $default ? 'DTTNAMEATTR' : 'edit_event_datetimes',
1180
-            'edit_dtt_expanded'    => '',
1181
-            'DTT_ID'               => $default ? '' : $datetime->ID(),
1182
-            'DTT_name'             => $default ? '' : $datetime->get_f('DTT_name'),
1183
-            'DTT_description'      => $default ? '' : $datetime->get_f('DTT_description'),
1184
-            'DTT_EVT_start'        => $default ? '' : $datetime->start_date($this->_date_time_format),
1185
-            'DTT_EVT_end'          => $default ? '' : $datetime->end_date($this->_date_time_format),
1186
-            'DTT_reg_limit'        => $default
1187
-                ? ''
1188
-                : $datetime->get_pretty(
1189
-                    'DTT_reg_limit',
1190
-                    'input'
1191
-                ),
1192
-            'DTT_order'            => $default ? 'DTTNUM' : $datetime_row,
1193
-            'dtt_sold'             => $default ? '0' : $datetime->get('DTT_sold'),
1194
-            'dtt_reserved'         => $default ? '0' : $datetime->reserved(),
1195
-            'clone_icon'           => ! empty($datetime) && $datetime->get('DTT_sold') > 0
1196
-                ? ''
1197
-                : 'clone-icon ee-icon ee-icon-clone clickable',
1198
-            'trash_icon'           => ! empty($datetime) && $datetime->get('DTT_sold') > 0
1199
-                ? 'dashicons dashicons-lock'
1200
-                : 'trash-icon dashicons dashicons-post-trash clickable',
1201
-            'reg_list_url'         => $default || ! $datetime->event() instanceof EE_Event
1202
-                ? ''
1203
-                : EE_Admin_Page::add_query_args_and_nonce(
1204
-                    [
1205
-                        'event_id' => $datetime->event()->ID(),
1206
-                        'datetime_id' => $datetime->ID(),
1207
-                        'use_filters' => true
1208
-                    ],
1209
-                    REG_ADMIN_URL
1210
-                ),
1211
-        ];
1212
-        $template_args['show_trash'] = count($all_datetimes) === 1
1213
-                                       && $template_args['trash_icon'] !== 'dashicons dashicons-lock'
1214
-            ? 'display:none'
1215
-            : '';
1216
-        // allow filtering of template args at this point.
1217
-        $template_args = apply_filters(
1218
-            'FHEE__espresso_events_Pricing_Hooks___get_dtt_edit_row__template_args',
1219
-            $template_args,
1220
-            $datetime_row,
1221
-            $datetime,
1222
-            $default,
1223
-            $all_datetimes,
1224
-            $this->_is_creating_event
1225
-        );
1226
-        return EEH_Template::display_template(
1227
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_edit_row.template.php',
1228
-            $template_args,
1229
-            true
1230
-        );
1231
-    }
1232
-
1233
-
1234
-    /**
1235
-     * @param int|string       $datetime_row
1236
-     * @param EE_Datetime|null $datetime
1237
-     * @param array            $datetime_tickets
1238
-     * @param array            $all_tickets
1239
-     * @param bool             $default
1240
-     * @return string
1241
-     * @throws DomainException
1242
-     * @throws EE_Error
1243
-     * @throws ReflectionException
1244
-     */
1245
-    protected function _get_dtt_attached_tickets_row(
1246
-        $datetime_row,
1247
-        ?EE_Datetime $datetime,
1248
-        array $datetime_tickets = [],
1249
-        array $all_tickets = [],
1250
-        bool $default = false
1251
-    ): string {
1252
-        $default = $default || ! $datetime instanceof EE_Datetime;
1253
-        $template_args = [
1254
-            'dtt_row'                           => $default ? 'DTTNUM' : $datetime_row,
1255
-            'event_datetimes_name'              => $default ? 'DTTNAMEATTR' : 'edit_event_datetimes',
1256
-            'DTT_description'                   => $default ? '' : $datetime->get_f('DTT_description'),
1257
-            'datetime_tickets_list'             => $default ? '<li class="hidden"></li>' : '',
1258
-            'show_tickets_row'                  => 'display:none;',
1259
-            'add_new_datetime_ticket_help_link' => EEH_Template::get_help_tab_link(
1260
-                'add_new_ticket_via_datetime',
1261
-                $this->_adminpage_obj->page_slug,
1262
-                $this->_adminpage_obj->get_req_action()
1263
-            ),
1264
-            // todo need to add this help info id to the Events_Admin_Page core file so we can access it here.
1265
-            'DTT_ID'                            => $default ? '' : $datetime->ID(),
1266
-        ];
1267
-        // need to setup the list items (but only if this isn't a default skeleton setup)
1268
-        if (! $default) {
1269
-            $ticket_row = 1;
1270
-            foreach ($all_tickets as $ticket) {
1271
-                $template_args['datetime_tickets_list'] .= $this->_get_datetime_tickets_list_item(
1272
-                    $datetime_row,
1273
-                    $ticket_row,
1274
-                    $datetime,
1275
-                    $ticket,
1276
-                    $datetime_tickets,
1277
-                    $default
1278
-                );
1279
-                $ticket_row++;
1280
-            }
1281
-        }
1282
-        // filter template args at this point
1283
-        $template_args = apply_filters(
1284
-            'FHEE__espresso_events_Pricing_Hooks___get_dtt_attached_ticket_row__template_args',
1285
-            $template_args,
1286
-            $datetime_row,
1287
-            $datetime,
1288
-            $datetime_tickets,
1289
-            $all_tickets,
1290
-            $default,
1291
-            $this->_is_creating_event
1292
-        );
1293
-        return EEH_Template::display_template(
1294
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_attached_tickets_row.template.php',
1295
-            $template_args,
1296
-            true
1297
-        );
1298
-    }
1299
-
1300
-
1301
-    /**
1302
-     * @param int|string       $datetime_row
1303
-     * @param int|string       $ticket_row
1304
-     * @param EE_Datetime|null $datetime
1305
-     * @param EE_Ticket|null   $ticket
1306
-     * @param array            $datetime_tickets
1307
-     * @param bool             $default
1308
-     * @return string
1309
-     * @throws EE_Error
1310
-     * @throws ReflectionException
1311
-     */
1312
-    protected function _get_datetime_tickets_list_item(
1313
-        $datetime_row,
1314
-        $ticket_row,
1315
-        ?EE_Datetime $datetime,
1316
-        ?EE_Ticket $ticket,
1317
-        array $datetime_tickets = [],
1318
-        bool $default = false
1319
-    ): string {
1320
-        $datetime_tickets = $datetime instanceof EE_Datetime && isset($datetime_tickets[ $datetime->ID() ])
1321
-            ? $datetime_tickets[ $datetime->ID() ]
1322
-            : [];
1323
-        $display_row      = $ticket instanceof EE_Ticket ? $ticket->get('TKT_row') : 0;
1324
-        $no_ticket        = $default && empty($ticket);
1325
-        $template_args    = [
1326
-            'dtt_row'                 => $default
1327
-                ? 'DTTNUM'
1328
-                : $datetime_row,
1329
-            'tkt_row'                 => $no_ticket
1330
-                ? 'TICKETNUM'
1331
-                : $ticket_row,
1332
-            'datetime_ticket_checked' => in_array($display_row, $datetime_tickets, true)
1333
-                ? ' checked'
1334
-                : '',
1335
-            'ticket_selected'         => in_array($display_row, $datetime_tickets, true)
1336
-                ? ' ticket-selected'
1337
-                : '',
1338
-            'TKT_name'                => $no_ticket
1339
-                ? 'TKTNAME'
1340
-                : $ticket->get('TKT_name'),
1341
-            'tkt_status_class'        => $no_ticket || $this->_is_creating_event
1342
-                ? ' tkt-status-' . EE_Ticket::onsale
1343
-                : ' tkt-status-' . $ticket->ticket_status(),
1344
-        ];
1345
-        // filter template args
1346
-        $template_args = apply_filters(
1347
-            'FHEE__espresso_events_Pricing_Hooks___get_datetime_tickets_list_item__template_args',
1348
-            $template_args,
1349
-            $datetime_row,
1350
-            $ticket_row,
1351
-            $datetime,
1352
-            $ticket,
1353
-            $datetime_tickets,
1354
-            $default,
1355
-            $this->_is_creating_event
1356
-        );
1357
-        return EEH_Template::display_template(
1358
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_dtt_tickets_list.template.php',
1359
-            $template_args,
1360
-            true
1361
-        );
1362
-    }
1363
-
1364
-
1365
-    /**
1366
-     * This generates the ticket row for tickets.
1367
-     * This same method is used to generate both the actual rows and the js skeleton row
1368
-     * (when default === true)
1369
-     *
1370
-     * @param int|string     $ticket_row       Represents the row number being generated.
1371
-     * @param EE_Ticket|null $ticket
1372
-     * @param EE_Datetime[]  $ticket_datetimes Either an array of all datetimes on all tickets indexed by each ticket
1373
-     *                                         or empty for default
1374
-     * @param EE_Datetime[]  $all_datetimes    All Datetimes on the event or empty for default.
1375
-     * @param bool           $default          Whether default row being generated or not.
1376
-     * @param EE_Ticket[]    $all_tickets      This is an array of all tickets attached to the event
1377
-     *                                         (or empty in the case of defaults)
1378
-     * @return string
1379
-     * @throws InvalidArgumentException
1380
-     * @throws InvalidInterfaceException
1381
-     * @throws InvalidDataTypeException
1382
-     * @throws DomainException
1383
-     * @throws EE_Error
1384
-     * @throws ReflectionException
1385
-     */
1386
-    protected function _get_ticket_row(
1387
-        $ticket_row,
1388
-        ?EE_Ticket $ticket,
1389
-        array $ticket_datetimes,
1390
-        array $all_datetimes,
1391
-        bool $default = false,
1392
-        array $all_tickets = []
1393
-    ): string {
1394
-        // if $ticket is not an instance of EE_Ticket then force default to true.
1395
-        $default = ! $ticket instanceof EE_Ticket ? true : $default;
1396
-        $prices  = ! empty($ticket) && ! $default
1397
-            ? $ticket->get_many_related(
1398
-                'Price',
1399
-                ['default_where_conditions' => 'none', 'order_by' => ['PRC_order' => 'ASC']]
1400
-            )
1401
-            : [];
1402
-        // if there is only one price (which would be the base price)
1403
-        // or NO prices and this ticket is a default ticket,
1404
-        // let's just make sure there are no cached default prices on the object.
1405
-        // This is done by not including any query_params.
1406
-        if ($ticket instanceof EE_Ticket && $ticket->is_default() && (count($prices) === 1 || empty($prices))) {
1407
-            $prices = $ticket->prices();
1408
-        }
1409
-        // check if we're dealing with a default ticket in which case
1410
-        // we don't want any starting_ticket_datetime_row values set
1411
-        // (otherwise there won't be any new relationships created for tickets based off of the default ticket).
1412
-        // This will future proof in case there is ever any behaviour change between what the primary_key defaults to.
1413
-        $default_datetime = $default || ($ticket instanceof EE_Ticket && $ticket->is_default());
1414
-        $ticket_datetimes = $ticket instanceof EE_Ticket && isset($ticket_datetimes[ $ticket->ID() ])
1415
-            ? $ticket_datetimes[ $ticket->ID() ]
1416
-            : [];
1417
-        $ticket_subtotal  = $default ? 0 : $ticket->get_ticket_subtotal();
1418
-        $base_price       = $default ? null : $ticket->base_price();
1419
-        $count_price_mods = EEM_Price::instance()->get_all_default_prices(true);
1420
-        // breaking out complicated condition for ticket_status
1421
-        if ($default) {
1422
-            $ticket_status_class = ' tkt-status-' . EE_Ticket::onsale;
1423
-        } else {
1424
-            $ticket_status_class = $ticket->is_default()
1425
-                ? ' tkt-status-' . EE_Ticket::onsale
1426
-                : ' tkt-status-' . $ticket->ticket_status();
1427
-        }
1428
-        // breaking out complicated condition for TKT_taxable
1429
-        if ($default) {
1430
-            $TKT_taxable = '';
1431
-        } else {
1432
-            $TKT_taxable = $ticket->taxable()
1433
-                ? 'checked'
1434
-                : '';
1435
-        }
1436
-        if ($default) {
1437
-            $TKT_status = EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence');
1438
-        } elseif ($ticket->is_default()) {
1439
-            $TKT_status = EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence');
1440
-        } else {
1441
-            $TKT_status = $ticket->ticket_status(true);
1442
-        }
1443
-        if ($default) {
1444
-            $TKT_min = '';
1445
-        } else {
1446
-            $TKT_min = $ticket->min();
1447
-            if ($TKT_min === -1 || $TKT_min === 0) {
1448
-                $TKT_min = '';
1449
-            }
1450
-        }
1451
-        $template_args                 = [
1452
-            'tkt_row'                       => $default ? 'TICKETNUM' : $ticket_row,
1453
-            'TKT_order'                     => $default ? 'TICKETNUM' : $ticket_row,
1454
-            // on initial page load this will always be the correct order.
1455
-            'tkt_status_class'              => $ticket_status_class,
1456
-            'display_edit_tkt_row'          => 'display:none;',
1457
-            'edit_tkt_expanded'             => '',
1458
-            'edit_tickets_name'             => $default ? 'TICKETNAMEATTR' : 'edit_tickets',
1459
-            'TKT_name'                      => $default ? '' : $ticket->get_f('TKT_name'),
1460
-            'TKT_start_date'                => $default
1461
-                ? ''
1462
-                : $ticket->get_date('TKT_start_date', $this->_date_time_format),
1463
-            'TKT_end_date'                  => $default
1464
-                ? ''
1465
-                : $ticket->get_date('TKT_end_date', $this->_date_time_format),
1466
-            'TKT_status'                    => $TKT_status,
1467
-            'TKT_price'                     => $default
1468
-                ? ''
1469
-                : EEH_Template::format_currency(
1470
-                    $ticket->get_ticket_total_with_taxes(),
1471
-                    false,
1472
-                    false
1473
-                ),
1474
-            'TKT_price_code'                => EE_Registry::instance()->CFG->currency->code,
1475
-            'TKT_price_amount'              => $default ? 0 : $ticket_subtotal,
1476
-            'TKT_qty'                       => $default
1477
-                ? ''
1478
-                : $ticket->get_pretty('TKT_qty', 'symbol'),
1479
-            'TKT_qty_for_input'             => $default
1480
-                ? ''
1481
-                : $ticket->get_pretty('TKT_qty', 'input'),
1482
-            'TKT_uses'                      => $default
1483
-                ? ''
1484
-                : $ticket->get_pretty('TKT_uses', 'input'),
1485
-            'TKT_min'                       => $TKT_min,
1486
-            'TKT_max'                       => $default
1487
-                ? ''
1488
-                : $ticket->get_pretty('TKT_max', 'input'),
1489
-            'TKT_sold'                      => $default ? 0 : $ticket->tickets_sold(),
1490
-            'TKT_reserved'                  => $default ? 0 : $ticket->reserved(),
1491
-            'TKT_registrations'             => $default
1492
-                ? 0
1493
-                : $ticket->count_registrations(
1494
-                    [
1495
-                        [
1496
-                            'STS_ID' => [
1497
-                                '!=',
1498
-                                EEM_Registration::status_id_incomplete,
1499
-                            ],
1500
-                        ],
1501
-                    ]
1502
-                ),
1503
-            'TKT_ID'                        => $default ? 0 : $ticket->ID(),
1504
-            'TKT_description'               => $default ? '' : $ticket->get_f('TKT_description'),
1505
-            'TKT_is_default'                => $default ? 0 : $ticket->is_default(),
1506
-            'TKT_required'                  => $default ? 0 : $ticket->required(),
1507
-            'TKT_is_default_selector'       => '',
1508
-            'ticket_price_rows'             => '',
1509
-            'TKT_base_price'                => $default || ! $base_price instanceof EE_Price
1510
-                ? ''
1511
-                : $base_price->get_pretty('PRC_amount', 'localized_float'),
1512
-            'TKT_base_price_ID'             => $default || ! $base_price instanceof EE_Price ? 0 : $base_price->ID(),
1513
-            'show_price_modifier'           => count($prices) > 1 || ($default && $count_price_mods > 0)
1514
-                ? ''
1515
-                : 'display:none;',
1516
-            'show_price_mod_button'         => count($prices) > 1
1517
-                                               || ($default && $count_price_mods > 0)
1518
-                                               || (! $default && $ticket->deleted())
1519
-                ? 'display:none;'
1520
-                : '',
1521
-            'total_price_rows'              => count($prices) > 1 ? count($prices) : 1,
1522
-            'ticket_datetimes_list'         => $default ? '<li class="hidden"></li>' : '',
1523
-            'starting_ticket_datetime_rows' => $default || $default_datetime ? '' : implode(',', $ticket_datetimes),
1524
-            'ticket_datetime_rows'          => $default ? '' : implode(',', $ticket_datetimes),
1525
-            'existing_ticket_price_ids'     => $default ? '' : implode(',', array_keys($prices)),
1526
-            'ticket_template_id'            => $default ? 0 : $ticket->get('TTM_ID'),
1527
-            'TKT_taxable'                   => $TKT_taxable,
1528
-            'display_subtotal'              => $ticket instanceof EE_Ticket && $ticket->taxable()
1529
-                ? ''
1530
-                : 'display:none;',
1531
-            'price_currency_symbol'         => EE_Registry::instance()->CFG->currency->sign,
1532
-            'TKT_subtotal_amount_display'   => EEH_Template::format_currency(
1533
-                $ticket_subtotal,
1534
-                false,
1535
-                false
1536
-            ),
1537
-            'TKT_subtotal_amount'           => $ticket_subtotal,
1538
-            'tax_rows'                      => $this->_get_tax_rows($ticket_row, $ticket),
1539
-            'disabled'                      => $ticket instanceof EE_Ticket && $ticket->deleted(),
1540
-            'ticket_archive_class'          => $ticket instanceof EE_Ticket && $ticket->deleted()
1541
-                ? ' ticket-archived'
1542
-                : '',
1543
-            'trash_icon'                    => $ticket instanceof EE_Ticket
1544
-                                               && $ticket->deleted()
1545
-                                               && ! $ticket->is_permanently_deleteable()
1546
-                ? 'dashicons dashicons-lock '
1547
-                : 'trash-icon dashicons dashicons-post-trash clickable',
1548
-            'clone_icon'                    => $ticket instanceof EE_Ticket && $ticket->deleted()
1549
-                ? ''
1550
-                : 'clone-icon ee-icon ee-icon-clone clickable',
1551
-        ];
1552
-        $template_args['trash_hidden'] = count($all_tickets) === 1
1553
-                                         && $template_args['trash_icon'] !== 'dashicons dashicons-lock'
1554
-            ? 'display:none'
1555
-            : '';
1556
-        // handle rows that should NOT be empty
1557
-        if (empty($template_args['TKT_start_date'])) {
1558
-            // if empty then the start date will be now.
1559
-            $template_args['TKT_start_date']   = date(
1560
-                $this->_date_time_format,
1561
-                current_time('timestamp')
1562
-            );
1563
-            $template_args['tkt_status_class'] = ' tkt-status-' . EE_Ticket::onsale;
1564
-        }
1565
-        if (empty($template_args['TKT_end_date'])) {
1566
-            // get the earliest datetime (if present);
1567
-            $earliest_datetime = $this->_adminpage_obj->get_cpt_model_obj()->ID() > 0
1568
-                ? $this->_adminpage_obj->get_cpt_model_obj()->get_first_related(
1569
-                    'Datetime',
1570
-                    ['order_by' => ['DTT_EVT_start' => 'ASC']]
1571
-                )
1572
-                : null;
1573
-            if (! empty($earliest_datetime)) {
1574
-                $template_args['TKT_end_date'] = $earliest_datetime->get_datetime(
1575
-                    'DTT_EVT_start',
1576
-                    $this->_date_time_format
1577
-                );
1578
-            } else {
1579
-                // default so let's just use what's been set for the default date-time which is 30 days from now.
1580
-                $template_args['TKT_end_date'] = date(
1581
-                    $this->_date_time_format,
1582
-                    mktime(
1583
-                        24,
1584
-                        0,
1585
-                        0,
1586
-                        date('m'),
1587
-                        date('d') + 29,
1588
-                        date('Y')
1589
-                    )
1590
-                );
1591
-            }
1592
-            $template_args['tkt_status_class'] = ' tkt-status-' . EE_Ticket::onsale;
1593
-        }
1594
-        // generate ticket_datetime items
1595
-        if (! $default) {
1596
-            $datetime_row = 1;
1597
-            foreach ($all_datetimes as $datetime) {
1598
-                $template_args['ticket_datetimes_list'] .= $this->_get_ticket_datetime_list_item(
1599
-                    $datetime_row,
1600
-                    $ticket_row,
1601
-                    $datetime,
1602
-                    $ticket,
1603
-                    $ticket_datetimes,
1604
-                    $default
1605
-                );
1606
-                $datetime_row++;
1607
-            }
1608
-        }
1609
-        $price_row = 1;
1610
-        foreach ($prices as $price) {
1611
-            if (! $price instanceof EE_Price) {
1612
-                continue;
1613
-            }
1614
-            if ($price->is_base_price()) {
1615
-                $price_row++;
1616
-                continue;
1617
-            }
1618
-
1619
-            $show_trash  = ! ((count($prices) > 1 && $price_row === 1) || count($prices) === 1);
1620
-            $show_create = ! (count($prices) > 1 && count($prices) !== $price_row);
1621
-
1622
-            $template_args['ticket_price_rows'] .= $this->_get_ticket_price_row(
1623
-                $ticket_row,
1624
-                $price_row,
1625
-                $price,
1626
-                $default,
1627
-                $ticket,
1628
-                $show_trash,
1629
-                $show_create
1630
-            );
1631
-            $price_row++;
1632
-        }
1633
-        // filter $template_args
1634
-        $template_args = apply_filters(
1635
-            'FHEE__espresso_events_Pricing_Hooks___get_ticket_row__template_args',
1636
-            $template_args,
1637
-            $ticket_row,
1638
-            $ticket,
1639
-            $ticket_datetimes,
1640
-            $all_datetimes,
1641
-            $default,
1642
-            $all_tickets,
1643
-            $this->_is_creating_event
1644
-        );
1645
-        return EEH_Template::display_template(
1646
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_row.template.php',
1647
-            $template_args,
1648
-            true
1649
-        );
1650
-    }
1651
-
1652
-
1653
-    /**
1654
-     * @param int|string     $ticket_row
1655
-     * @param EE_Ticket|null $ticket
1656
-     * @return string
1657
-     * @throws DomainException
1658
-     * @throws EE_Error
1659
-     * @throws ReflectionException
1660
-     */
1661
-    protected function _get_tax_rows($ticket_row, ?EE_Ticket $ticket): string
1662
-    {
1663
-        $tax_rows = '';
1664
-        /** @var EE_Price[] $taxes */
1665
-        $taxes = empty($ticket) ? EE_Taxes::get_taxes_for_admin() : $ticket->get_ticket_taxes_for_admin();
1666
-        foreach ($taxes as $tax) {
1667
-            $tax_added     = $this->_get_tax_added($tax, $ticket);
1668
-            $template_args = [
1669
-                'display_tax'       => ! empty($ticket) && $ticket->get('TKT_taxable')
1670
-                    ? ''
1671
-                    : 'display:none;',
1672
-                'tax_id'            => $tax->ID(),
1673
-                'tkt_row'           => $ticket_row,
1674
-                'tax_label'         => $tax->get('PRC_name'),
1675
-                'tax_added'         => $tax_added,
1676
-                'tax_added_display' => EEH_Template::format_currency($tax_added, false, false),
1677
-                'tax_amount'        => $tax->get('PRC_amount'),
1678
-            ];
1679
-            $template_args = apply_filters(
1680
-                'FHEE__espresso_events_Pricing_Hooks___get_tax_rows__template_args',
1681
-                $template_args,
1682
-                $ticket_row,
1683
-                $ticket,
1684
-                $this->_is_creating_event
1685
-            );
1686
-            $tax_rows      .= EEH_Template::display_template(
1687
-                PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_tax_row.template.php',
1688
-                $template_args,
1689
-                true
1690
-            );
1691
-        }
1692
-        return $tax_rows;
1693
-    }
1694
-
1695
-
1696
-    /**
1697
-     * @param EE_Price       $tax
1698
-     * @param EE_Ticket|null $ticket
1699
-     * @return float|int
1700
-     * @throws EE_Error
1701
-     * @throws ReflectionException
1702
-     */
1703
-    protected function _get_tax_added(EE_Price $tax, ?EE_Ticket $ticket)
1704
-    {
1705
-        $subtotal = empty($ticket) ? 0 : $ticket->get_ticket_subtotal();
1706
-        return $subtotal * $tax->get('PRC_amount') / 100;
1707
-    }
1708
-
1709
-
1710
-    /**
1711
-     * @param int|string     $ticket_row
1712
-     * @param int|string     $price_row
1713
-     * @param EE_Price|null  $price
1714
-     * @param bool           $default
1715
-     * @param EE_Ticket|null $ticket
1716
-     * @param bool           $show_trash
1717
-     * @param bool           $show_create
1718
-     * @return string
1719
-     * @throws InvalidArgumentException
1720
-     * @throws InvalidInterfaceException
1721
-     * @throws InvalidDataTypeException
1722
-     * @throws DomainException
1723
-     * @throws EE_Error
1724
-     * @throws ReflectionException
1725
-     */
1726
-    protected function _get_ticket_price_row(
1727
-        $ticket_row,
1728
-        $price_row,
1729
-        ?EE_Price $price,
1730
-        bool $default,
1731
-        ?EE_Ticket $ticket,
1732
-        bool $show_trash = true,
1733
-        bool $show_create = true
1734
-    ): string {
1735
-        $send_disabled = ! empty($ticket) && $ticket->get('TKT_deleted');
1736
-        $template_args = [
1737
-            'tkt_row'               => $default && empty($ticket)
1738
-                ? 'TICKETNUM'
1739
-                : $ticket_row,
1740
-            'PRC_order'             => $default && empty($price)
1741
-                ? 'PRICENUM'
1742
-                : $price_row,
1743
-            'edit_prices_name'      => $default && empty($price)
1744
-                ? 'PRICENAMEATTR'
1745
-                : 'edit_prices',
1746
-            'price_type_selector'   => $default && empty($price)
1747
-                ? $this->_get_base_price_template($ticket_row, $price_row, $price, true)
1748
-                : $this->_get_price_type_selector(
1749
-                    $ticket_row,
1750
-                    $price_row,
1751
-                    $price,
1752
-                    $default,
1753
-                    $send_disabled
1754
-                ),
1755
-            'PRC_ID'                => $default && empty($price)
1756
-                ? 0
1757
-                : $price->ID(),
1758
-            'PRC_is_default'        => $default && empty($price)
1759
-                ? 0
1760
-                : $price->get('PRC_is_default'),
1761
-            'PRC_name'              => $default && empty($price)
1762
-                ? ''
1763
-                : $price->get('PRC_name'),
1764
-            'price_currency_symbol' => EE_Registry::instance()->CFG->currency->sign,
1765
-            'show_plus_or_minus'    => $default && empty($price)
1766
-                ? ''
1767
-                : 'display:none;',
1768
-            'show_plus'             => ($default && empty($price)) || ($price->is_discount() || $price->is_base_price())
1769
-                ? 'display:none;'
1770
-                : '',
1771
-            'show_minus'            => ($default && empty($price)) || ! $price->is_discount()
1772
-                ? 'display:none;'
1773
-                : '',
1774
-            'show_currency_symbol'  => ($default && empty($price)) || $price->is_percent()
1775
-                ? 'display:none'
1776
-                : '',
1777
-            'PRC_amount'            => $default && empty($price)
1778
-                ? 0
1779
-                : $price->get_pretty('PRC_amount', 'localized_float'),
1780
-            'show_percentage'       => ($default && empty($price)) || ! $price->is_percent()
1781
-                ? 'display:none;'
1782
-                : '',
1783
-            'show_trash_icon'       => $show_trash
1784
-                ? ''
1785
-                : ' style="display:none;"',
1786
-            'show_create_button'    => $show_create
1787
-                ? ''
1788
-                : ' style="display:none;"',
1789
-            'PRC_desc'              => $default && empty($price)
1790
-                ? ''
1791
-                : $price->get('PRC_desc'),
1792
-            'disabled'              => ! empty($ticket) && $ticket->get('TKT_deleted'),
1793
-        ];
1794
-        $template_args = apply_filters(
1795
-            'FHEE__espresso_events_Pricing_Hooks___get_ticket_price_row__template_args',
1796
-            $template_args,
1797
-            $ticket_row,
1798
-            $price_row,
1799
-            $price,
1800
-            $default,
1801
-            $ticket,
1802
-            $show_trash,
1803
-            $show_create,
1804
-            $this->_is_creating_event
1805
-        );
1806
-        return EEH_Template::display_template(
1807
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_price_row.template.php',
1808
-            $template_args,
1809
-            true
1810
-        );
1811
-    }
1812
-
1813
-
1814
-    /**
1815
-     * @param int|string    $ticket_row
1816
-     * @param int|string    $price_row
1817
-     * @param EE_Price|null $price
1818
-     * @param bool          $default
1819
-     * @param bool          $disabled
1820
-     * @return string
1821
-     * @throws ReflectionException
1822
-     * @throws InvalidArgumentException
1823
-     * @throws InvalidInterfaceException
1824
-     * @throws InvalidDataTypeException
1825
-     * @throws DomainException
1826
-     * @throws EE_Error
1827
-     */
1828
-    protected function _get_price_type_selector(
1829
-        $ticket_row,
1830
-        $price_row,
1831
-        ?EE_Price $price,
1832
-        bool $default,
1833
-        bool $disabled = false
1834
-    ): string {
1835
-        if ($price->is_base_price()) {
1836
-            return $this->_get_base_price_template(
1837
-                $ticket_row,
1838
-                $price_row,
1839
-                $price,
1840
-                $default
1841
-            );
1842
-        }
1843
-        return $this->_get_price_modifier_template(
1844
-            $ticket_row,
1845
-            $price_row,
1846
-            $price,
1847
-            $default,
1848
-            $disabled
1849
-        );
1850
-    }
1851
-
1852
-
1853
-    /**
1854
-     * @param int|string    $ticket_row
1855
-     * @param int|string    $price_row
1856
-     * @param EE_Price|null $price
1857
-     * @param bool          $default
1858
-     * @return string
1859
-     * @throws DomainException
1860
-     * @throws EE_Error
1861
-     * @throws ReflectionException
1862
-     */
1863
-    protected function _get_base_price_template(
1864
-        $ticket_row,
1865
-        $price_row,
1866
-        ?EE_Price $price,
1867
-        bool $default
1868
-    ): string {
1869
-        $template_args = [
1870
-            'tkt_row'                   => $default ? 'TICKETNUM' : $ticket_row,
1871
-            'PRC_order'                 => $default && empty($price) ? 'PRICENUM' : $price_row,
1872
-            'PRT_ID'                    => $default && empty($price) ? 1 : $price->get('PRT_ID'),
1873
-            'PRT_name'                  => esc_html__('Price', 'event_espresso'),
1874
-            'price_selected_operator'   => '+',
1875
-            'price_selected_is_percent' => 0,
1876
-        ];
1877
-        $template_args = apply_filters(
1878
-            'FHEE__espresso_events_Pricing_Hooks___get_base_price_template__template_args',
1879
-            $template_args,
1880
-            $ticket_row,
1881
-            $price_row,
1882
-            $price,
1883
-            $default,
1884
-            $this->_is_creating_event
1885
-        );
1886
-        return EEH_Template::display_template(
1887
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_type_base.template.php',
1888
-            $template_args,
1889
-            true
1890
-        );
1891
-    }
1892
-
1893
-
1894
-    /**
1895
-     * @param int|string    $ticket_row
1896
-     * @param int|string    $price_row
1897
-     * @param EE_Price|null $price
1898
-     * @param bool          $default
1899
-     * @param bool          $disabled
1900
-     * @return string
1901
-     * @throws ReflectionException
1902
-     * @throws InvalidArgumentException
1903
-     * @throws InvalidInterfaceException
1904
-     * @throws InvalidDataTypeException
1905
-     * @throws DomainException
1906
-     * @throws EE_Error
1907
-     */
1908
-    protected function _get_price_modifier_template(
1909
-        $ticket_row,
1910
-        $price_row,
1911
-        ?EE_Price $price,
1912
-        bool $default,
1913
-        bool $disabled = false
1914
-    ): string {
1915
-        $select_name = $default && ! $price instanceof EE_Price
1916
-            ? 'edit_prices[TICKETNUM][PRICENUM][PRT_ID]'
1917
-            : 'edit_prices[' . esc_attr($ticket_row) . '][' . esc_attr($price_row) . '][PRT_ID]';
1918
-        /** @var EEM_Price_Type $price_type_model */
1919
-        $price_type_model       = EE_Registry::instance()->load_model('Price_Type');
1920
-        $price_types            = $price_type_model->get_all(
1921
-            [
1922
-                [
1923
-                    'OR' => [
1924
-                        'PBT_ID'  => '2',
1925
-                        'PBT_ID*' => '3',
1926
-                    ],
1927
-                ],
1928
-            ]
1929
-        );
1930
-        $all_price_types        = $default && ! $price instanceof EE_Price
1931
-            ? [esc_html__('Select Modifier', 'event_espresso')]
1932
-            : [];
1933
-        $selected_price_type_id = $default && ! $price instanceof EE_Price ? 0 : $price->type();
1934
-        $price_option_spans     = '';
1935
-        // setup price types for selector
1936
-        foreach ($price_types as $price_type) {
1937
-            if (! $price_type instanceof EE_Price_Type) {
1938
-                continue;
1939
-            }
1940
-            $all_price_types[ $price_type->ID() ] = $price_type->get('PRT_name');
1941
-            // while we're in the loop let's setup the option spans used by js
1942
-            $span_args          = [
1943
-                'PRT_ID'         => $price_type->ID(),
1944
-                'PRT_operator'   => $price_type->is_discount() ? '-' : '+',
1945
-                'PRT_is_percent' => $price_type->get('PRT_is_percent') ? 1 : 0,
1946
-            ];
1947
-            $price_option_spans .= EEH_Template::display_template(
1948
-                PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_option_span.template.php',
1949
-                $span_args,
1950
-                true
1951
-            );
1952
-        }
1953
-
1954
-        $select_name = $disabled
1955
-            ? 'archive_price[' . $ticket_row . '][' . $price_row . '][PRT_ID]'
1956
-            : $select_name;
1957
-
1958
-        $select_input = new EE_Select_Input(
1959
-            $all_price_types,
1960
-            [
1961
-                'default'               => $selected_price_type_id,
1962
-                'html_name'             => $select_name,
1963
-                'html_class'            => 'edit-price-PRT_ID',
1964
-                'other_html_attributes' => $disabled ? 'style="width:auto;" disabled' : 'style="width:auto;"',
1965
-            ]
1966
-        );
1967
-
1968
-        $price_selected_operator   = $price instanceof EE_Price && $price->is_discount() ? '-' : '+';
1969
-        $price_selected_operator   = $default && ! $price instanceof EE_Price ? '' : $price_selected_operator;
1970
-        $price_selected_is_percent = $price instanceof EE_Price && $price->is_percent() ? 1 : 0;
1971
-        $price_selected_is_percent = $default && ! $price instanceof EE_Price ? '' : $price_selected_is_percent;
1972
-        $template_args             = [
1973
-            'tkt_row'                   => $default ? 'TICKETNUM' : $ticket_row,
1974
-            'PRC_order'                 => $default && ! $price instanceof EE_Price ? 'PRICENUM' : $price_row,
1975
-            'price_modifier_selector'   => $select_input->get_html_for_input(),
1976
-            'main_name'                 => $select_name,
1977
-            'selected_price_type_id'    => $selected_price_type_id,
1978
-            'price_option_spans'        => $price_option_spans,
1979
-            'price_selected_operator'   => $price_selected_operator,
1980
-            'price_selected_is_percent' => $price_selected_is_percent,
1981
-            'disabled'                  => $disabled,
1982
-        ];
1983
-        $template_args             = apply_filters(
1984
-            'FHEE__espresso_events_Pricing_Hooks___get_price_modifier_template__template_args',
1985
-            $template_args,
1986
-            $ticket_row,
1987
-            $price_row,
1988
-            $price,
1989
-            $default,
1990
-            $disabled,
1991
-            $this->_is_creating_event
1992
-        );
1993
-        return EEH_Template::display_template(
1994
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_modifier_selector.template.php',
1995
-            $template_args,
1996
-            true
1997
-        );
1998
-    }
1999
-
2000
-
2001
-    /**
2002
-     * @param int|string       $datetime_row
2003
-     * @param int|string       $ticket_row
2004
-     * @param EE_Datetime|null $datetime
2005
-     * @param EE_Ticket|null   $ticket
2006
-     * @param array            $ticket_datetimes
2007
-     * @param bool             $default
2008
-     * @return string
2009
-     * @throws DomainException
2010
-     * @throws EE_Error
2011
-     * @throws ReflectionException
2012
-     */
2013
-    protected function _get_ticket_datetime_list_item(
2014
-        $datetime_row,
2015
-        $ticket_row,
2016
-        ?EE_Datetime $datetime,
2017
-        ?EE_Ticket $ticket,
2018
-        array $ticket_datetimes = [],
2019
-        bool $default = false
2020
-    ): string {
2021
-        $ticket_datetimes = $ticket instanceof EE_Ticket && isset($ticket_datetimes[ $ticket->ID() ])
2022
-            ? $ticket_datetimes[ $ticket->ID() ]
2023
-            : [];
2024
-        $template_args    = [
2025
-            'dtt_row'                  => $default && ! $datetime instanceof EE_Datetime
2026
-                ? 'DTTNUM'
2027
-                : $datetime_row,
2028
-            'tkt_row'                  => $default
2029
-                ? 'TICKETNUM'
2030
-                : $ticket_row,
2031
-            'ticket_datetime_selected' => in_array($datetime_row, $ticket_datetimes, true)
2032
-                ? ' ticket-selected'
2033
-                : '',
2034
-            'ticket_datetime_checked'  => in_array($datetime_row, $ticket_datetimes, true)
2035
-                ? ' checked'
2036
-                : '',
2037
-            'DTT_name'                 => $default && empty($datetime)
2038
-                ? 'DTTNAME'
2039
-                : $datetime->get_dtt_display_name(true),
2040
-            'tkt_status_class'         => '',
2041
-        ];
2042
-        $template_args    = apply_filters(
2043
-            'FHEE__espresso_events_Pricing_Hooks___get_ticket_datetime_list_item__template_args',
2044
-            $template_args,
2045
-            $datetime_row,
2046
-            $ticket_row,
2047
-            $datetime,
2048
-            $ticket,
2049
-            $ticket_datetimes,
2050
-            $default,
2051
-            $this->_is_creating_event
2052
-        );
2053
-        return EEH_Template::display_template(
2054
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_datetimes_list_item.template.php',
2055
-            $template_args,
2056
-            true
2057
-        );
2058
-    }
2059
-
2060
-
2061
-    /**
2062
-     * @param array $all_datetimes
2063
-     * @param array $all_tickets
2064
-     * @return string
2065
-     * @throws ReflectionException
2066
-     * @throws InvalidArgumentException
2067
-     * @throws InvalidInterfaceException
2068
-     * @throws InvalidDataTypeException
2069
-     * @throws DomainException
2070
-     * @throws EE_Error
2071
-     */
2072
-    protected function _get_ticket_js_structure(array $all_datetimes = [], array $all_tickets = []): string
2073
-    {
2074
-        $template_args = [
2075
-            'default_datetime_edit_row' => $this->_get_dtt_edit_row(
2076
-                'DTTNUM',
2077
-                null,
2078
-                true,
2079
-                $all_datetimes
2080
-            ),
2081
-            'default_ticket_row'        => $this->_get_ticket_row(
2082
-                'TICKETNUM',
2083
-                null,
2084
-                [],
2085
-                [],
2086
-                true
2087
-            ),
2088
-            'default_price_row'         => $this->_get_ticket_price_row(
2089
-                'TICKETNUM',
2090
-                'PRICENUM',
2091
-                null,
2092
-                true,
2093
-                null
2094
-            ),
2095
-
2096
-            'default_price_rows'                       => '',
2097
-            'default_base_price_amount'                => 0,
2098
-            'default_base_price_name'                  => '',
2099
-            'default_base_price_description'           => '',
2100
-            'default_price_modifier_selector_row'      => $this->_get_price_modifier_template(
2101
-                'TICKETNUM',
2102
-                'PRICENUM',
2103
-                null,
2104
-                true
2105
-            ),
2106
-            'default_available_tickets_for_datetime'   => $this->_get_dtt_attached_tickets_row(
2107
-                'DTTNUM',
2108
-                null,
2109
-                [],
2110
-                [],
2111
-                true
2112
-            ),
2113
-            'existing_available_datetime_tickets_list' => '',
2114
-            'existing_available_ticket_datetimes_list' => '',
2115
-            'new_available_datetime_ticket_list_item'  => $this->_get_datetime_tickets_list_item(
2116
-                'DTTNUM',
2117
-                'TICKETNUM',
2118
-                null,
2119
-                null,
2120
-                [],
2121
-                true
2122
-            ),
2123
-            'new_available_ticket_datetime_list_item'  => $this->_get_ticket_datetime_list_item(
2124
-                'DTTNUM',
2125
-                'TICKETNUM',
2126
-                null,
2127
-                null,
2128
-                [],
2129
-                true
2130
-            ),
2131
-        ];
2132
-        $ticket_row    = 1;
2133
-        foreach ($all_tickets as $ticket) {
2134
-            $template_args['existing_available_datetime_tickets_list'] .= $this->_get_datetime_tickets_list_item(
2135
-                'DTTNUM',
2136
-                $ticket_row,
2137
-                null,
2138
-                $ticket,
2139
-                [],
2140
-                true
2141
-            );
2142
-            $ticket_row++;
2143
-        }
2144
-        $datetime_row = 1;
2145
-        foreach ($all_datetimes as $datetime) {
2146
-            $template_args['existing_available_ticket_datetimes_list'] .= $this->_get_ticket_datetime_list_item(
2147
-                $datetime_row,
2148
-                'TICKETNUM',
2149
-                $datetime,
2150
-                null,
2151
-                [],
2152
-                true
2153
-            );
2154
-            $datetime_row++;
2155
-        }
2156
-        /** @var EEM_Price $price_model */
2157
-        $price_model    = EE_Registry::instance()->load_model('Price');
2158
-        $default_prices = $price_model->get_all_default_prices();
2159
-        $price_row      = 1;
2160
-        foreach ($default_prices as $price) {
2161
-            if (! $price instanceof EE_Price) {
2162
-                continue;
2163
-            }
2164
-            if ($price->is_base_price()) {
2165
-                $template_args['default_base_price_amount']      = $price->get_pretty(
2166
-                    'PRC_amount',
2167
-                    'localized_float'
2168
-                );
2169
-                $template_args['default_base_price_name']        = $price->get('PRC_name');
2170
-                $template_args['default_base_price_description'] = $price->get('PRC_desc');
2171
-                $price_row++;
2172
-                continue;
2173
-            }
2174
-
2175
-            $show_trash  = ! ((count($default_prices) > 1 && $price_row === 1) || count($default_prices) === 1);
2176
-            $show_create = ! (count($default_prices) > 1 && count($default_prices) !== $price_row);
2177
-
2178
-            $template_args['default_price_rows'] .= $this->_get_ticket_price_row(
2179
-                'TICKETNUM',
2180
-                $price_row,
2181
-                $price,
2182
-                true,
2183
-                null,
2184
-                $show_trash,
2185
-                $show_create
2186
-            );
2187
-            $price_row++;
2188
-        }
2189
-        $template_args = apply_filters(
2190
-            'FHEE__espresso_events_Pricing_Hooks___get_ticket_js_structure__template_args',
2191
-            $template_args,
2192
-            $all_datetimes,
2193
-            $all_tickets,
2194
-            $this->_is_creating_event
2195
-        );
2196
-        return EEH_Template::display_template(
2197
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_js_structure.template.php',
2198
-            $template_args,
2199
-            true
2200
-        );
2201
-    }
231
+					],
232
+					'DTT_OVERSELL_WARNING'  => [
233
+						'datetime_ticket' => esc_html__(
234
+							'You cannot add this ticket to this datetime because it has a sold amount that is greater than the amount of spots remaining for this datetime.',
235
+							'event_espresso'
236
+						),
237
+						'ticket_datetime' => esc_html__(
238
+							'You cannot add this datetime to this ticket because the ticket has a sold amount that is greater than the amount of spots remaining on the datetime.',
239
+							'event_espresso'
240
+						),
241
+					],
242
+					'DTT_CONVERTED_FORMATS' => EEH_DTT_Helper::convert_php_to_js_and_moment_date_formats(
243
+						$this->_date_format_strings['date'],
244
+						$this->_date_format_strings['time']
245
+					),
246
+					'DTT_START_OF_WEEK'     => ['dayValue' => (int) get_option('start_of_week')],
247
+				],
248
+			],
249
+		];
250
+	}
251
+
252
+
253
+	/**
254
+	 * @param array $update_callbacks
255
+	 * @return array
256
+	 */
257
+	public function caf_updates(array $update_callbacks): array
258
+	{
259
+		unset($update_callbacks['_default_tickets_update']);
260
+		$update_callbacks['datetime_and_tickets_caf_update'] = [$this, 'datetime_and_tickets_caf_update'];
261
+		return $update_callbacks;
262
+	}
263
+
264
+
265
+	/**
266
+	 * Handles saving everything related to Tickets (datetimes, tickets, prices)
267
+	 *
268
+	 * @param EE_Event $event The Event object we're attaching data to
269
+	 * @param array    $data  The request data from the form
270
+	 * @throws ReflectionException
271
+	 * @throws Exception
272
+	 * @throws InvalidInterfaceException
273
+	 * @throws InvalidDataTypeException
274
+	 * @throws EE_Error
275
+	 * @throws InvalidArgumentException
276
+	 */
277
+	public function datetime_and_tickets_caf_update(EE_Event $event, array $data)
278
+	{
279
+		// first we need to start with datetimes cause they are the "root" items attached to events.
280
+		$saved_datetimes = $this->_update_datetimes($event, $data);
281
+		// next tackle the tickets (and prices?)
282
+		$this->_update_tickets($event, $saved_datetimes, $data);
283
+	}
284
+
285
+
286
+	/**
287
+	 * update event_datetimes
288
+	 *
289
+	 * @param EE_Event $event Event being updated
290
+	 * @param array    $data  the request data from the form
291
+	 * @return EE_Datetime[]
292
+	 * @throws Exception
293
+	 * @throws ReflectionException
294
+	 * @throws InvalidInterfaceException
295
+	 * @throws InvalidDataTypeException
296
+	 * @throws InvalidArgumentException
297
+	 * @throws EE_Error
298
+	 */
299
+	protected function _update_datetimes(EE_Event $event, array $data): array
300
+	{
301
+		$timezone            = $data['timezone_string'] ?? null;
302
+		$saved_datetime_ids  = [];
303
+		$saved_datetime_objs = [];
304
+		if (empty($data['edit_event_datetimes']) || ! is_array($data['edit_event_datetimes'])) {
305
+			throw new InvalidArgumentException(
306
+				esc_html__(
307
+					'The "edit_event_datetimes" array is invalid therefore the event can not be updated.',
308
+					'event_espresso'
309
+				)
310
+			);
311
+		}
312
+		foreach ($data['edit_event_datetimes'] as $row => $datetime_data) {
313
+			// trim all values to ensure any excess whitespace is removed.
314
+			$datetime_data                = array_map(
315
+				function ($datetime_data) {
316
+					return is_array($datetime_data) ? $datetime_data : trim($datetime_data);
317
+				},
318
+				$datetime_data
319
+			);
320
+
321
+			$datetime_data['DTT_EVT_end'] = isset($datetime_data['DTT_EVT_end'])
322
+											&& ! empty($datetime_data['DTT_EVT_end'])
323
+				? $datetime_data['DTT_EVT_end']
324
+				: $datetime_data['DTT_EVT_start'];
325
+
326
+			$datetime_values              = [
327
+				'DTT_ID'          => ! empty($datetime_data['DTT_ID'])
328
+					? $datetime_data['DTT_ID']
329
+					: null,
330
+				'DTT_name'        => ! empty($datetime_data['DTT_name'])
331
+					? $datetime_data['DTT_name']
332
+					: '',
333
+				'DTT_description' => ! empty($datetime_data['DTT_description'])
334
+					? $datetime_data['DTT_description']
335
+					: '',
336
+				'DTT_EVT_start'   => $datetime_data['DTT_EVT_start'],
337
+				'DTT_EVT_end'     => $datetime_data['DTT_EVT_end'],
338
+				'DTT_reg_limit'   => empty($datetime_data['DTT_reg_limit'])
339
+					? EE_INF
340
+					: $datetime_data['DTT_reg_limit'],
341
+				'DTT_order'       => ! isset($datetime_data['DTT_order'])
342
+					? $row
343
+					: $datetime_data['DTT_order'],
344
+			];
345
+
346
+			// if we have an id then let's get existing object first and then set the new values.
347
+			// Otherwise we instantiate a new object for save.
348
+			if (! empty($datetime_data['DTT_ID'])) {
349
+				$datetime = EEM_Datetime::instance($timezone)->get_one_by_ID($datetime_data['DTT_ID']);
350
+				// set date and time format according to what is set in this class.
351
+				$datetime->set_date_format($this->_date_format_strings['date']);
352
+				$datetime->set_time_format($this->_date_format_strings['time']);
353
+				foreach ($datetime_values as $field => $value) {
354
+					$datetime->set($field, $value);
355
+				}
356
+
357
+				// make sure the $datetime_id here is saved just in case
358
+				// after the add_relation_to() the autosave replaces it.
359
+				// We need to do this so we dont' TRASH the parent DTT.
360
+				// (save the ID for both key and value to avoid duplications)
361
+				$saved_datetime_ids[ $datetime->ID() ] = $datetime->ID();
362
+			} else {
363
+				$datetime = EE_Datetime::new_instance(
364
+					$datetime_values,
365
+					$timezone,
366
+					[$this->_date_format_strings['date'], $this->_date_format_strings['time']]
367
+				);
368
+				foreach ($datetime_values as $field => $value) {
369
+					$datetime->set($field, $value);
370
+				}
371
+			}
372
+			$datetime->save();
373
+			do_action(
374
+				'AHEE__espresso_events_Pricing_Hooks___update_datetimes_after_save',
375
+				$datetime,
376
+				$row,
377
+				$datetime_data,
378
+				$data
379
+			);
380
+			$datetime = $event->_add_relation_to($datetime, 'Datetime');
381
+			// before going any further make sure our dates are setup correctly
382
+			// so that the end date is always equal or greater than the start date.
383
+			if ($datetime->get_raw('DTT_EVT_start') > $datetime->get_raw('DTT_EVT_end')) {
384
+				$datetime->set('DTT_EVT_end', $datetime->get('DTT_EVT_start'));
385
+				$datetime = EEH_DTT_Helper::date_time_add($datetime, 'DTT_EVT_end', 'days');
386
+				$datetime->save();
387
+			}
388
+			// now we have to make sure we add the new DTT_ID to the $saved_datetime_ids array
389
+			// because it is possible there was a new one created for the autosave.
390
+			// (save the ID for both key and value to avoid duplications)
391
+			$DTT_ID                        = $datetime->ID();
392
+			$saved_datetime_ids[ $DTT_ID ] = $DTT_ID;
393
+			$saved_datetime_objs[ $row ]   = $datetime;
394
+			// @todo if ANY of these updates fail then we want the appropriate global error message.
395
+		}
396
+		$event->save();
397
+		// now we need to REMOVE any datetimes that got deleted.
398
+		// Keep in mind that this process will only kick in for datetimes that don't have any DTT_sold on them.
399
+		// So its safe to permanently delete at this point.
400
+		$old_datetimes = explode(',', $data['datetime_IDs']);
401
+		$old_datetimes = $old_datetimes[0] === '' ? [] : $old_datetimes;
402
+		if (is_array($old_datetimes)) {
403
+			$datetimes_to_delete = array_diff($old_datetimes, $saved_datetime_ids);
404
+			foreach ($datetimes_to_delete as $id) {
405
+				$id = absint($id);
406
+				if (empty($id)) {
407
+					continue;
408
+				}
409
+				$datetime_to_remove = EE_Registry::instance()->load_model('Datetime')->get_one_by_ID($id);
410
+				// remove ticket relationships.
411
+				$related_tickets = $datetime_to_remove->get_many_related('Ticket');
412
+				foreach ($related_tickets as $ticket) {
413
+					$datetime_to_remove->_remove_relation_to($ticket, 'Ticket');
414
+				}
415
+				$event->_remove_relation_to($id, 'Datetime');
416
+				$datetime_to_remove->refresh_cache_of_related_objects();
417
+			}
418
+		}
419
+		return $saved_datetime_objs;
420
+	}
421
+
422
+
423
+	/**
424
+	 * update tickets
425
+	 *
426
+	 * @param EE_Event      $event           Event object being updated
427
+	 * @param EE_Datetime[] $saved_datetimes an array of datetime ids being updated
428
+	 * @param array         $data            incoming request data
429
+	 * @return EE_Ticket[]
430
+	 * @throws Exception
431
+	 * @throws ReflectionException
432
+	 * @throws InvalidInterfaceException
433
+	 * @throws InvalidDataTypeException
434
+	 * @throws InvalidArgumentException
435
+	 * @throws EE_Error
436
+	 */
437
+	protected function _update_tickets(EE_Event $event, array $saved_datetimes, array $data): array
438
+	{
439
+		$new_ticket = null;
440
+		// stripslashes because WP filtered the $_POST ($data) array to add slashes
441
+		$data          = stripslashes_deep($data);
442
+		$timezone      = $data['timezone_string'] ?? null;
443
+		$saved_tickets = [];
444
+		$old_tickets   = isset($data['ticket_IDs']) ? explode(',', $data['ticket_IDs']) : [];
445
+		if (empty($data['edit_tickets']) || ! is_array($data['edit_tickets'])) {
446
+			throw new InvalidArgumentException(
447
+				esc_html__(
448
+					'The "edit_tickets" array is invalid therefore the event can not be updated.',
449
+					'event_espresso'
450
+				)
451
+			);
452
+		}
453
+		foreach ($data['edit_tickets'] as $row => $ticket_data) {
454
+			$update_prices = $create_new_TKT = false;
455
+			// figure out what datetimes were added to the ticket
456
+			// and what datetimes were removed from the ticket in the session.
457
+			$starting_ticket_datetime_rows = explode(',', $data['starting_ticket_datetime_rows'][ $row ]);
458
+			$ticket_datetime_rows          = explode(',', $data['ticket_datetime_rows'][ $row ]);
459
+			$datetimes_added               = array_diff($ticket_datetime_rows, $starting_ticket_datetime_rows);
460
+			$datetimes_removed             = array_diff($starting_ticket_datetime_rows, $ticket_datetime_rows);
461
+			// trim inputs to ensure any excess whitespace is removed.
462
+			$ticket_data = array_map(
463
+				function ($ticket_data) {
464
+					return is_array($ticket_data) ? $ticket_data : trim($ticket_data);
465
+				},
466
+				$ticket_data
467
+			);
468
+			// note we are doing conversions to floats here instead of allowing EE_Money_Field to handle
469
+			// because we're doing calculations prior to using the models.
470
+			// note incoming ['TKT_price'] value is already in standard notation (via js).
471
+			$ticket_price = isset($ticket_data['TKT_price'])
472
+				? round((float) $ticket_data['TKT_price'], 3)
473
+				: 0;
474
+			// note incoming base price needs converted from localized value.
475
+			$base_price = isset($ticket_data['TKT_base_price'])
476
+				? EEH_Money::convert_to_float_from_localized_money($ticket_data['TKT_base_price'])
477
+				: 0;
478
+			// if ticket price == 0 and $base_price != 0 then ticket price == base_price
479
+			$ticket_price  = $ticket_price === 0 && $base_price !== 0
480
+				? $base_price
481
+				: $ticket_price;
482
+			$base_price_id = $ticket_data['TKT_base_price_ID'] ?? 0;
483
+			$price_rows    = is_array($data['edit_prices']) && isset($data['edit_prices'][ $row ])
484
+				? $data['edit_prices'][ $row ]
485
+				: [];
486
+			$now           = null;
487
+			if (empty($ticket_data['TKT_start_date'])) {
488
+				// lets' use now in the set timezone.
489
+				$now                           = new DateTime('now', new DateTimeZone($event->get_timezone()));
490
+				$ticket_data['TKT_start_date'] = $now->format($this->_date_time_format);
491
+			}
492
+			if (empty($ticket_data['TKT_end_date'])) {
493
+				/**
494
+				 * set the TKT_end_date to the first datetime attached to the ticket.
495
+				 */
496
+				$first_datetime              = $saved_datetimes[ reset($ticket_datetime_rows) ];
497
+				$ticket_data['TKT_end_date'] = $first_datetime->start_date_and_time($this->_date_time_format);
498
+			}
499
+			$TKT_values = [
500
+				'TKT_ID'          => ! empty($ticket_data['TKT_ID']) ? $ticket_data['TKT_ID'] : null,
501
+				'TTM_ID'          => ! empty($ticket_data['TTM_ID']) ? $ticket_data['TTM_ID'] : 0,
502
+				'TKT_name'        => ! empty($ticket_data['TKT_name']) ? $ticket_data['TKT_name'] : '',
503
+				'TKT_description' => ! empty($ticket_data['TKT_description'])
504
+									 && $ticket_data['TKT_description'] !== esc_html__(
505
+										 'You can modify this description',
506
+										 'event_espresso'
507
+									 )
508
+					? $ticket_data['TKT_description']
509
+					: '',
510
+				'TKT_start_date'  => $ticket_data['TKT_start_date'],
511
+				'TKT_end_date'    => $ticket_data['TKT_end_date'],
512
+				'TKT_qty'         => ! isset($ticket_data['TKT_qty']) || $ticket_data['TKT_qty'] === ''
513
+					? EE_INF
514
+					: $ticket_data['TKT_qty'],
515
+				'TKT_uses'        => ! isset($ticket_data['TKT_uses']) || $ticket_data['TKT_uses'] === ''
516
+					? EE_INF
517
+					: $ticket_data['TKT_uses'],
518
+				'TKT_min'         => empty($ticket_data['TKT_min']) ? 0 : $ticket_data['TKT_min'],
519
+				'TKT_max'         => empty($ticket_data['TKT_max']) ? EE_INF : $ticket_data['TKT_max'],
520
+				'TKT_row'         => $row,
521
+				'TKT_order'       => $ticket_data['TKT_order'] ?? 0,
522
+				'TKT_taxable'     => ! empty($ticket_data['TKT_taxable']) ? 1 : 0,
523
+				'TKT_required'    => ! empty($ticket_data['TKT_required']) ? 1 : 0,
524
+				'TKT_price'       => $ticket_price,
525
+			];
526
+			// if this is a default TKT, then we need to set the TKT_ID to 0 and update accordingly,
527
+			// which means in turn that the prices will become new prices as well.
528
+			if (isset($ticket_data['TKT_is_default']) && $ticket_data['TKT_is_default']) {
529
+				$TKT_values['TKT_ID']         = 0;
530
+				$TKT_values['TKT_is_default'] = 0;
531
+				$update_prices                = true;
532
+			}
533
+			// if we have a TKT_ID then we need to get that existing TKT_obj and update it
534
+			// we actually do our saves ahead of doing any add_relations to
535
+			// because its entirely possible that this ticket wasn't removed or added to any datetime in the session
536
+			// but DID have it's items modified.
537
+			// keep in mind that if the TKT has been sold (and we have changed pricing information),
538
+			// then we won't be updating the ticket but instead a new ticket will be created and the old one archived.
539
+			if (absint($TKT_values['TKT_ID'])) {
540
+				$ticket = EEM_Ticket::instance($timezone)->get_one_by_ID($ticket_data['TKT_ID']);
541
+				if ($ticket instanceof EE_Ticket) {
542
+					$ticket = $this->_update_ticket_datetimes(
543
+						$ticket,
544
+						$saved_datetimes,
545
+						$datetimes_added,
546
+						$datetimes_removed
547
+					);
548
+					// are there any registrations using this ticket ?
549
+					$tickets_sold = $ticket->count_related(
550
+						'Registration',
551
+						[
552
+							[
553
+								'STS_ID' => ['NOT IN', [EEM_Registration::status_id_incomplete]],
554
+							],
555
+						]
556
+					);
557
+					// set ticket formats
558
+					$ticket->set_date_format($this->_date_format_strings['date']);
559
+					$ticket->set_time_format($this->_date_format_strings['time']);
560
+					// let's just check the total price for the existing ticket
561
+					// and determine if it matches the new total price.
562
+					// if they are different then we create a new ticket (if tickets sold)
563
+					// if they aren't different then we go ahead and modify existing ticket.
564
+					$create_new_TKT = $tickets_sold > 0 && $ticket_price !== $ticket->price() && ! $ticket->deleted();
565
+					// set new values
566
+					foreach ($TKT_values as $field => $value) {
567
+						if ($field === 'TKT_qty') {
568
+							$ticket->set_qty($value);
569
+						} else {
570
+							$ticket->set($field, $value);
571
+						}
572
+					}
573
+					// if $create_new_TKT is false then we can safely update the existing ticket.
574
+					// Otherwise we have to create a new ticket.
575
+					if ($create_new_TKT) {
576
+						$new_ticket = $this->_duplicate_ticket(
577
+							$ticket,
578
+							$price_rows,
579
+							$ticket_price,
580
+							$base_price,
581
+							$base_price_id
582
+						);
583
+					}
584
+				}
585
+			} else {
586
+				// no TKT_id so a new TKT
587
+				$ticket = EE_Ticket::new_instance(
588
+					$TKT_values,
589
+					$timezone,
590
+					[$this->_date_format_strings['date'], $this->_date_format_strings['time']]
591
+				);
592
+				if ($ticket instanceof EE_Ticket) {
593
+					// make sure ticket has an ID of setting relations won't work
594
+					$ticket->save();
595
+					$ticket        = $this->_update_ticket_datetimes(
596
+						$ticket,
597
+						$saved_datetimes,
598
+						$datetimes_added,
599
+						$datetimes_removed
600
+					);
601
+					$update_prices = true;
602
+				}
603
+			}
604
+			// make sure any current values have been saved.
605
+			// $ticket->save();
606
+			// before going any further make sure our dates are setup correctly
607
+			// so that the end date is always equal or greater than the start date.
608
+			if ($ticket->get_raw('TKT_start_date') > $ticket->get_raw('TKT_end_date')) {
609
+				$ticket->set('TKT_end_date', $ticket->get('TKT_start_date'));
610
+				$ticket = EEH_DTT_Helper::date_time_add($ticket, 'TKT_end_date', 'days');
611
+			}
612
+			// let's make sure the base price is handled
613
+			$ticket = ! $create_new_TKT
614
+				? $this->_add_prices_to_ticket(
615
+					[],
616
+					$ticket,
617
+					$update_prices,
618
+					$base_price,
619
+					$base_price_id
620
+				)
621
+				: $ticket;
622
+			// add/update price_modifiers
623
+			$ticket = ! $create_new_TKT
624
+				? $this->_add_prices_to_ticket($price_rows, $ticket, $update_prices)
625
+				: $ticket;
626
+			// need to make sue that the TKT_price is accurate after saving the prices.
627
+			$ticket->ensure_TKT_Price_correct();
628
+			// handle CREATING a default ticket from the incoming ticket but ONLY if this isn't an autosave.
629
+			if (! defined('DOING_AUTOSAVE') && ! empty($ticket_data['TKT_is_default_selector'])) {
630
+				$new_default = clone $ticket;
631
+				$new_default->set('TKT_ID', 0);
632
+				$new_default->set('TKT_is_default', 1);
633
+				$new_default->set('TKT_row', 1);
634
+				$new_default->set('TKT_price', $ticket_price);
635
+				// remove any datetime relations cause we DON'T want datetime relations attached
636
+				// (note this is just removing the cached relations in the object)
637
+				$new_default->_remove_relations('Datetime');
638
+				// @todo we need to add the current attached prices as new prices to the new default ticket.
639
+				$new_default = $this->_add_prices_to_ticket(
640
+					$price_rows,
641
+					$new_default,
642
+					true
643
+				);
644
+				// don't forget the base price!
645
+				$new_default = $this->_add_prices_to_ticket(
646
+					[],
647
+					$new_default,
648
+					true,
649
+					$base_price,
650
+					$base_price_id
651
+				);
652
+				$new_default->save();
653
+				do_action(
654
+					'AHEE__espresso_events_Pricing_Hooks___update_tkts_new_default_ticket',
655
+					$new_default,
656
+					$row,
657
+					$ticket,
658
+					$data
659
+				);
660
+			}
661
+			// DO ALL datetime relationships for both current tickets and any archived tickets
662
+			// for the given datetime that are related to the current ticket.
663
+			// TODO... not sure exactly how we're going to do this considering we don't know
664
+			// what current ticket the archived tickets are related to
665
+			// (and TKT_parent is used for autosaves so that's not a field we can reliably use).
666
+			// let's assign any tickets that have been setup to the saved_tickets tracker
667
+			// save existing TKT
668
+			$ticket->save();
669
+			if ($create_new_TKT && $new_ticket instanceof EE_Ticket) {
670
+				// save new TKT
671
+				$new_ticket->save();
672
+				// add new ticket to array
673
+				$saved_tickets[ $new_ticket->ID() ] = $new_ticket;
674
+				do_action(
675
+					'AHEE__espresso_events_Pricing_Hooks___update_tkts_new_ticket',
676
+					$new_ticket,
677
+					$row,
678
+					$ticket_data,
679
+					$data
680
+				);
681
+			} else {
682
+				// add ticket to saved tickets
683
+				$saved_tickets[ $ticket->ID() ] = $ticket;
684
+				do_action(
685
+					'AHEE__espresso_events_Pricing_Hooks___update_tkts_update_ticket',
686
+					$ticket,
687
+					$row,
688
+					$ticket_data,
689
+					$data
690
+				);
691
+			}
692
+		}
693
+		// now we need to handle tickets actually "deleted permanently".
694
+		// There are cases where we'd want this to happen
695
+		// (i.e. autosaves are happening and then in between autosaves the user trashes a ticket).
696
+		// Or a draft event was saved and in the process of editing a ticket is trashed.
697
+		// No sense in keeping all the related data in the db!
698
+		$old_tickets     = isset($old_tickets[0]) && $old_tickets[0] === '' ? [] : $old_tickets;
699
+		$tickets_removed = array_diff($old_tickets, array_keys($saved_tickets));
700
+		foreach ($tickets_removed as $id) {
701
+			$id = absint($id);
702
+			// get the ticket for this id
703
+			$ticket_to_remove = EE_Registry::instance()->load_model('Ticket')->get_one_by_ID($id);
704
+			// if this ticket is a default ticket we leave it alone cause it won't be attached to the datetime
705
+			if ($ticket_to_remove->get('TKT_is_default')) {
706
+				continue;
707
+			}
708
+			// if this ticket has any registrations attached so then we just ARCHIVE
709
+			// because we don't actually permanently delete these tickets.
710
+			if ($ticket_to_remove->count_related('Registration') > 0) {
711
+				$ticket_to_remove->delete();
712
+				continue;
713
+			}
714
+			// need to get all the related datetimes on this ticket and remove from every single one of them
715
+			// (remember this process can ONLY kick off if there are NO tickets_sold)
716
+			$datetimes = $ticket_to_remove->get_many_related('Datetime');
717
+			foreach ($datetimes as $datetime) {
718
+				$ticket_to_remove->_remove_relation_to($datetime, 'Datetime');
719
+			}
720
+			// need to do the same for prices (except these prices can also be deleted because again,
721
+			// tickets can only be trashed if they don't have any TKTs sold (otherwise they are just archived))
722
+			$ticket_to_remove->delete_related_permanently('Price');
723
+			do_action('AHEE__espresso_events_Pricing_Hooks___update_tkts_delete_ticket', $ticket_to_remove);
724
+			// finally let's delete this ticket
725
+			// (which should not be blocked at this point b/c we've removed all our relationships)
726
+			$ticket_to_remove->delete_permanently();
727
+		}
728
+		return $saved_tickets;
729
+	}
730
+
731
+
732
+	/**
733
+	 * @access  protected
734
+	 * @param EE_Ticket     $ticket
735
+	 * @param EE_Datetime[] $saved_datetimes
736
+	 * @param int[]         $added_datetimes
737
+	 * @param int[]         $removed_datetimes
738
+	 * @return EE_Ticket
739
+	 * @throws EE_Error
740
+	 * @throws ReflectionException
741
+	 */
742
+	protected function _update_ticket_datetimes(
743
+		EE_Ticket $ticket,
744
+		array $saved_datetimes = [],
745
+		array $added_datetimes = [],
746
+		array $removed_datetimes = []
747
+	): EE_Ticket {
748
+		// to start we have to add the ticket to all the datetimes its supposed to be with,
749
+		// and removing the ticket from datetimes it got removed from.
750
+		// first let's add datetimes
751
+		if (! empty($added_datetimes) && is_array($added_datetimes)) {
752
+			foreach ($added_datetimes as $row_id) {
753
+				if (isset($saved_datetimes[ $row_id ]) && $saved_datetimes[ $row_id ] instanceof EE_Datetime) {
754
+					$ticket->_add_relation_to($saved_datetimes[ $row_id ], 'Datetime');
755
+					// Is this an existing ticket (has an ID) and does it have any sold?
756
+					// If so, then we need to add that to the DTT sold because this DTT is getting added.
757
+					if ($ticket->ID() && $ticket->sold() > 0) {
758
+						$saved_datetimes[ $row_id ]->increaseSold($ticket->sold(), false);
759
+					}
760
+				}
761
+			}
762
+		}
763
+		// then remove datetimes
764
+		if (! empty($removed_datetimes) && is_array($removed_datetimes)) {
765
+			foreach ($removed_datetimes as $row_id) {
766
+				// its entirely possible that a datetime got deleted (instead of just removed from relationship.
767
+				// So make sure we skip over this if the datetime isn't in the $saved_datetimes array)
768
+				if (isset($saved_datetimes[ $row_id ]) && $saved_datetimes[ $row_id ] instanceof EE_Datetime) {
769
+					$ticket->_remove_relation_to($saved_datetimes[ $row_id ], 'Datetime');
770
+				}
771
+			}
772
+		}
773
+		// cap ticket qty by datetime reg limits
774
+		$ticket->set_qty(min($ticket->qty(), $ticket->qty('reg_limit')));
775
+		return $ticket;
776
+	}
777
+
778
+
779
+	/**
780
+	 * @access  protected
781
+	 * @param EE_Ticket $ticket
782
+	 * @param array     $price_rows
783
+	 * @param int|float $ticket_price
784
+	 * @param int|float $base_price
785
+	 * @param int       $base_price_id
786
+	 * @return EE_Ticket
787
+	 * @throws ReflectionException
788
+	 * @throws InvalidArgumentException
789
+	 * @throws InvalidInterfaceException
790
+	 * @throws InvalidDataTypeException
791
+	 * @throws EE_Error
792
+	 */
793
+	protected function _duplicate_ticket(
794
+		EE_Ticket $ticket,
795
+		array $price_rows = [],
796
+		$ticket_price = 0,
797
+		$base_price = 0,
798
+		int $base_price_id = 0
799
+	): EE_Ticket {
800
+		// create new ticket that's a copy of the existing
801
+		// except a new id of course (and not archived)
802
+		// AND has the new TKT_price associated with it.
803
+		$new_ticket = clone $ticket;
804
+		$new_ticket->set('TKT_ID', 0);
805
+		$new_ticket->set_deleted(0);
806
+		$new_ticket->set_price($ticket_price);
807
+		$new_ticket->set_sold(0);
808
+		// let's get a new ID for this ticket
809
+		$new_ticket->save();
810
+		// we also need to make sure this new ticket gets the same datetime attachments as the archived ticket
811
+		$datetimes_on_existing = $ticket->datetimes();
812
+		$new_ticket            = $this->_update_ticket_datetimes(
813
+			$new_ticket,
814
+			$datetimes_on_existing,
815
+			array_keys($datetimes_on_existing)
816
+		);
817
+		// $ticket will get archived later b/c we are NOT adding it to the saved_tickets array.
818
+		// if existing $ticket has sold amount, then we need to adjust the qty for the new TKT to = the remaining
819
+		// available.
820
+		if ($ticket->sold() > 0) {
821
+			$new_qty = $ticket->qty() - $ticket->sold();
822
+			$new_ticket->set_qty($new_qty);
823
+		}
824
+		// now we update the prices just for this ticket
825
+		$new_ticket = $this->_add_prices_to_ticket($price_rows, $new_ticket, true);
826
+		// and we update the base price
827
+		return $this->_add_prices_to_ticket(
828
+			[],
829
+			$new_ticket,
830
+			true,
831
+			$base_price,
832
+			$base_price_id
833
+		);
834
+	}
835
+
836
+
837
+	/**
838
+	 * This attaches a list of given prices to a ticket.
839
+	 * Note we dont' have to worry about ever removing relationships (or archiving prices) because if there is a change
840
+	 * in price information on a ticket, a new ticket is created anyways so the archived ticket will retain the old
841
+	 * price info and prices are automatically "archived" via the ticket.
842
+	 *
843
+	 * @access  private
844
+	 * @param array     $prices        Array of prices from the form.
845
+	 * @param EE_Ticket $ticket        EE_Ticket object that prices are being attached to.
846
+	 * @param bool      $new_prices    Whether attach existing incoming prices or create new ones.
847
+	 * @param int|bool  $base_price    if FALSE then NOT doing a base price add.
848
+	 * @param int|bool  $base_price_id if present then this is the base_price_id being updated.
849
+	 * @return EE_Ticket
850
+	 * @throws ReflectionException
851
+	 * @throws InvalidArgumentException
852
+	 * @throws InvalidInterfaceException
853
+	 * @throws InvalidDataTypeException
854
+	 * @throws EE_Error
855
+	 */
856
+	protected function _add_prices_to_ticket(
857
+		array $prices,
858
+		EE_Ticket $ticket,
859
+		bool $new_prices = false,
860
+		$base_price = false,
861
+		$base_price_id = false
862
+	): EE_Ticket {
863
+		// let's just get any current prices that may exist on the given ticket
864
+		// so we can remove any prices that got trashed in this session.
865
+		$current_prices_on_ticket = $base_price !== false
866
+			? $ticket->base_price(true)
867
+			: $ticket->price_modifiers();
868
+		$updated_prices           = [];
869
+		// if $base_price ! FALSE then updating a base price.
870
+		if ($base_price !== false) {
871
+			$prices[1] = [
872
+				'PRC_ID'     => $new_prices || $base_price_id === 1 ? null : $base_price_id,
873
+				'PRT_ID'     => 1,
874
+				'PRC_amount' => $base_price,
875
+				'PRC_name'   => $ticket->get('TKT_name'),
876
+				'PRC_desc'   => $ticket->get('TKT_description'),
877
+			];
878
+		}
879
+		// possibly need to save ticket
880
+		if (! $ticket->ID()) {
881
+			$ticket->save();
882
+		}
883
+		foreach ($prices as $row => $prc) {
884
+			$prt_id = ! empty($prc['PRT_ID']) ? $prc['PRT_ID'] : null;
885
+			if (empty($prt_id)) {
886
+				continue;
887
+			} //prices MUST have a price type id.
888
+			$PRC_values = [
889
+				'PRC_ID'         => ! empty($prc['PRC_ID']) ? $prc['PRC_ID'] : null,
890
+				'PRT_ID'         => $prt_id,
891
+				'PRC_amount'     => ! empty($prc['PRC_amount']) ? $prc['PRC_amount'] : 0,
892
+				'PRC_name'       => ! empty($prc['PRC_name']) ? $prc['PRC_name'] : '',
893
+				'PRC_desc'       => ! empty($prc['PRC_desc']) ? $prc['PRC_desc'] : '',
894
+				'PRC_is_default' => false,
895
+				// make sure we set PRC_is_default to false for all ticket saves from event_editor
896
+				'PRC_order'      => $row,
897
+			];
898
+			if ($new_prices || empty($PRC_values['PRC_ID'])) {
899
+				$PRC_values['PRC_ID'] = 0;
900
+				$price                = EE_Registry::instance()->load_class(
901
+					'Price',
902
+					[$PRC_values],
903
+					false,
904
+					false
905
+				);
906
+			} else {
907
+				$price = EE_Registry::instance()->load_model('Price')->get_one_by_ID($prc['PRC_ID']);
908
+				// update this price with new values
909
+				foreach ($PRC_values as $field => $value) {
910
+					$price->set($field, $value);
911
+				}
912
+			}
913
+			$price->save();
914
+			$updated_prices[ $price->ID() ] = $price;
915
+			$ticket->_add_relation_to($price, 'Price');
916
+		}
917
+		// now let's remove any prices that got removed from the ticket
918
+		if (! empty($current_prices_on_ticket)) {
919
+			$current          = array_keys($current_prices_on_ticket);
920
+			$updated          = array_keys($updated_prices);
921
+			$prices_to_remove = array_diff($current, $updated);
922
+			if (! empty($prices_to_remove)) {
923
+				foreach ($prices_to_remove as $prc_id) {
924
+					$p = $current_prices_on_ticket[ $prc_id ];
925
+					$ticket->_remove_relation_to($p, 'Price');
926
+					// delete permanently the price
927
+					$p->delete_permanently();
928
+				}
929
+			}
930
+		}
931
+		return $ticket;
932
+	}
933
+
934
+
935
+	/**
936
+	 * @throws ReflectionException
937
+	 * @throws InvalidArgumentException
938
+	 * @throws InvalidInterfaceException
939
+	 * @throws InvalidDataTypeException
940
+	 * @throws DomainException
941
+	 * @throws EE_Error
942
+	 */
943
+	public function pricing_metabox()
944
+	{
945
+		$existing_datetime_ids = $existing_ticket_ids = $datetime_tickets = $ticket_datetimes = [];
946
+		$event                 = $this->_adminpage_obj->get_cpt_model_obj();
947
+
948
+		// set is_creating_event property.
949
+		$EVT_ID                   = $event->ID();
950
+		$this->_is_creating_event = empty($this->_req_data['post']);
951
+
952
+		// default main template args
953
+		$main_template_args = [
954
+			'event_datetime_help_link' => EEH_Template::get_help_tab_link(
955
+				'event_editor_event_datetimes_help_tab',
956
+				$this->_adminpage_obj->page_slug,
957
+				$this->_adminpage_obj->get_req_action()
958
+			),
959
+
960
+			// todo need to add a filter to the template for the help text
961
+			// in the Events_Admin_Page core file so we can add further help
962
+			'add_new_dtt_help_link'    => EEH_Template::get_help_tab_link(
963
+				'add_new_dtt_info',
964
+				$this->_adminpage_obj->page_slug,
965
+				$this->_adminpage_obj->get_req_action()
966
+			),
967
+			// todo need to add this help info id to the Events_Admin_Page core file so we can access it here.
968
+			'datetime_rows'            => '',
969
+			'show_tickets_container'   => '',
970
+			'ticket_rows'              => '',
971
+			'ee_collapsible_status'    => ' ee-collapsible-open',
972
+		];
973
+		$timezone           = $event instanceof EE_Event ? $event->timezone_string() : null;
974
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
975
+
976
+		/**
977
+		 * 1. Start with retrieving Datetimes
978
+		 * 2. For each datetime get related tickets
979
+		 * 3. For each ticket get related prices
980
+		 */
981
+		/** @var EEM_Datetime $datetime_model */
982
+		$datetime_model                       = EE_Registry::instance()->load_model('Datetime', [$timezone]);
983
+		$datetimes                            = $datetime_model->get_all_event_dates($EVT_ID);
984
+		$main_template_args['total_dtt_rows'] = count($datetimes);
985
+
986
+		/**
987
+		 * @see https://events.codebasehq.com/projects/event-espresso/tickets/9486
988
+		 * for why we are counting $datetime_row and then setting that on the Datetime object
989
+		 */
990
+		$datetime_row = 1;
991
+		foreach ($datetimes as $datetime) {
992
+			$DTT_ID = $datetime->get('DTT_ID');
993
+			$datetime->set('DTT_order', $datetime_row);
994
+			$existing_datetime_ids[] = $DTT_ID;
995
+			// tickets attached
996
+			$related_tickets = $datetime->ID() > 0
997
+				? $datetime->get_many_related(
998
+					'Ticket',
999
+					[
1000
+						[
1001
+							'OR' => ['TKT_deleted' => 1, 'TKT_deleted*' => 0],
1002
+						],
1003
+						'default_where_conditions' => 'none',
1004
+						'order_by'                 => ['TKT_order' => 'ASC'],
1005
+					]
1006
+				)
1007
+				: [];
1008
+			// if there are no related tickets this is likely a new event OR auto-draft
1009
+			// event so we need to generate the default tickets because datetimes
1010
+			// ALWAYS have at least one related ticket!!.  EXCEPT, we dont' do this if there is already more than one
1011
+			// datetime on the event.
1012
+			if (empty($related_tickets) && count($datetimes) < 2) {
1013
+				/** @var EEM_Ticket $ticket_model */
1014
+				$ticket_model    = EE_Registry::instance()->load_model('Ticket');
1015
+				$related_tickets = $ticket_model->get_all_default_tickets();
1016
+				// this should be ordered by TKT_ID, so let's grab the first default ticket
1017
+				// (which will be the main default) and ensure it has any default prices added to it (but do NOT save).
1018
+				$default_prices      = EEM_Price::instance()->get_all_default_prices();
1019
+				$main_default_ticket = reset($related_tickets);
1020
+				if ($main_default_ticket instanceof EE_Ticket) {
1021
+					foreach ($default_prices as $default_price) {
1022
+						if ($default_price instanceof EE_Price && $default_price->is_base_price()) {
1023
+							continue;
1024
+						}
1025
+						$main_default_ticket->cache('Price', $default_price);
1026
+					}
1027
+				}
1028
+			}
1029
+			// we can't actually setup rows in this loop yet cause we don't know all
1030
+			// the unique tickets for this event yet (tickets are linked through all datetimes).
1031
+			// So we're going to temporarily cache some of that information.
1032
+			// loop through and setup the ticket rows and make sure the order is set.
1033
+			foreach ($related_tickets as $ticket) {
1034
+				$TKT_ID     = $ticket->get('TKT_ID');
1035
+				$ticket_row = $ticket->get('TKT_row');
1036
+				// we only want unique tickets in our final display!!
1037
+				if (! in_array($TKT_ID, $existing_ticket_ids, true)) {
1038
+					$existing_ticket_ids[] = $TKT_ID;
1039
+					$all_tickets[]         = $ticket;
1040
+				}
1041
+				// temporary cache of this ticket info for this datetime for later processing of datetime rows.
1042
+				$datetime_tickets[ $DTT_ID ][] = $ticket_row;
1043
+				// temporary cache of this datetime info for this ticket for later processing of ticket rows.
1044
+				if (
1045
+					! isset($ticket_datetimes[ $TKT_ID ])
1046
+					|| ! in_array($datetime_row, $ticket_datetimes[ $TKT_ID ], true)
1047
+				) {
1048
+					$ticket_datetimes[ $TKT_ID ][] = $datetime_row;
1049
+				}
1050
+			}
1051
+			$datetime_row++;
1052
+		}
1053
+		$main_template_args['total_ticket_rows']     = count($existing_ticket_ids);
1054
+		$main_template_args['existing_ticket_ids']   = implode(',', $existing_ticket_ids);
1055
+		$main_template_args['existing_datetime_ids'] = implode(',', $existing_datetime_ids);
1056
+		// sort $all_tickets by order
1057
+		usort(
1058
+			$all_tickets,
1059
+			function (EE_Ticket $a, EE_Ticket $b) {
1060
+				$a_order = (int) $a->get('TKT_order');
1061
+				$b_order = (int) $b->get('TKT_order');
1062
+				if ($a_order === $b_order) {
1063
+					return 0;
1064
+				}
1065
+				return ($a_order < $b_order) ? -1 : 1;
1066
+			}
1067
+		);
1068
+		// k NOW we have all the data we need for setting up the datetime rows
1069
+		// and ticket rows so we start our datetime loop again.
1070
+		$datetime_row = 1;
1071
+		foreach ($datetimes as $datetime) {
1072
+			$main_template_args['datetime_rows'] .= $this->_get_datetime_row(
1073
+				$datetime_row,
1074
+				$datetime,
1075
+				$datetime_tickets,
1076
+				$all_tickets,
1077
+				false,
1078
+				$datetimes
1079
+			);
1080
+			$datetime_row++;
1081
+		}
1082
+		// then loop through all tickets for the ticket rows.
1083
+		$ticket_row = 1;
1084
+		foreach ($all_tickets as $ticket) {
1085
+			$main_template_args['ticket_rows'] .= $this->_get_ticket_row(
1086
+				$ticket_row,
1087
+				$ticket,
1088
+				$ticket_datetimes,
1089
+				$datetimes,
1090
+				false,
1091
+				$all_tickets
1092
+			);
1093
+			$ticket_row++;
1094
+		}
1095
+		$main_template_args['ticket_js_structure'] = $this->_get_ticket_js_structure($datetimes, $all_tickets);
1096
+
1097
+		$status_change_notice = LoaderFactory::getLoader()->getShared(
1098
+			'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
1099
+		);
1100
+
1101
+		$main_template_args['status_change_notice'] = $status_change_notice->display(
1102
+			'__event-editor',
1103
+			'espresso-events'
1104
+		);
1105
+
1106
+		EEH_Template::display_template(
1107
+			PRICING_TEMPLATE_PATH . 'event_tickets_metabox_main.template.php',
1108
+			$main_template_args
1109
+		);
1110
+	}
1111
+
1112
+
1113
+	/**
1114
+	 * @param int|string  $datetime_row
1115
+	 * @param EE_Datetime $datetime
1116
+	 * @param array       $datetime_tickets
1117
+	 * @param array       $all_tickets
1118
+	 * @param bool        $default
1119
+	 * @param array       $all_datetimes
1120
+	 * @return string
1121
+	 * @throws DomainException
1122
+	 * @throws EE_Error
1123
+	 * @throws ReflectionException
1124
+	 */
1125
+	protected function _get_datetime_row(
1126
+		$datetime_row,
1127
+		EE_Datetime $datetime,
1128
+		array $datetime_tickets = [],
1129
+		array $all_tickets = [],
1130
+		bool $default = false,
1131
+		array $all_datetimes = []
1132
+	): string {
1133
+		return EEH_Template::display_template(
1134
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_row_wrapper.template.php',
1135
+			[
1136
+				'dtt_edit_row'             => $this->_get_dtt_edit_row(
1137
+					$datetime_row,
1138
+					$datetime,
1139
+					$default,
1140
+					$all_datetimes
1141
+				),
1142
+				'dtt_attached_tickets_row' => $this->_get_dtt_attached_tickets_row(
1143
+					$datetime_row,
1144
+					$datetime,
1145
+					$datetime_tickets,
1146
+					$all_tickets,
1147
+					$default
1148
+				),
1149
+				'dtt_row'                  => $default ? 'DTTNUM' : $datetime_row,
1150
+			],
1151
+			true
1152
+		);
1153
+	}
1154
+
1155
+
1156
+	/**
1157
+	 * This method is used to generate a datetime fields  edit row.
1158
+	 * The same row is used to generate a row with valid DTT objects
1159
+	 * and the default row that is used as the skeleton by the js.
1160
+	 *
1161
+	 * @param int|string       $datetime_row  The row number for the row being generated.
1162
+	 * @param EE_Datetime|null $datetime
1163
+	 * @param bool             $default       Whether a default row is being generated or not.
1164
+	 * @param EE_Datetime[]    $all_datetimes This is the array of all datetimes used in the editor.
1165
+	 * @return string
1166
+	 * @throws EE_Error
1167
+	 * @throws ReflectionException
1168
+	 */
1169
+	protected function _get_dtt_edit_row(
1170
+		$datetime_row,
1171
+		?EE_Datetime $datetime,
1172
+		bool $default,
1173
+		array $all_datetimes
1174
+	): string {
1175
+		// if the incoming $datetime object is NOT an instance of EE_Datetime then force default to true.
1176
+		$default                     = ! $datetime instanceof EE_Datetime ? true : $default;
1177
+		$template_args               = [
1178
+			'dtt_row'              => $default ? 'DTTNUM' : $datetime_row,
1179
+			'event_datetimes_name' => $default ? 'DTTNAMEATTR' : 'edit_event_datetimes',
1180
+			'edit_dtt_expanded'    => '',
1181
+			'DTT_ID'               => $default ? '' : $datetime->ID(),
1182
+			'DTT_name'             => $default ? '' : $datetime->get_f('DTT_name'),
1183
+			'DTT_description'      => $default ? '' : $datetime->get_f('DTT_description'),
1184
+			'DTT_EVT_start'        => $default ? '' : $datetime->start_date($this->_date_time_format),
1185
+			'DTT_EVT_end'          => $default ? '' : $datetime->end_date($this->_date_time_format),
1186
+			'DTT_reg_limit'        => $default
1187
+				? ''
1188
+				: $datetime->get_pretty(
1189
+					'DTT_reg_limit',
1190
+					'input'
1191
+				),
1192
+			'DTT_order'            => $default ? 'DTTNUM' : $datetime_row,
1193
+			'dtt_sold'             => $default ? '0' : $datetime->get('DTT_sold'),
1194
+			'dtt_reserved'         => $default ? '0' : $datetime->reserved(),
1195
+			'clone_icon'           => ! empty($datetime) && $datetime->get('DTT_sold') > 0
1196
+				? ''
1197
+				: 'clone-icon ee-icon ee-icon-clone clickable',
1198
+			'trash_icon'           => ! empty($datetime) && $datetime->get('DTT_sold') > 0
1199
+				? 'dashicons dashicons-lock'
1200
+				: 'trash-icon dashicons dashicons-post-trash clickable',
1201
+			'reg_list_url'         => $default || ! $datetime->event() instanceof EE_Event
1202
+				? ''
1203
+				: EE_Admin_Page::add_query_args_and_nonce(
1204
+					[
1205
+						'event_id' => $datetime->event()->ID(),
1206
+						'datetime_id' => $datetime->ID(),
1207
+						'use_filters' => true
1208
+					],
1209
+					REG_ADMIN_URL
1210
+				),
1211
+		];
1212
+		$template_args['show_trash'] = count($all_datetimes) === 1
1213
+									   && $template_args['trash_icon'] !== 'dashicons dashicons-lock'
1214
+			? 'display:none'
1215
+			: '';
1216
+		// allow filtering of template args at this point.
1217
+		$template_args = apply_filters(
1218
+			'FHEE__espresso_events_Pricing_Hooks___get_dtt_edit_row__template_args',
1219
+			$template_args,
1220
+			$datetime_row,
1221
+			$datetime,
1222
+			$default,
1223
+			$all_datetimes,
1224
+			$this->_is_creating_event
1225
+		);
1226
+		return EEH_Template::display_template(
1227
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_edit_row.template.php',
1228
+			$template_args,
1229
+			true
1230
+		);
1231
+	}
1232
+
1233
+
1234
+	/**
1235
+	 * @param int|string       $datetime_row
1236
+	 * @param EE_Datetime|null $datetime
1237
+	 * @param array            $datetime_tickets
1238
+	 * @param array            $all_tickets
1239
+	 * @param bool             $default
1240
+	 * @return string
1241
+	 * @throws DomainException
1242
+	 * @throws EE_Error
1243
+	 * @throws ReflectionException
1244
+	 */
1245
+	protected function _get_dtt_attached_tickets_row(
1246
+		$datetime_row,
1247
+		?EE_Datetime $datetime,
1248
+		array $datetime_tickets = [],
1249
+		array $all_tickets = [],
1250
+		bool $default = false
1251
+	): string {
1252
+		$default = $default || ! $datetime instanceof EE_Datetime;
1253
+		$template_args = [
1254
+			'dtt_row'                           => $default ? 'DTTNUM' : $datetime_row,
1255
+			'event_datetimes_name'              => $default ? 'DTTNAMEATTR' : 'edit_event_datetimes',
1256
+			'DTT_description'                   => $default ? '' : $datetime->get_f('DTT_description'),
1257
+			'datetime_tickets_list'             => $default ? '<li class="hidden"></li>' : '',
1258
+			'show_tickets_row'                  => 'display:none;',
1259
+			'add_new_datetime_ticket_help_link' => EEH_Template::get_help_tab_link(
1260
+				'add_new_ticket_via_datetime',
1261
+				$this->_adminpage_obj->page_slug,
1262
+				$this->_adminpage_obj->get_req_action()
1263
+			),
1264
+			// todo need to add this help info id to the Events_Admin_Page core file so we can access it here.
1265
+			'DTT_ID'                            => $default ? '' : $datetime->ID(),
1266
+		];
1267
+		// need to setup the list items (but only if this isn't a default skeleton setup)
1268
+		if (! $default) {
1269
+			$ticket_row = 1;
1270
+			foreach ($all_tickets as $ticket) {
1271
+				$template_args['datetime_tickets_list'] .= $this->_get_datetime_tickets_list_item(
1272
+					$datetime_row,
1273
+					$ticket_row,
1274
+					$datetime,
1275
+					$ticket,
1276
+					$datetime_tickets,
1277
+					$default
1278
+				);
1279
+				$ticket_row++;
1280
+			}
1281
+		}
1282
+		// filter template args at this point
1283
+		$template_args = apply_filters(
1284
+			'FHEE__espresso_events_Pricing_Hooks___get_dtt_attached_ticket_row__template_args',
1285
+			$template_args,
1286
+			$datetime_row,
1287
+			$datetime,
1288
+			$datetime_tickets,
1289
+			$all_tickets,
1290
+			$default,
1291
+			$this->_is_creating_event
1292
+		);
1293
+		return EEH_Template::display_template(
1294
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_attached_tickets_row.template.php',
1295
+			$template_args,
1296
+			true
1297
+		);
1298
+	}
1299
+
1300
+
1301
+	/**
1302
+	 * @param int|string       $datetime_row
1303
+	 * @param int|string       $ticket_row
1304
+	 * @param EE_Datetime|null $datetime
1305
+	 * @param EE_Ticket|null   $ticket
1306
+	 * @param array            $datetime_tickets
1307
+	 * @param bool             $default
1308
+	 * @return string
1309
+	 * @throws EE_Error
1310
+	 * @throws ReflectionException
1311
+	 */
1312
+	protected function _get_datetime_tickets_list_item(
1313
+		$datetime_row,
1314
+		$ticket_row,
1315
+		?EE_Datetime $datetime,
1316
+		?EE_Ticket $ticket,
1317
+		array $datetime_tickets = [],
1318
+		bool $default = false
1319
+	): string {
1320
+		$datetime_tickets = $datetime instanceof EE_Datetime && isset($datetime_tickets[ $datetime->ID() ])
1321
+			? $datetime_tickets[ $datetime->ID() ]
1322
+			: [];
1323
+		$display_row      = $ticket instanceof EE_Ticket ? $ticket->get('TKT_row') : 0;
1324
+		$no_ticket        = $default && empty($ticket);
1325
+		$template_args    = [
1326
+			'dtt_row'                 => $default
1327
+				? 'DTTNUM'
1328
+				: $datetime_row,
1329
+			'tkt_row'                 => $no_ticket
1330
+				? 'TICKETNUM'
1331
+				: $ticket_row,
1332
+			'datetime_ticket_checked' => in_array($display_row, $datetime_tickets, true)
1333
+				? ' checked'
1334
+				: '',
1335
+			'ticket_selected'         => in_array($display_row, $datetime_tickets, true)
1336
+				? ' ticket-selected'
1337
+				: '',
1338
+			'TKT_name'                => $no_ticket
1339
+				? 'TKTNAME'
1340
+				: $ticket->get('TKT_name'),
1341
+			'tkt_status_class'        => $no_ticket || $this->_is_creating_event
1342
+				? ' tkt-status-' . EE_Ticket::onsale
1343
+				: ' tkt-status-' . $ticket->ticket_status(),
1344
+		];
1345
+		// filter template args
1346
+		$template_args = apply_filters(
1347
+			'FHEE__espresso_events_Pricing_Hooks___get_datetime_tickets_list_item__template_args',
1348
+			$template_args,
1349
+			$datetime_row,
1350
+			$ticket_row,
1351
+			$datetime,
1352
+			$ticket,
1353
+			$datetime_tickets,
1354
+			$default,
1355
+			$this->_is_creating_event
1356
+		);
1357
+		return EEH_Template::display_template(
1358
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_dtt_tickets_list.template.php',
1359
+			$template_args,
1360
+			true
1361
+		);
1362
+	}
1363
+
1364
+
1365
+	/**
1366
+	 * This generates the ticket row for tickets.
1367
+	 * This same method is used to generate both the actual rows and the js skeleton row
1368
+	 * (when default === true)
1369
+	 *
1370
+	 * @param int|string     $ticket_row       Represents the row number being generated.
1371
+	 * @param EE_Ticket|null $ticket
1372
+	 * @param EE_Datetime[]  $ticket_datetimes Either an array of all datetimes on all tickets indexed by each ticket
1373
+	 *                                         or empty for default
1374
+	 * @param EE_Datetime[]  $all_datetimes    All Datetimes on the event or empty for default.
1375
+	 * @param bool           $default          Whether default row being generated or not.
1376
+	 * @param EE_Ticket[]    $all_tickets      This is an array of all tickets attached to the event
1377
+	 *                                         (or empty in the case of defaults)
1378
+	 * @return string
1379
+	 * @throws InvalidArgumentException
1380
+	 * @throws InvalidInterfaceException
1381
+	 * @throws InvalidDataTypeException
1382
+	 * @throws DomainException
1383
+	 * @throws EE_Error
1384
+	 * @throws ReflectionException
1385
+	 */
1386
+	protected function _get_ticket_row(
1387
+		$ticket_row,
1388
+		?EE_Ticket $ticket,
1389
+		array $ticket_datetimes,
1390
+		array $all_datetimes,
1391
+		bool $default = false,
1392
+		array $all_tickets = []
1393
+	): string {
1394
+		// if $ticket is not an instance of EE_Ticket then force default to true.
1395
+		$default = ! $ticket instanceof EE_Ticket ? true : $default;
1396
+		$prices  = ! empty($ticket) && ! $default
1397
+			? $ticket->get_many_related(
1398
+				'Price',
1399
+				['default_where_conditions' => 'none', 'order_by' => ['PRC_order' => 'ASC']]
1400
+			)
1401
+			: [];
1402
+		// if there is only one price (which would be the base price)
1403
+		// or NO prices and this ticket is a default ticket,
1404
+		// let's just make sure there are no cached default prices on the object.
1405
+		// This is done by not including any query_params.
1406
+		if ($ticket instanceof EE_Ticket && $ticket->is_default() && (count($prices) === 1 || empty($prices))) {
1407
+			$prices = $ticket->prices();
1408
+		}
1409
+		// check if we're dealing with a default ticket in which case
1410
+		// we don't want any starting_ticket_datetime_row values set
1411
+		// (otherwise there won't be any new relationships created for tickets based off of the default ticket).
1412
+		// This will future proof in case there is ever any behaviour change between what the primary_key defaults to.
1413
+		$default_datetime = $default || ($ticket instanceof EE_Ticket && $ticket->is_default());
1414
+		$ticket_datetimes = $ticket instanceof EE_Ticket && isset($ticket_datetimes[ $ticket->ID() ])
1415
+			? $ticket_datetimes[ $ticket->ID() ]
1416
+			: [];
1417
+		$ticket_subtotal  = $default ? 0 : $ticket->get_ticket_subtotal();
1418
+		$base_price       = $default ? null : $ticket->base_price();
1419
+		$count_price_mods = EEM_Price::instance()->get_all_default_prices(true);
1420
+		// breaking out complicated condition for ticket_status
1421
+		if ($default) {
1422
+			$ticket_status_class = ' tkt-status-' . EE_Ticket::onsale;
1423
+		} else {
1424
+			$ticket_status_class = $ticket->is_default()
1425
+				? ' tkt-status-' . EE_Ticket::onsale
1426
+				: ' tkt-status-' . $ticket->ticket_status();
1427
+		}
1428
+		// breaking out complicated condition for TKT_taxable
1429
+		if ($default) {
1430
+			$TKT_taxable = '';
1431
+		} else {
1432
+			$TKT_taxable = $ticket->taxable()
1433
+				? 'checked'
1434
+				: '';
1435
+		}
1436
+		if ($default) {
1437
+			$TKT_status = EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence');
1438
+		} elseif ($ticket->is_default()) {
1439
+			$TKT_status = EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence');
1440
+		} else {
1441
+			$TKT_status = $ticket->ticket_status(true);
1442
+		}
1443
+		if ($default) {
1444
+			$TKT_min = '';
1445
+		} else {
1446
+			$TKT_min = $ticket->min();
1447
+			if ($TKT_min === -1 || $TKT_min === 0) {
1448
+				$TKT_min = '';
1449
+			}
1450
+		}
1451
+		$template_args                 = [
1452
+			'tkt_row'                       => $default ? 'TICKETNUM' : $ticket_row,
1453
+			'TKT_order'                     => $default ? 'TICKETNUM' : $ticket_row,
1454
+			// on initial page load this will always be the correct order.
1455
+			'tkt_status_class'              => $ticket_status_class,
1456
+			'display_edit_tkt_row'          => 'display:none;',
1457
+			'edit_tkt_expanded'             => '',
1458
+			'edit_tickets_name'             => $default ? 'TICKETNAMEATTR' : 'edit_tickets',
1459
+			'TKT_name'                      => $default ? '' : $ticket->get_f('TKT_name'),
1460
+			'TKT_start_date'                => $default
1461
+				? ''
1462
+				: $ticket->get_date('TKT_start_date', $this->_date_time_format),
1463
+			'TKT_end_date'                  => $default
1464
+				? ''
1465
+				: $ticket->get_date('TKT_end_date', $this->_date_time_format),
1466
+			'TKT_status'                    => $TKT_status,
1467
+			'TKT_price'                     => $default
1468
+				? ''
1469
+				: EEH_Template::format_currency(
1470
+					$ticket->get_ticket_total_with_taxes(),
1471
+					false,
1472
+					false
1473
+				),
1474
+			'TKT_price_code'                => EE_Registry::instance()->CFG->currency->code,
1475
+			'TKT_price_amount'              => $default ? 0 : $ticket_subtotal,
1476
+			'TKT_qty'                       => $default
1477
+				? ''
1478
+				: $ticket->get_pretty('TKT_qty', 'symbol'),
1479
+			'TKT_qty_for_input'             => $default
1480
+				? ''
1481
+				: $ticket->get_pretty('TKT_qty', 'input'),
1482
+			'TKT_uses'                      => $default
1483
+				? ''
1484
+				: $ticket->get_pretty('TKT_uses', 'input'),
1485
+			'TKT_min'                       => $TKT_min,
1486
+			'TKT_max'                       => $default
1487
+				? ''
1488
+				: $ticket->get_pretty('TKT_max', 'input'),
1489
+			'TKT_sold'                      => $default ? 0 : $ticket->tickets_sold(),
1490
+			'TKT_reserved'                  => $default ? 0 : $ticket->reserved(),
1491
+			'TKT_registrations'             => $default
1492
+				? 0
1493
+				: $ticket->count_registrations(
1494
+					[
1495
+						[
1496
+							'STS_ID' => [
1497
+								'!=',
1498
+								EEM_Registration::status_id_incomplete,
1499
+							],
1500
+						],
1501
+					]
1502
+				),
1503
+			'TKT_ID'                        => $default ? 0 : $ticket->ID(),
1504
+			'TKT_description'               => $default ? '' : $ticket->get_f('TKT_description'),
1505
+			'TKT_is_default'                => $default ? 0 : $ticket->is_default(),
1506
+			'TKT_required'                  => $default ? 0 : $ticket->required(),
1507
+			'TKT_is_default_selector'       => '',
1508
+			'ticket_price_rows'             => '',
1509
+			'TKT_base_price'                => $default || ! $base_price instanceof EE_Price
1510
+				? ''
1511
+				: $base_price->get_pretty('PRC_amount', 'localized_float'),
1512
+			'TKT_base_price_ID'             => $default || ! $base_price instanceof EE_Price ? 0 : $base_price->ID(),
1513
+			'show_price_modifier'           => count($prices) > 1 || ($default && $count_price_mods > 0)
1514
+				? ''
1515
+				: 'display:none;',
1516
+			'show_price_mod_button'         => count($prices) > 1
1517
+											   || ($default && $count_price_mods > 0)
1518
+											   || (! $default && $ticket->deleted())
1519
+				? 'display:none;'
1520
+				: '',
1521
+			'total_price_rows'              => count($prices) > 1 ? count($prices) : 1,
1522
+			'ticket_datetimes_list'         => $default ? '<li class="hidden"></li>' : '',
1523
+			'starting_ticket_datetime_rows' => $default || $default_datetime ? '' : implode(',', $ticket_datetimes),
1524
+			'ticket_datetime_rows'          => $default ? '' : implode(',', $ticket_datetimes),
1525
+			'existing_ticket_price_ids'     => $default ? '' : implode(',', array_keys($prices)),
1526
+			'ticket_template_id'            => $default ? 0 : $ticket->get('TTM_ID'),
1527
+			'TKT_taxable'                   => $TKT_taxable,
1528
+			'display_subtotal'              => $ticket instanceof EE_Ticket && $ticket->taxable()
1529
+				? ''
1530
+				: 'display:none;',
1531
+			'price_currency_symbol'         => EE_Registry::instance()->CFG->currency->sign,
1532
+			'TKT_subtotal_amount_display'   => EEH_Template::format_currency(
1533
+				$ticket_subtotal,
1534
+				false,
1535
+				false
1536
+			),
1537
+			'TKT_subtotal_amount'           => $ticket_subtotal,
1538
+			'tax_rows'                      => $this->_get_tax_rows($ticket_row, $ticket),
1539
+			'disabled'                      => $ticket instanceof EE_Ticket && $ticket->deleted(),
1540
+			'ticket_archive_class'          => $ticket instanceof EE_Ticket && $ticket->deleted()
1541
+				? ' ticket-archived'
1542
+				: '',
1543
+			'trash_icon'                    => $ticket instanceof EE_Ticket
1544
+											   && $ticket->deleted()
1545
+											   && ! $ticket->is_permanently_deleteable()
1546
+				? 'dashicons dashicons-lock '
1547
+				: 'trash-icon dashicons dashicons-post-trash clickable',
1548
+			'clone_icon'                    => $ticket instanceof EE_Ticket && $ticket->deleted()
1549
+				? ''
1550
+				: 'clone-icon ee-icon ee-icon-clone clickable',
1551
+		];
1552
+		$template_args['trash_hidden'] = count($all_tickets) === 1
1553
+										 && $template_args['trash_icon'] !== 'dashicons dashicons-lock'
1554
+			? 'display:none'
1555
+			: '';
1556
+		// handle rows that should NOT be empty
1557
+		if (empty($template_args['TKT_start_date'])) {
1558
+			// if empty then the start date will be now.
1559
+			$template_args['TKT_start_date']   = date(
1560
+				$this->_date_time_format,
1561
+				current_time('timestamp')
1562
+			);
1563
+			$template_args['tkt_status_class'] = ' tkt-status-' . EE_Ticket::onsale;
1564
+		}
1565
+		if (empty($template_args['TKT_end_date'])) {
1566
+			// get the earliest datetime (if present);
1567
+			$earliest_datetime = $this->_adminpage_obj->get_cpt_model_obj()->ID() > 0
1568
+				? $this->_adminpage_obj->get_cpt_model_obj()->get_first_related(
1569
+					'Datetime',
1570
+					['order_by' => ['DTT_EVT_start' => 'ASC']]
1571
+				)
1572
+				: null;
1573
+			if (! empty($earliest_datetime)) {
1574
+				$template_args['TKT_end_date'] = $earliest_datetime->get_datetime(
1575
+					'DTT_EVT_start',
1576
+					$this->_date_time_format
1577
+				);
1578
+			} else {
1579
+				// default so let's just use what's been set for the default date-time which is 30 days from now.
1580
+				$template_args['TKT_end_date'] = date(
1581
+					$this->_date_time_format,
1582
+					mktime(
1583
+						24,
1584
+						0,
1585
+						0,
1586
+						date('m'),
1587
+						date('d') + 29,
1588
+						date('Y')
1589
+					)
1590
+				);
1591
+			}
1592
+			$template_args['tkt_status_class'] = ' tkt-status-' . EE_Ticket::onsale;
1593
+		}
1594
+		// generate ticket_datetime items
1595
+		if (! $default) {
1596
+			$datetime_row = 1;
1597
+			foreach ($all_datetimes as $datetime) {
1598
+				$template_args['ticket_datetimes_list'] .= $this->_get_ticket_datetime_list_item(
1599
+					$datetime_row,
1600
+					$ticket_row,
1601
+					$datetime,
1602
+					$ticket,
1603
+					$ticket_datetimes,
1604
+					$default
1605
+				);
1606
+				$datetime_row++;
1607
+			}
1608
+		}
1609
+		$price_row = 1;
1610
+		foreach ($prices as $price) {
1611
+			if (! $price instanceof EE_Price) {
1612
+				continue;
1613
+			}
1614
+			if ($price->is_base_price()) {
1615
+				$price_row++;
1616
+				continue;
1617
+			}
1618
+
1619
+			$show_trash  = ! ((count($prices) > 1 && $price_row === 1) || count($prices) === 1);
1620
+			$show_create = ! (count($prices) > 1 && count($prices) !== $price_row);
1621
+
1622
+			$template_args['ticket_price_rows'] .= $this->_get_ticket_price_row(
1623
+				$ticket_row,
1624
+				$price_row,
1625
+				$price,
1626
+				$default,
1627
+				$ticket,
1628
+				$show_trash,
1629
+				$show_create
1630
+			);
1631
+			$price_row++;
1632
+		}
1633
+		// filter $template_args
1634
+		$template_args = apply_filters(
1635
+			'FHEE__espresso_events_Pricing_Hooks___get_ticket_row__template_args',
1636
+			$template_args,
1637
+			$ticket_row,
1638
+			$ticket,
1639
+			$ticket_datetimes,
1640
+			$all_datetimes,
1641
+			$default,
1642
+			$all_tickets,
1643
+			$this->_is_creating_event
1644
+		);
1645
+		return EEH_Template::display_template(
1646
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_row.template.php',
1647
+			$template_args,
1648
+			true
1649
+		);
1650
+	}
1651
+
1652
+
1653
+	/**
1654
+	 * @param int|string     $ticket_row
1655
+	 * @param EE_Ticket|null $ticket
1656
+	 * @return string
1657
+	 * @throws DomainException
1658
+	 * @throws EE_Error
1659
+	 * @throws ReflectionException
1660
+	 */
1661
+	protected function _get_tax_rows($ticket_row, ?EE_Ticket $ticket): string
1662
+	{
1663
+		$tax_rows = '';
1664
+		/** @var EE_Price[] $taxes */
1665
+		$taxes = empty($ticket) ? EE_Taxes::get_taxes_for_admin() : $ticket->get_ticket_taxes_for_admin();
1666
+		foreach ($taxes as $tax) {
1667
+			$tax_added     = $this->_get_tax_added($tax, $ticket);
1668
+			$template_args = [
1669
+				'display_tax'       => ! empty($ticket) && $ticket->get('TKT_taxable')
1670
+					? ''
1671
+					: 'display:none;',
1672
+				'tax_id'            => $tax->ID(),
1673
+				'tkt_row'           => $ticket_row,
1674
+				'tax_label'         => $tax->get('PRC_name'),
1675
+				'tax_added'         => $tax_added,
1676
+				'tax_added_display' => EEH_Template::format_currency($tax_added, false, false),
1677
+				'tax_amount'        => $tax->get('PRC_amount'),
1678
+			];
1679
+			$template_args = apply_filters(
1680
+				'FHEE__espresso_events_Pricing_Hooks___get_tax_rows__template_args',
1681
+				$template_args,
1682
+				$ticket_row,
1683
+				$ticket,
1684
+				$this->_is_creating_event
1685
+			);
1686
+			$tax_rows      .= EEH_Template::display_template(
1687
+				PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_tax_row.template.php',
1688
+				$template_args,
1689
+				true
1690
+			);
1691
+		}
1692
+		return $tax_rows;
1693
+	}
1694
+
1695
+
1696
+	/**
1697
+	 * @param EE_Price       $tax
1698
+	 * @param EE_Ticket|null $ticket
1699
+	 * @return float|int
1700
+	 * @throws EE_Error
1701
+	 * @throws ReflectionException
1702
+	 */
1703
+	protected function _get_tax_added(EE_Price $tax, ?EE_Ticket $ticket)
1704
+	{
1705
+		$subtotal = empty($ticket) ? 0 : $ticket->get_ticket_subtotal();
1706
+		return $subtotal * $tax->get('PRC_amount') / 100;
1707
+	}
1708
+
1709
+
1710
+	/**
1711
+	 * @param int|string     $ticket_row
1712
+	 * @param int|string     $price_row
1713
+	 * @param EE_Price|null  $price
1714
+	 * @param bool           $default
1715
+	 * @param EE_Ticket|null $ticket
1716
+	 * @param bool           $show_trash
1717
+	 * @param bool           $show_create
1718
+	 * @return string
1719
+	 * @throws InvalidArgumentException
1720
+	 * @throws InvalidInterfaceException
1721
+	 * @throws InvalidDataTypeException
1722
+	 * @throws DomainException
1723
+	 * @throws EE_Error
1724
+	 * @throws ReflectionException
1725
+	 */
1726
+	protected function _get_ticket_price_row(
1727
+		$ticket_row,
1728
+		$price_row,
1729
+		?EE_Price $price,
1730
+		bool $default,
1731
+		?EE_Ticket $ticket,
1732
+		bool $show_trash = true,
1733
+		bool $show_create = true
1734
+	): string {
1735
+		$send_disabled = ! empty($ticket) && $ticket->get('TKT_deleted');
1736
+		$template_args = [
1737
+			'tkt_row'               => $default && empty($ticket)
1738
+				? 'TICKETNUM'
1739
+				: $ticket_row,
1740
+			'PRC_order'             => $default && empty($price)
1741
+				? 'PRICENUM'
1742
+				: $price_row,
1743
+			'edit_prices_name'      => $default && empty($price)
1744
+				? 'PRICENAMEATTR'
1745
+				: 'edit_prices',
1746
+			'price_type_selector'   => $default && empty($price)
1747
+				? $this->_get_base_price_template($ticket_row, $price_row, $price, true)
1748
+				: $this->_get_price_type_selector(
1749
+					$ticket_row,
1750
+					$price_row,
1751
+					$price,
1752
+					$default,
1753
+					$send_disabled
1754
+				),
1755
+			'PRC_ID'                => $default && empty($price)
1756
+				? 0
1757
+				: $price->ID(),
1758
+			'PRC_is_default'        => $default && empty($price)
1759
+				? 0
1760
+				: $price->get('PRC_is_default'),
1761
+			'PRC_name'              => $default && empty($price)
1762
+				? ''
1763
+				: $price->get('PRC_name'),
1764
+			'price_currency_symbol' => EE_Registry::instance()->CFG->currency->sign,
1765
+			'show_plus_or_minus'    => $default && empty($price)
1766
+				? ''
1767
+				: 'display:none;',
1768
+			'show_plus'             => ($default && empty($price)) || ($price->is_discount() || $price->is_base_price())
1769
+				? 'display:none;'
1770
+				: '',
1771
+			'show_minus'            => ($default && empty($price)) || ! $price->is_discount()
1772
+				? 'display:none;'
1773
+				: '',
1774
+			'show_currency_symbol'  => ($default && empty($price)) || $price->is_percent()
1775
+				? 'display:none'
1776
+				: '',
1777
+			'PRC_amount'            => $default && empty($price)
1778
+				? 0
1779
+				: $price->get_pretty('PRC_amount', 'localized_float'),
1780
+			'show_percentage'       => ($default && empty($price)) || ! $price->is_percent()
1781
+				? 'display:none;'
1782
+				: '',
1783
+			'show_trash_icon'       => $show_trash
1784
+				? ''
1785
+				: ' style="display:none;"',
1786
+			'show_create_button'    => $show_create
1787
+				? ''
1788
+				: ' style="display:none;"',
1789
+			'PRC_desc'              => $default && empty($price)
1790
+				? ''
1791
+				: $price->get('PRC_desc'),
1792
+			'disabled'              => ! empty($ticket) && $ticket->get('TKT_deleted'),
1793
+		];
1794
+		$template_args = apply_filters(
1795
+			'FHEE__espresso_events_Pricing_Hooks___get_ticket_price_row__template_args',
1796
+			$template_args,
1797
+			$ticket_row,
1798
+			$price_row,
1799
+			$price,
1800
+			$default,
1801
+			$ticket,
1802
+			$show_trash,
1803
+			$show_create,
1804
+			$this->_is_creating_event
1805
+		);
1806
+		return EEH_Template::display_template(
1807
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_price_row.template.php',
1808
+			$template_args,
1809
+			true
1810
+		);
1811
+	}
1812
+
1813
+
1814
+	/**
1815
+	 * @param int|string    $ticket_row
1816
+	 * @param int|string    $price_row
1817
+	 * @param EE_Price|null $price
1818
+	 * @param bool          $default
1819
+	 * @param bool          $disabled
1820
+	 * @return string
1821
+	 * @throws ReflectionException
1822
+	 * @throws InvalidArgumentException
1823
+	 * @throws InvalidInterfaceException
1824
+	 * @throws InvalidDataTypeException
1825
+	 * @throws DomainException
1826
+	 * @throws EE_Error
1827
+	 */
1828
+	protected function _get_price_type_selector(
1829
+		$ticket_row,
1830
+		$price_row,
1831
+		?EE_Price $price,
1832
+		bool $default,
1833
+		bool $disabled = false
1834
+	): string {
1835
+		if ($price->is_base_price()) {
1836
+			return $this->_get_base_price_template(
1837
+				$ticket_row,
1838
+				$price_row,
1839
+				$price,
1840
+				$default
1841
+			);
1842
+		}
1843
+		return $this->_get_price_modifier_template(
1844
+			$ticket_row,
1845
+			$price_row,
1846
+			$price,
1847
+			$default,
1848
+			$disabled
1849
+		);
1850
+	}
1851
+
1852
+
1853
+	/**
1854
+	 * @param int|string    $ticket_row
1855
+	 * @param int|string    $price_row
1856
+	 * @param EE_Price|null $price
1857
+	 * @param bool          $default
1858
+	 * @return string
1859
+	 * @throws DomainException
1860
+	 * @throws EE_Error
1861
+	 * @throws ReflectionException
1862
+	 */
1863
+	protected function _get_base_price_template(
1864
+		$ticket_row,
1865
+		$price_row,
1866
+		?EE_Price $price,
1867
+		bool $default
1868
+	): string {
1869
+		$template_args = [
1870
+			'tkt_row'                   => $default ? 'TICKETNUM' : $ticket_row,
1871
+			'PRC_order'                 => $default && empty($price) ? 'PRICENUM' : $price_row,
1872
+			'PRT_ID'                    => $default && empty($price) ? 1 : $price->get('PRT_ID'),
1873
+			'PRT_name'                  => esc_html__('Price', 'event_espresso'),
1874
+			'price_selected_operator'   => '+',
1875
+			'price_selected_is_percent' => 0,
1876
+		];
1877
+		$template_args = apply_filters(
1878
+			'FHEE__espresso_events_Pricing_Hooks___get_base_price_template__template_args',
1879
+			$template_args,
1880
+			$ticket_row,
1881
+			$price_row,
1882
+			$price,
1883
+			$default,
1884
+			$this->_is_creating_event
1885
+		);
1886
+		return EEH_Template::display_template(
1887
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_type_base.template.php',
1888
+			$template_args,
1889
+			true
1890
+		);
1891
+	}
1892
+
1893
+
1894
+	/**
1895
+	 * @param int|string    $ticket_row
1896
+	 * @param int|string    $price_row
1897
+	 * @param EE_Price|null $price
1898
+	 * @param bool          $default
1899
+	 * @param bool          $disabled
1900
+	 * @return string
1901
+	 * @throws ReflectionException
1902
+	 * @throws InvalidArgumentException
1903
+	 * @throws InvalidInterfaceException
1904
+	 * @throws InvalidDataTypeException
1905
+	 * @throws DomainException
1906
+	 * @throws EE_Error
1907
+	 */
1908
+	protected function _get_price_modifier_template(
1909
+		$ticket_row,
1910
+		$price_row,
1911
+		?EE_Price $price,
1912
+		bool $default,
1913
+		bool $disabled = false
1914
+	): string {
1915
+		$select_name = $default && ! $price instanceof EE_Price
1916
+			? 'edit_prices[TICKETNUM][PRICENUM][PRT_ID]'
1917
+			: 'edit_prices[' . esc_attr($ticket_row) . '][' . esc_attr($price_row) . '][PRT_ID]';
1918
+		/** @var EEM_Price_Type $price_type_model */
1919
+		$price_type_model       = EE_Registry::instance()->load_model('Price_Type');
1920
+		$price_types            = $price_type_model->get_all(
1921
+			[
1922
+				[
1923
+					'OR' => [
1924
+						'PBT_ID'  => '2',
1925
+						'PBT_ID*' => '3',
1926
+					],
1927
+				],
1928
+			]
1929
+		);
1930
+		$all_price_types        = $default && ! $price instanceof EE_Price
1931
+			? [esc_html__('Select Modifier', 'event_espresso')]
1932
+			: [];
1933
+		$selected_price_type_id = $default && ! $price instanceof EE_Price ? 0 : $price->type();
1934
+		$price_option_spans     = '';
1935
+		// setup price types for selector
1936
+		foreach ($price_types as $price_type) {
1937
+			if (! $price_type instanceof EE_Price_Type) {
1938
+				continue;
1939
+			}
1940
+			$all_price_types[ $price_type->ID() ] = $price_type->get('PRT_name');
1941
+			// while we're in the loop let's setup the option spans used by js
1942
+			$span_args          = [
1943
+				'PRT_ID'         => $price_type->ID(),
1944
+				'PRT_operator'   => $price_type->is_discount() ? '-' : '+',
1945
+				'PRT_is_percent' => $price_type->get('PRT_is_percent') ? 1 : 0,
1946
+			];
1947
+			$price_option_spans .= EEH_Template::display_template(
1948
+				PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_option_span.template.php',
1949
+				$span_args,
1950
+				true
1951
+			);
1952
+		}
1953
+
1954
+		$select_name = $disabled
1955
+			? 'archive_price[' . $ticket_row . '][' . $price_row . '][PRT_ID]'
1956
+			: $select_name;
1957
+
1958
+		$select_input = new EE_Select_Input(
1959
+			$all_price_types,
1960
+			[
1961
+				'default'               => $selected_price_type_id,
1962
+				'html_name'             => $select_name,
1963
+				'html_class'            => 'edit-price-PRT_ID',
1964
+				'other_html_attributes' => $disabled ? 'style="width:auto;" disabled' : 'style="width:auto;"',
1965
+			]
1966
+		);
1967
+
1968
+		$price_selected_operator   = $price instanceof EE_Price && $price->is_discount() ? '-' : '+';
1969
+		$price_selected_operator   = $default && ! $price instanceof EE_Price ? '' : $price_selected_operator;
1970
+		$price_selected_is_percent = $price instanceof EE_Price && $price->is_percent() ? 1 : 0;
1971
+		$price_selected_is_percent = $default && ! $price instanceof EE_Price ? '' : $price_selected_is_percent;
1972
+		$template_args             = [
1973
+			'tkt_row'                   => $default ? 'TICKETNUM' : $ticket_row,
1974
+			'PRC_order'                 => $default && ! $price instanceof EE_Price ? 'PRICENUM' : $price_row,
1975
+			'price_modifier_selector'   => $select_input->get_html_for_input(),
1976
+			'main_name'                 => $select_name,
1977
+			'selected_price_type_id'    => $selected_price_type_id,
1978
+			'price_option_spans'        => $price_option_spans,
1979
+			'price_selected_operator'   => $price_selected_operator,
1980
+			'price_selected_is_percent' => $price_selected_is_percent,
1981
+			'disabled'                  => $disabled,
1982
+		];
1983
+		$template_args             = apply_filters(
1984
+			'FHEE__espresso_events_Pricing_Hooks___get_price_modifier_template__template_args',
1985
+			$template_args,
1986
+			$ticket_row,
1987
+			$price_row,
1988
+			$price,
1989
+			$default,
1990
+			$disabled,
1991
+			$this->_is_creating_event
1992
+		);
1993
+		return EEH_Template::display_template(
1994
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_modifier_selector.template.php',
1995
+			$template_args,
1996
+			true
1997
+		);
1998
+	}
1999
+
2000
+
2001
+	/**
2002
+	 * @param int|string       $datetime_row
2003
+	 * @param int|string       $ticket_row
2004
+	 * @param EE_Datetime|null $datetime
2005
+	 * @param EE_Ticket|null   $ticket
2006
+	 * @param array            $ticket_datetimes
2007
+	 * @param bool             $default
2008
+	 * @return string
2009
+	 * @throws DomainException
2010
+	 * @throws EE_Error
2011
+	 * @throws ReflectionException
2012
+	 */
2013
+	protected function _get_ticket_datetime_list_item(
2014
+		$datetime_row,
2015
+		$ticket_row,
2016
+		?EE_Datetime $datetime,
2017
+		?EE_Ticket $ticket,
2018
+		array $ticket_datetimes = [],
2019
+		bool $default = false
2020
+	): string {
2021
+		$ticket_datetimes = $ticket instanceof EE_Ticket && isset($ticket_datetimes[ $ticket->ID() ])
2022
+			? $ticket_datetimes[ $ticket->ID() ]
2023
+			: [];
2024
+		$template_args    = [
2025
+			'dtt_row'                  => $default && ! $datetime instanceof EE_Datetime
2026
+				? 'DTTNUM'
2027
+				: $datetime_row,
2028
+			'tkt_row'                  => $default
2029
+				? 'TICKETNUM'
2030
+				: $ticket_row,
2031
+			'ticket_datetime_selected' => in_array($datetime_row, $ticket_datetimes, true)
2032
+				? ' ticket-selected'
2033
+				: '',
2034
+			'ticket_datetime_checked'  => in_array($datetime_row, $ticket_datetimes, true)
2035
+				? ' checked'
2036
+				: '',
2037
+			'DTT_name'                 => $default && empty($datetime)
2038
+				? 'DTTNAME'
2039
+				: $datetime->get_dtt_display_name(true),
2040
+			'tkt_status_class'         => '',
2041
+		];
2042
+		$template_args    = apply_filters(
2043
+			'FHEE__espresso_events_Pricing_Hooks___get_ticket_datetime_list_item__template_args',
2044
+			$template_args,
2045
+			$datetime_row,
2046
+			$ticket_row,
2047
+			$datetime,
2048
+			$ticket,
2049
+			$ticket_datetimes,
2050
+			$default,
2051
+			$this->_is_creating_event
2052
+		);
2053
+		return EEH_Template::display_template(
2054
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_datetimes_list_item.template.php',
2055
+			$template_args,
2056
+			true
2057
+		);
2058
+	}
2059
+
2060
+
2061
+	/**
2062
+	 * @param array $all_datetimes
2063
+	 * @param array $all_tickets
2064
+	 * @return string
2065
+	 * @throws ReflectionException
2066
+	 * @throws InvalidArgumentException
2067
+	 * @throws InvalidInterfaceException
2068
+	 * @throws InvalidDataTypeException
2069
+	 * @throws DomainException
2070
+	 * @throws EE_Error
2071
+	 */
2072
+	protected function _get_ticket_js_structure(array $all_datetimes = [], array $all_tickets = []): string
2073
+	{
2074
+		$template_args = [
2075
+			'default_datetime_edit_row' => $this->_get_dtt_edit_row(
2076
+				'DTTNUM',
2077
+				null,
2078
+				true,
2079
+				$all_datetimes
2080
+			),
2081
+			'default_ticket_row'        => $this->_get_ticket_row(
2082
+				'TICKETNUM',
2083
+				null,
2084
+				[],
2085
+				[],
2086
+				true
2087
+			),
2088
+			'default_price_row'         => $this->_get_ticket_price_row(
2089
+				'TICKETNUM',
2090
+				'PRICENUM',
2091
+				null,
2092
+				true,
2093
+				null
2094
+			),
2095
+
2096
+			'default_price_rows'                       => '',
2097
+			'default_base_price_amount'                => 0,
2098
+			'default_base_price_name'                  => '',
2099
+			'default_base_price_description'           => '',
2100
+			'default_price_modifier_selector_row'      => $this->_get_price_modifier_template(
2101
+				'TICKETNUM',
2102
+				'PRICENUM',
2103
+				null,
2104
+				true
2105
+			),
2106
+			'default_available_tickets_for_datetime'   => $this->_get_dtt_attached_tickets_row(
2107
+				'DTTNUM',
2108
+				null,
2109
+				[],
2110
+				[],
2111
+				true
2112
+			),
2113
+			'existing_available_datetime_tickets_list' => '',
2114
+			'existing_available_ticket_datetimes_list' => '',
2115
+			'new_available_datetime_ticket_list_item'  => $this->_get_datetime_tickets_list_item(
2116
+				'DTTNUM',
2117
+				'TICKETNUM',
2118
+				null,
2119
+				null,
2120
+				[],
2121
+				true
2122
+			),
2123
+			'new_available_ticket_datetime_list_item'  => $this->_get_ticket_datetime_list_item(
2124
+				'DTTNUM',
2125
+				'TICKETNUM',
2126
+				null,
2127
+				null,
2128
+				[],
2129
+				true
2130
+			),
2131
+		];
2132
+		$ticket_row    = 1;
2133
+		foreach ($all_tickets as $ticket) {
2134
+			$template_args['existing_available_datetime_tickets_list'] .= $this->_get_datetime_tickets_list_item(
2135
+				'DTTNUM',
2136
+				$ticket_row,
2137
+				null,
2138
+				$ticket,
2139
+				[],
2140
+				true
2141
+			);
2142
+			$ticket_row++;
2143
+		}
2144
+		$datetime_row = 1;
2145
+		foreach ($all_datetimes as $datetime) {
2146
+			$template_args['existing_available_ticket_datetimes_list'] .= $this->_get_ticket_datetime_list_item(
2147
+				$datetime_row,
2148
+				'TICKETNUM',
2149
+				$datetime,
2150
+				null,
2151
+				[],
2152
+				true
2153
+			);
2154
+			$datetime_row++;
2155
+		}
2156
+		/** @var EEM_Price $price_model */
2157
+		$price_model    = EE_Registry::instance()->load_model('Price');
2158
+		$default_prices = $price_model->get_all_default_prices();
2159
+		$price_row      = 1;
2160
+		foreach ($default_prices as $price) {
2161
+			if (! $price instanceof EE_Price) {
2162
+				continue;
2163
+			}
2164
+			if ($price->is_base_price()) {
2165
+				$template_args['default_base_price_amount']      = $price->get_pretty(
2166
+					'PRC_amount',
2167
+					'localized_float'
2168
+				);
2169
+				$template_args['default_base_price_name']        = $price->get('PRC_name');
2170
+				$template_args['default_base_price_description'] = $price->get('PRC_desc');
2171
+				$price_row++;
2172
+				continue;
2173
+			}
2174
+
2175
+			$show_trash  = ! ((count($default_prices) > 1 && $price_row === 1) || count($default_prices) === 1);
2176
+			$show_create = ! (count($default_prices) > 1 && count($default_prices) !== $price_row);
2177
+
2178
+			$template_args['default_price_rows'] .= $this->_get_ticket_price_row(
2179
+				'TICKETNUM',
2180
+				$price_row,
2181
+				$price,
2182
+				true,
2183
+				null,
2184
+				$show_trash,
2185
+				$show_create
2186
+			);
2187
+			$price_row++;
2188
+		}
2189
+		$template_args = apply_filters(
2190
+			'FHEE__espresso_events_Pricing_Hooks___get_ticket_js_structure__template_args',
2191
+			$template_args,
2192
+			$all_datetimes,
2193
+			$all_tickets,
2194
+			$this->_is_creating_event
2195
+		);
2196
+		return EEH_Template::display_template(
2197
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_js_structure.template.php',
2198
+			$template_args,
2199
+			true
2200
+		);
2201
+	}
2202 2202
 }
Please login to merge, or discard this patch.