Completed
Branch dev (22bd0c)
by
unknown
24:44 queued 18:15
created
core/EE_System.core.php 2 patches
Indentation   +1204 added lines, -1204 removed lines patch added patch discarded remove patch
@@ -24,1208 +24,1208 @@
 block discarded – undo
24 24
  */
25 25
 final class EE_System implements ResettableInterface
26 26
 {
27
-    /**
28
-     * indicates this is a 'normal' request. Ie, not activation, nor upgrade, nor activation.
29
-     * So examples of this would be a normal GET request on the frontend or backend, or a POST, etc
30
-     */
31
-    const req_type_normal = 0;
32
-
33
-    /**
34
-     * Indicates this is a brand new installation of EE so we should install
35
-     * tables and default data etc
36
-     */
37
-    const req_type_new_activation = 1;
38
-
39
-    /**
40
-     * we've detected that EE has been reactivated (or EE was activated during maintenance mode,
41
-     * and we just exited maintenance mode). We MUST check the database is setup properly
42
-     * and that default data is setup too
43
-     */
44
-    const req_type_reactivation = 2;
45
-
46
-    /**
47
-     * indicates that EE has been upgraded since its previous request.
48
-     * We may have data migration scripts to call and will want to trigger maintenance mode
49
-     */
50
-    const req_type_upgrade = 3;
51
-
52
-    /**
53
-     * TODO  will detect that EE has been DOWNGRADED. We probably don't want to run in this case...
54
-     */
55
-    const req_type_downgrade = 4;
56
-
57
-    /**
58
-     * @deprecated since version 4.6.0.dev.006
59
-     * Now whenever a new_activation is detected the request type is still just
60
-     * new_activation (same for reactivation, upgrade, downgrade etc), but if we're in maintenance mode
61
-     * EE_System::initialize_db_if_no_migrations_required and EE_Addon::initialize_db_if_no_migrations_required
62
-     * will instead enqueue that EE plugin's db initialization for when we're taken out of maintenance mode.
63
-     * (Specifically, when the migration manager indicates migrations are finished
64
-     * EE_Data_Migration_Manager::initialize_db_for_enqueued_ee_plugins() will be called)
65
-     */
66
-    const req_type_activation_but_not_installed = 5;
67
-
68
-    /**
69
-     * option prefix for recording the activation history (like core's "espresso_db_update") of addons
70
-     */
71
-    const addon_activation_history_option_prefix = 'ee_addon_activation_history_';
72
-
73
-    /**
74
-     * @var AddonManager $addon_manager
75
-     */
76
-    private $addon_manager;
77
-
78
-    /**
79
-     * @var EE_System $_instance
80
-     */
81
-    private static $_instance;
82
-
83
-    /**
84
-     * @var EE_Registry $registry
85
-     */
86
-    private $registry;
87
-
88
-    /**
89
-     * @var LoaderInterface $loader
90
-     */
91
-    private $loader;
92
-
93
-    /**
94
-     * @var EE_Capabilities $capabilities
95
-     */
96
-    private $capabilities;
97
-
98
-    /**
99
-     * @var EE_Maintenance_Mode $maintenance_mode
100
-     */
101
-    private $maintenance_mode;
102
-
103
-    /**
104
-     * @var RequestInterface $request
105
-     */
106
-    private $request;
107
-
108
-    /**
109
-     * Stores which type of request this is, options being one of the constants on EE_System starting with req_type_*.
110
-     * It can be a brand-new activation, a reactivation, an upgrade, a downgrade, or a normal request.
111
-     *
112
-     * @var int $_req_type
113
-     */
114
-    private $_req_type;
115
-
116
-    /**
117
-     * Whether or not there was a non-micro version change in EE core version during this request
118
-     *
119
-     * @var boolean $_major_version_change
120
-     */
121
-    private $_major_version_change = false;
122
-
123
-    /**
124
-     * @var Router $router
125
-     */
126
-    private $router;
127
-
128
-    /**
129
-     * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes
130
-     */
131
-    private $register_custom_post_types;
132
-
133
-    /**
134
-     * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies
135
-     */
136
-    private $register_custom_taxonomies;
137
-
138
-    /**
139
-     * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms
140
-     */
141
-    private $register_custom_taxonomy_terms;
142
-
143
-    /**
144
-     * @singleton method used to instantiate class object
145
-     * @param LoaderInterface|null     $loader
146
-     * @param EE_Maintenance_Mode|null $maintenance_mode
147
-     * @param EE_Registry|null         $registry
148
-     * @param RequestInterface|null    $request
149
-     * @param Router|null              $router
150
-     * @return EE_System
151
-     */
152
-    public static function instance(
153
-        LoaderInterface $loader = null,
154
-        EE_Maintenance_Mode $maintenance_mode = null,
155
-        EE_Registry $registry = null,
156
-        RequestInterface $request = null,
157
-        Router $router = null
158
-    ): EE_System {
159
-        // check if class object is instantiated
160
-        if (! self::$_instance instanceof EE_System) {
161
-            self::$_instance = new self($loader, $maintenance_mode, $registry, $request, $router);
162
-        }
163
-        return self::$_instance;
164
-    }
165
-
166
-
167
-    /**
168
-     * resets the instance and returns it
169
-     *
170
-     * @return EE_System
171
-     */
172
-    public static function reset(): EE_System
173
-    {
174
-        self::$_instance->_req_type = null;
175
-        // make sure none of the old hooks are left hanging around
176
-        remove_all_actions('AHEE__EE_System__perform_activations_upgrades_and_migrations');
177
-        // we need to reset the migration manager in order for it to detect DMSs properly
178
-        EE_Data_Migration_Manager::reset();
179
-        self::instance()->detect_activations_or_upgrades();
180
-        self::instance()->perform_activations_upgrades_and_migrations();
181
-        return self::instance();
182
-    }
183
-
184
-
185
-    /**
186
-     * sets hooks for running rest of system
187
-     * provides "AHEE__EE_System__construct__complete" hook for EE Addons to use as their starting point
188
-     * starting EE Addons from any other point may lead to problems
189
-     *
190
-     * @param LoaderInterface     $loader
191
-     * @param EE_Maintenance_Mode $maintenance_mode
192
-     * @param EE_Registry         $registry
193
-     * @param RequestInterface    $request
194
-     * @param Router              $router
195
-     */
196
-    private function __construct(
197
-        LoaderInterface $loader,
198
-        EE_Maintenance_Mode $maintenance_mode,
199
-        EE_Registry $registry,
200
-        RequestInterface $request,
201
-        Router $router
202
-    ) {
203
-        $this->registry         = $registry;
204
-        $this->loader           = $loader;
205
-        $this->request          = $request;
206
-        $this->router           = $router;
207
-        $this->maintenance_mode = $maintenance_mode;
208
-        do_action('AHEE__EE_System__construct__begin', $this);
209
-        add_action(
210
-            'AHEE__EE_Bootstrap__load_espresso_addons',
211
-            [$this, 'loadCapabilities'],
212
-            5
213
-        );
214
-        add_action(
215
-            'AHEE__EE_Bootstrap__load_espresso_addons',
216
-            [$this, 'loadCommandBus'],
217
-            7
218
-        );
219
-        add_action(
220
-            'AHEE__EE_Bootstrap__load_espresso_addons',
221
-            [$this, 'loadPluginApi'],
222
-            9
223
-        );
224
-        // allow addons to load first so that they can register autoloaders, set hooks for running DMS's, etc
225
-        add_action(
226
-            'AHEE__EE_Bootstrap__load_espresso_addons',
227
-            [$this, 'load_espresso_addons']
228
-        );
229
-        // when an ee addon is activated, we want to call the core hook(s) again
230
-        // because the newly-activated addon didn't get a chance to run at all
231
-        add_action('activate_plugin', [$this, 'load_espresso_addons'], 1);
232
-        // detect whether install or upgrade
233
-        add_action(
234
-            'AHEE__EE_Bootstrap__detect_activations_or_upgrades',
235
-            [$this, 'detect_activations_or_upgrades'],
236
-            3
237
-        );
238
-        // load EE_Config, EE_Textdomain, etc
239
-        add_action(
240
-            'AHEE__EE_Bootstrap__load_core_configuration',
241
-            [$this, 'load_core_configuration'],
242
-            5
243
-        );
244
-        // load specifications for matching routes to current request
245
-        add_action(
246
-            'AHEE__EE_Bootstrap__load_core_configuration',
247
-            [$this, 'loadRouteMatchSpecifications']
248
-        );
249
-        // load specifications for custom post types
250
-        add_action(
251
-            'AHEE__EE_Bootstrap__load_core_configuration',
252
-            array($this, 'loadCustomPostTypes')
253
-        );
254
-        // load specifications for custom post types
255
-        add_action(
256
-            'AHEE__EE_Bootstrap__load_core_configuration',
257
-            array($this, 'loadCustomPostTypes')
258
-        );
259
-        // load EE_Config, EE_Textdomain, etc
260
-        add_action(
261
-            'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets',
262
-            [$this, 'register_shortcodes_modules_and_widgets'],
263
-            7
264
-        );
265
-        // you wanna get going? I wanna get going... let's get going!
266
-        add_action(
267
-            'AHEE__EE_Bootstrap__brew_espresso',
268
-            [$this, 'brew_espresso'],
269
-            9
270
-        );
271
-        // other housekeeping
272
-        // exclude EE critical pages from wp_list_pages
273
-        add_filter(
274
-            'wp_list_pages_excludes',
275
-            [$this, 'remove_pages_from_wp_list_pages'],
276
-            10
277
-        );
278
-        // ALL EE Addons should use the following hook point to attach their initial setup too
279
-        // it's extremely important for EE Addons to register any class autoloaders so that they can be available when the EE_Config loads
280
-        do_action('AHEE__EE_System__construct__complete', $this);
281
-    }
282
-
283
-
284
-    /**
285
-     * load and setup EE_Capabilities
286
-     *
287
-     * @return void
288
-     */
289
-    public function loadCapabilities()
290
-    {
291
-        $this->capabilities = $this->loader->getShared('EE_Capabilities');
292
-        add_action(
293
-            'AHEE__EE_Capabilities__init_caps__before_initialization',
294
-            function () {
295
-                LoaderFactory::getLoader()->getShared('EE_Payment_Method_Manager');
296
-            }
297
-        );
298
-    }
299
-
300
-
301
-    /**
302
-     * create and cache the CommandBus, and also add middleware
303
-     * The CapChecker middleware requires the use of EE_Capabilities
304
-     * which is why we need to load the CommandBus after Caps are set up
305
-     *
306
-     * @return void
307
-     */
308
-    public function loadCommandBus()
309
-    {
310
-        $this->loader->getShared(
311
-            'CommandBusInterface',
312
-            [
313
-                null,
314
-                apply_filters(
315
-                    'FHEE__EE_Load_Espresso_Core__handle_request__CommandBus_middleware',
316
-                    [
317
-                        $this->loader->getShared('EventEspresso\core\services\commands\middleware\CapChecker'),
318
-                        $this->loader->getShared('EventEspresso\core\services\commands\middleware\AddActionHook'),
319
-                    ]
320
-                ),
321
-            ]
322
-        );
323
-    }
324
-
325
-
326
-    /**
327
-     * @return void
328
-     * @throws Exception
329
-     */
330
-    public function loadPluginApi()
331
-    {
332
-        $this->addon_manager = $this->loader->getShared(AddonManager::class);
333
-        $this->addon_manager->initialize();
334
-        $this->loader->getShared('EE_Request_Handler');
335
-    }
336
-
337
-
338
-    /**
339
-     * load_espresso_addons
340
-     * allow addons to load first so that they can set hooks for running DMS's, etc
341
-     * this is hooked into both:
342
-     *    'AHEE__EE_Bootstrap__load_core_configuration'
343
-     *        which runs during the WP 'plugins_loaded' action at priority 5
344
-     *    and the WP 'activate_plugin' hook point
345
-     *
346
-     * @return void
347
-     * @throws Exception
348
-     */
349
-    public function load_espresso_addons()
350
-    {
351
-        // looking for hooks? they've been moved into the AddonManager to maintain compatibility
352
-        $this->addon_manager->loadAddons();
353
-    }
354
-
355
-
356
-    /**
357
-     * detect_activations_or_upgrades
358
-     * Checks for activation or upgrade of core first;
359
-     * then also checks if any registered addons have been activated or upgraded
360
-     * This is hooked into 'AHEE__EE_Bootstrap__detect_activations_or_upgrades'
361
-     * which runs during the WP 'plugins_loaded' action at priority 3
362
-     *
363
-     * @access public
364
-     * @return void
365
-     */
366
-    public function detect_activations_or_upgrades()
367
-    {
368
-        // first off: let's make sure to handle core
369
-        $this->detect_if_activation_or_upgrade();
370
-        foreach ($this->registry->addons as $addon) {
371
-            if ($addon instanceof EE_Addon) {
372
-                // detect teh request type for that addon
373
-                $addon->detect_req_type();
374
-            }
375
-        }
376
-    }
377
-
378
-
379
-    /**
380
-     * detect_if_activation_or_upgrade
381
-     * Takes care of detecting whether this is a brand new install or code upgrade,
382
-     * and either setting up the DB or setting up maintenance mode etc.
383
-     *
384
-     * @access public
385
-     * @return void
386
-     */
387
-    public function detect_if_activation_or_upgrade()
388
-    {
389
-        do_action('AHEE__EE_System___detect_if_activation_or_upgrade__begin');
390
-        // check if db has been updated, or if its a brand-new installation
391
-        $espresso_db_update = $this->fix_espresso_db_upgrade_option();
392
-        $request_type       = $this->detect_req_type($espresso_db_update);
393
-        // EEH_Debug_Tools::printr( $request_type, '$request_type', __FILE__, __LINE__ );
394
-        switch ($request_type) {
395
-            case EE_System::req_type_new_activation:
396
-                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__new_activation');
397
-                $this->_handle_core_version_change($espresso_db_update);
398
-                break;
399
-            case EE_System::req_type_reactivation:
400
-                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__reactivation');
401
-                $this->_handle_core_version_change($espresso_db_update);
402
-                break;
403
-            case EE_System::req_type_upgrade:
404
-                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__upgrade');
405
-                // migrations may be required now that we've upgraded
406
-                $this->maintenance_mode->set_maintenance_mode_if_db_old();
407
-                $this->_handle_core_version_change($espresso_db_update);
408
-                break;
409
-            case EE_System::req_type_downgrade:
410
-                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__downgrade');
411
-                // its possible migrations are no longer required
412
-                $this->maintenance_mode->set_maintenance_mode_if_db_old();
413
-                $this->_handle_core_version_change($espresso_db_update);
414
-                break;
415
-            case EE_System::req_type_normal:
416
-            default:
417
-                break;
418
-        }
419
-        do_action('AHEE__EE_System__detect_if_activation_or_upgrade__complete');
420
-    }
421
-
422
-
423
-    /**
424
-     * Updates the list of installed versions and sets hooks for
425
-     * initializing the database later during the request
426
-     *
427
-     * @param array $espresso_db_update
428
-     */
429
-    private function _handle_core_version_change(array $espresso_db_update)
430
-    {
431
-        $this->update_list_of_installed_versions($espresso_db_update);
432
-        // get ready to verify the DB is ok (provided we aren't in maintenance mode, of course)
433
-        add_action(
434
-            'AHEE__EE_System__perform_activations_upgrades_and_migrations',
435
-            [$this, 'initialize_db_if_no_migrations_required']
436
-        );
437
-    }
438
-
439
-
440
-    /**
441
-     * standardizes the wp option 'espresso_db_upgrade' which actually stores
442
-     * information about what versions of EE have been installed and activated,
443
-     * NOT necessarily the state of the database
444
-     *
445
-     * @param mixed $espresso_db_update           the value of the WordPress option.
446
-     *                                            If not supplied, fetches it from the options table
447
-     * @return array the correct value of 'espresso_db_upgrade', after saving it, if it needed correction
448
-     */
449
-    private function fix_espresso_db_upgrade_option($espresso_db_update = null): array
450
-    {
451
-        do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__begin', $espresso_db_update);
452
-        if (! $espresso_db_update) {
453
-            $espresso_db_update = get_option('espresso_db_update');
454
-        }
455
-        // check that option is an array
456
-        if (! is_array($espresso_db_update)) {
457
-            // if option is FALSE, then it never existed
458
-            if ($espresso_db_update === false) {
459
-                // make $espresso_db_update an array and save option with autoload OFF
460
-                $espresso_db_update = [];
461
-                add_option('espresso_db_update', $espresso_db_update, '', 'no');
462
-            } else {
463
-                // option is NOT FALSE but also is NOT an array, so make it an array and save it
464
-                $espresso_db_update = [$espresso_db_update => []];
465
-                update_option('espresso_db_update', $espresso_db_update);
466
-            }
467
-        } else {
468
-            $corrected_db_update = [];
469
-            // if IS an array, but is it an array where KEYS are version numbers, and values are arrays?
470
-            foreach ($espresso_db_update as $should_be_version_string => $should_be_array) {
471
-                if (is_int($should_be_version_string) && ! is_array($should_be_array)) {
472
-                    // the key is an int, and the value IS NOT an array
473
-                    // so it must be numerically-indexed, where values are versions installed...
474
-                    // fix it!
475
-                    $version_string                         = $should_be_array;
476
-                    $corrected_db_update[ $version_string ] = ['unknown-date'];
477
-                } else {
478
-                    // ok it checks out
479
-                    $corrected_db_update[ $should_be_version_string ] = $should_be_array;
480
-                }
481
-            }
482
-            $espresso_db_update = $corrected_db_update;
483
-            update_option('espresso_db_update', $espresso_db_update);
484
-        }
485
-        do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__complete', $espresso_db_update);
486
-        return ! empty($espresso_db_update) ? $espresso_db_update : [];
487
-    }
488
-
489
-
490
-    /**
491
-     * Does the traditional work of setting up the plugin's database and adding default data.
492
-     * If migration script/process did not exist, this is what would happen on every activation/reactivation/upgrade.
493
-     * NOTE: if we're in maintenance mode (which would be the case if we detect there are data
494
-     * migration scripts that need to be run and a version change happens), enqueues core for database initialization,
495
-     * so that it will be done when migrations are finished
496
-     *
497
-     * @param boolean $initialize_addons_too if true, we double-check addons' database tables etc too;
498
-     * @param boolean $verify_schema         if true will re-check the database tables have the correct schema.
499
-     *                                       This is a resource-intensive job
500
-     *                                       so we prefer to only do it when necessary
501
-     * @return void
502
-     * @throws EE_Error
503
-     * @throws ReflectionException
504
-     */
505
-    public function initialize_db_if_no_migrations_required($initialize_addons_too = false, $verify_schema = true)
506
-    {
507
-        $request_type = $this->detect_req_type();
508
-        // only initialize system if we're not in maintenance mode.
509
-        if ($this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance) {
510
-            /** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
511
-            $rewrite_rules = $this->loader->getShared(
512
-                'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
513
-            );
514
-            $rewrite_rules->flush();
515
-            if ($verify_schema) {
516
-                EEH_Activation::initialize_db_and_folders();
517
-            }
518
-            EEH_Activation::initialize_db_content();
519
-            EEH_Activation::system_initialization();
520
-            if ($initialize_addons_too) {
521
-                $this->initialize_addons();
522
-            }
523
-        } else {
524
-            EE_Data_Migration_Manager::instance()->enqueue_db_initialization_for('Core');
525
-        }
526
-        if (
527
-            $request_type === EE_System::req_type_new_activation
528
-            || $request_type === EE_System::req_type_reactivation
529
-            || (
530
-                $request_type === EE_System::req_type_upgrade
531
-                && $this->is_major_version_change()
532
-            )
533
-        ) {
534
-            add_action('AHEE__EE_System__initialize_last', [$this, 'redirect_to_about_ee'], 9);
535
-        }
536
-    }
537
-
538
-
539
-    /**
540
-     * Initializes the db for all registered addons
541
-     *
542
-     * @throws EE_Error
543
-     * @throws ReflectionException
544
-     */
545
-    public function initialize_addons()
546
-    {
547
-        // foreach registered addon, make sure its db is up-to-date too
548
-        foreach ($this->registry->addons as $addon) {
549
-            if ($addon instanceof EE_Addon) {
550
-                $addon->initialize_db_if_no_migrations_required();
551
-            }
552
-        }
553
-    }
554
-
555
-
556
-    /**
557
-     * Adds the current code version to the saved wp option which stores a list of all ee versions ever installed.
558
-     *
559
-     * @param array  $version_history
560
-     * @param string $current_version_to_add version to be added to the version history
561
-     * @return    boolean success as to whether or not this option was changed
562
-     */
563
-    public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null): bool
564
-    {
565
-        if (! $version_history) {
566
-            $version_history = $this->fix_espresso_db_upgrade_option($version_history);
567
-        }
568
-        if ($current_version_to_add === null) {
569
-            $current_version_to_add = espresso_version();
570
-        }
571
-        $version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
572
-        // re-save
573
-        return update_option('espresso_db_update', $version_history);
574
-    }
575
-
576
-
577
-    /**
578
-     * Detects if the current version indicated in the has existed in the list of
579
-     * previously-installed versions of EE (espresso_db_update). Does NOT modify it (ie, no side-effect)
580
-     *
581
-     * @param array $espresso_db_update array from the wp option stored under the name 'espresso_db_update'.
582
-     *                                  If not supplied, fetches it from the options table.
583
-     *                                  Also, caches its result so later parts of the code can also know whether
584
-     *                                  there's been an update or not. This way we can add the current version to
585
-     *                                  espresso_db_update, but still know if this is a new install or not
586
-     * @return int one of the constants on EE_System::req_type_
587
-     */
588
-    public function detect_req_type($espresso_db_update = null): int
589
-    {
590
-        if ($this->_req_type === null) {
591
-            $espresso_db_update          = ! empty($espresso_db_update)
592
-                ? $espresso_db_update
593
-                : $this->fix_espresso_db_upgrade_option();
594
-            $this->_req_type             = EE_System::detect_req_type_given_activation_history(
595
-                $espresso_db_update,
596
-                'ee_espresso_activation',
597
-                espresso_version()
598
-            );
599
-            $this->_major_version_change = $this->_detect_major_version_change($espresso_db_update);
600
-            $this->request->setIsActivation($this->_req_type !== EE_System::req_type_normal);
601
-        }
602
-        return $this->_req_type;
603
-    }
604
-
605
-
606
-    /**
607
-     * Returns whether or not there was a non-micro version change (ie, change in either
608
-     * the first or second number in the version. Eg 4.9.0.rc.001 to 4.10.0.rc.000,
609
-     * but not 4.9.0.rc.0001 to 4.9.1.rc.0001
610
-     *
611
-     * @param $activation_history
612
-     * @return bool
613
-     */
614
-    private function _detect_major_version_change($activation_history): bool
615
-    {
616
-        $previous_version       = EE_System::getMostRecentlyActiveVersion($activation_history);
617
-        $previous_version_parts = explode('.', $previous_version);
618
-        $current_version_parts  = explode('.', espresso_version());
619
-        return isset(
620
-            $previous_version_parts[0],
621
-            $previous_version_parts[1],
622
-            $current_version_parts[0],
623
-            $current_version_parts[1]
624
-        ) && (
625
-            $previous_version_parts[0] !== $current_version_parts[0]
626
-            || $previous_version_parts[1] !== $current_version_parts[1]
627
-        );
628
-    }
629
-
630
-
631
-    /**
632
-     * Returns true if either the major or minor version of EE changed during this request.
633
-     * Eg 4.9.0.rc.001 to 4.10.0.rc.000, but not 4.9.0.rc.0001 to 4.9.1.rc.0001
634
-     *
635
-     * @return bool
636
-     */
637
-    public function is_major_version_change(): bool
638
-    {
639
-        return $this->_major_version_change;
640
-    }
641
-
642
-
643
-    /**
644
-     * Determines the request type for any ee addon, given three piece of info: the current array of activation
645
-     * histories (for core that' 'espresso_db_update' wp option); the name of the WordPress option which is temporarily
646
-     * set upon activation of the plugin (for core it's 'ee_espresso_activation'); and the version that this plugin was
647
-     * just activated to (for core that will always be espresso_version())
648
-     *
649
-     * @param array|null $activation_history             the option's value which stores the activation history for
650
-     *                                                 this
651
-     *                                                 ee plugin. for core that's 'espresso_db_update'
652
-     * @param string $activation_indicator_option_name the name of the WordPress option that is temporarily set to
653
-     *                                                 indicate that this plugin was just activated
654
-     * @param string $current_version                  the version that was just upgraded to (for core that will be
655
-     *                                                 espresso_version())
656
-     * @return int one of the constants on EE_System::req_type_
657
-     */
658
-    public static function detect_req_type_given_activation_history(
659
-        array $activation_history,
660
-        string $activation_indicator_option_name,
661
-        string $current_version
662
-    ): int {
663
-        $version_change = self::compareVersionWithPrevious($activation_history, $current_version);
664
-        $is_activation  = get_option($activation_indicator_option_name, false);
665
-        $req_type       = self::getRequestType($activation_history, $version_change, $is_activation);
666
-        if ($is_activation) {
667
-            // cleanup in aisle 6
668
-            delete_option($activation_indicator_option_name);
669
-        }
670
-        return $req_type;
671
-    }
672
-
673
-
674
-    /**
675
-     * @param array  $activation_history
676
-     * @param int    $version_change
677
-     * @param bool   $is_activation
678
-     * @return int
679
-     * @since $VID:$
680
-     */
681
-    private static function getRequestType(array $activation_history, int $version_change, bool $is_activation): int
682
-    {
683
-        // if no previous activation history exists, then this is a brand new install
684
-        if (empty($activation_history)) {
685
-            return EE_System::req_type_new_activation;
686
-        }
687
-        // current version is higher than previous version, so it's an upgrade
688
-        if ($version_change === 1) {
689
-            return EE_System::req_type_upgrade;
690
-        }
691
-        // current version is lower than previous version, so it's a downgrade
692
-        if ($version_change === -1) {
693
-            return EE_System::req_type_downgrade;
694
-        }
695
-        // version hasn't changed since last version so check if the activation indicator is set
696
-        // to determine if it's a reactivation, or just a normal request
697
-        return $is_activation
698
-            ? EE_System::req_type_reactivation
699
-            : EE_System::req_type_normal;
700
-    }
701
-
702
-
703
-    /**
704
-     * Detects if the $version_to_upgrade_to is higher than the most recent version in
705
-     * the $activation_history_for_addon
706
-     *
707
-     * @param array  $activation_history    array where keys are versions,
708
-     *                                      values are arrays of times activated (sometimes 'unknown-date')
709
-     * @param string $current_version
710
-     * @return int results of version_compare( $version_to_upgrade_to, $most_recently_active_version ).
711
-     *                                      -1 if $version_to_upgrade_to is LOWER (downgrade);
712
-     *                                      0 if $version_to_upgrade_to MATCHES (reactivation or normal request);
713
-     *                                      1 if $version_to_upgrade_to is HIGHER (upgrade) ;
714
-     */
715
-    private static function compareVersionWithPrevious(array $activation_history, string $current_version): int
716
-    {
717
-        // find the most recently-activated version
718
-        $most_recently_active_version = EE_System::getMostRecentlyActiveVersion($activation_history);
719
-        return version_compare($current_version, $most_recently_active_version);
720
-    }
721
-
722
-
723
-    /**
724
-     * Gets the most recently active version listed in the activation history,
725
-     * and if none are found (ie, it's a brand new install) returns '0.0.0.dev.000'.
726
-     *
727
-     * @param array $activation_history  (keys are versions, values are arrays of times activated,
728
-     *                                   sometimes containing 'unknown-date'
729
-     * @return string
730
-     */
731
-    private static function getMostRecentlyActiveVersion(array $activation_history): string
732
-    {
733
-        $most_recent_activation_date  = '1970-01-01 00:00:00';
734
-        $most_recently_active_version = '0.0.0.dev.000';
735
-        if (is_array($activation_history)) {
736
-            foreach ($activation_history as $version => $activation_dates) {
737
-                // check there is a record of when this version was activated.
738
-                // Otherwise, mark it as unknown
739
-                if (! $activation_dates) {
740
-                    $activation_dates = ['unknown-date'];
741
-                }
742
-                $activation_dates = is_string($activation_dates) ? [$activation_dates] : $activation_dates;
743
-                foreach ($activation_dates as $activation_date) {
744
-                    if ($activation_date !== 'unknown-date' && $activation_date > $most_recent_activation_date) {
745
-                        $most_recently_active_version = $version;
746
-                        $most_recent_activation_date  = $activation_date;
747
-                    }
748
-                }
749
-            }
750
-        }
751
-        return $most_recently_active_version;
752
-    }
753
-
754
-
755
-    /**
756
-     * This redirects to the about EE page after activation
757
-     *
758
-     * @return void
759
-     */
760
-    public function redirect_to_about_ee()
761
-    {
762
-        $notices = EE_Error::get_notices(false);
763
-        // if current user is an admin and it's not an ajax or rest request
764
-        if (
765
-            ! isset($notices['errors'])
766
-            && $this->request->isAdmin()
767
-            && apply_filters(
768
-                'FHEE__EE_System__redirect_to_about_ee__do_redirect',
769
-                $this->capabilities->current_user_can('manage_options', 'espresso_about_default')
770
-            )
771
-        ) {
772
-            $query_params = ['page' => 'espresso_about'];
773
-            if (EE_System::instance()->detect_req_type() === EE_System::req_type_new_activation) {
774
-                $query_params['new_activation'] = true;
775
-            }
776
-            if (EE_System::instance()->detect_req_type() === EE_System::req_type_reactivation) {
777
-                $query_params['reactivation'] = true;
778
-            }
779
-            $url = add_query_arg($query_params, admin_url('admin.php'));
780
-            EEH_URL::safeRedirectAndExit($url);
781
-        }
782
-    }
783
-
784
-
785
-    /**
786
-     * load_core_configuration
787
-     * this is hooked into 'AHEE__EE_Bootstrap__load_core_configuration'
788
-     * which runs during the WP 'plugins_loaded' action at priority 5
789
-     *
790
-     * @return void
791
-     * @throws ReflectionException
792
-     * @throws Exception
793
-     */
794
-    public function load_core_configuration()
795
-    {
796
-        do_action('AHEE__EE_System__load_core_configuration__begin', $this);
797
-        $this->loader->getShared('EE_Load_Textdomain');
798
-        // load textdomain
799
-        EE_Load_Textdomain::load_textdomain();
800
-        // load caf stuff a chance to play during the activation process too.
801
-        $this->_maybe_brew_regular();
802
-        // load and setup EE_Config and EE_Network_Config
803
-        $config = $this->loader->getShared('EE_Config');
804
-        $this->loader->getShared('EE_Network_Config');
805
-        // setup autoloaders
806
-        // enable logging?
807
-        if ($config->admin->use_remote_logging) {
808
-            $this->loader->getShared('EE_Log');
809
-        }
810
-        // check for activation errors
811
-        $activation_errors = get_option('ee_plugin_activation_errors', false);
812
-        if ($activation_errors) {
813
-            EE_Error::add_error($activation_errors, __FILE__, __FUNCTION__, __LINE__);
814
-            update_option('ee_plugin_activation_errors', false);
815
-        }
816
-        // get model names
817
-        $this->_parse_model_names();
818
-        // configure custom post type definitions
819
-        $this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions');
820
-        $this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions');
821
-        do_action('AHEE__EE_System__load_core_configuration__complete', $this);
822
-    }
823
-
824
-
825
-    /**
826
-     * cycles through all of the models/*.model.php files, and assembles an array of model names
827
-     *
828
-     * @return void
829
-     * @throws ReflectionException
830
-     */
831
-    private function _parse_model_names()
832
-    {
833
-        // get all the files in the EE_MODELS folder that end in .model.php
834
-        $models                 = glob(EE_MODELS . '*.model.php');
835
-        $model_names            = [];
836
-        $non_abstract_db_models = [];
837
-        foreach ($models as $model) {
838
-            // get model classname
839
-            $classname       = EEH_File::get_classname_from_filepath_with_standard_filename($model);
840
-            $short_name      = str_replace('EEM_', '', $classname);
841
-            $reflectionClass = new ReflectionClass($classname);
842
-            if ($reflectionClass->isSubclassOf('EEM_Base') && ! $reflectionClass->isAbstract()) {
843
-                $non_abstract_db_models[ $short_name ] = $classname;
844
-            }
845
-            $model_names[ $short_name ] = $classname;
846
-        }
847
-        $this->registry->models                 = apply_filters('FHEE__EE_System__parse_model_names', $model_names);
848
-        $this->registry->non_abstract_db_models = apply_filters(
849
-            'FHEE__EE_System__parse_implemented_model_names',
850
-            $non_abstract_db_models
851
-        );
852
-    }
853
-
854
-
855
-    /**
856
-     * The purpose of this method is to simply check for a file named "caffeinated/brewing_regular.php" for any hooks
857
-     * that need to be setup before our EE_System launches.
858
-     *
859
-     * @return void
860
-     * @throws DomainException
861
-     * @throws InvalidArgumentException
862
-     * @throws InvalidDataTypeException
863
-     * @throws InvalidInterfaceException
864
-     * @throws InvalidClassException
865
-     * @throws InvalidFilePathException
866
-     */
867
-    private function _maybe_brew_regular()
868
-    {
869
-        /** @var Domain $domain */
870
-        $domain = DomainFactory::getEventEspressoCoreDomain();
871
-        if ($domain->isCaffeinated()) {
872
-            require_once EE_CAFF_PATH . 'brewing_regular.php';
873
-        }
874
-    }
875
-
876
-
877
-    /**
878
-     * @throws Exception
879
-     * @since 4.9.71.p
880
-     */
881
-    public function loadRouteMatchSpecifications()
882
-    {
883
-        try {
884
-            $this->loader->getShared('EventEspresso\core\services\routing\RouteMatchSpecificationManager');
885
-            $this->loader->getShared('EventEspresso\core\services\routing\RouteCollection');
886
-            $this->router->loadPrimaryRoutes();
887
-        } catch (Exception $exception) {
888
-            new ExceptionStackTraceDisplay($exception);
889
-        }
890
-        do_action('AHEE__EE_System__loadRouteMatchSpecifications');
891
-    }
892
-
893
-
894
-    /**
895
-     * loading CPT related classes earlier so that their definitions are available
896
-     * but not performing any actual registration with WP core until load_CPTs_and_session() is called
897
-     *
898
-     * @since   4.10.21.p
899
-     */
900
-    public function loadCustomPostTypes()
901
-    {
902
-        $this->register_custom_taxonomies = $this->loader->getShared(
903
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'
904
-        );
905
-        $this->register_custom_post_types = $this->loader->getShared(
906
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'
907
-        );
908
-        $this->register_custom_taxonomy_terms = $this->loader->getShared(
909
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms'
910
-        );
911
-        // integrate WP_Query with the EE models
912
-        $this->loader->getShared('EE_CPT_Strategy');
913
-        // load legacy EE_Request_Handler in case add-ons still need it
914
-        $this->loader->getShared('EE_Request_Handler');
915
-    }
916
-
917
-
918
-    /**
919
-     * register_shortcodes_modules_and_widgets
920
-     * generate lists of shortcodes and modules, then verify paths and classes
921
-     * This is hooked into 'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets'
922
-     * which runs during the WP 'plugins_loaded' action at priority 7
923
-     *
924
-     * @access public
925
-     * @return void
926
-     * @throws Exception
927
-     */
928
-    public function register_shortcodes_modules_and_widgets()
929
-    {
930
-        $this->router->registerShortcodesModulesAndWidgets();
931
-        do_action('AHEE__EE_System__register_shortcodes_modules_and_widgets');
932
-        // check for addons using old hook point
933
-        if (has_action('AHEE__EE_System__register_shortcodes_modules_and_addons')) {
934
-            $this->_incompatible_addon_error();
935
-        }
936
-    }
937
-
938
-
939
-    /**
940
-     * _incompatible_addon_error
941
-     *
942
-     * @access public
943
-     * @return void
944
-     */
945
-    private function _incompatible_addon_error()
946
-    {
947
-        // get array of classes hooking into here
948
-        $class_names = EEH_Class_Tools::get_class_names_for_all_callbacks_on_hook(
949
-            'AHEE__EE_System__register_shortcodes_modules_and_addons'
950
-        );
951
-        if (! empty($class_names)) {
952
-            $msg = esc_html__(
953
-                'The following plugins, addons, or modules appear to be incompatible with this version of Event Espresso and were automatically deactivated to avoid fatal errors:',
954
-                'event_espresso'
955
-            );
956
-            $msg .= '<ul>';
957
-            foreach ($class_names as $class_name) {
958
-                $msg .= '<li><b>Event Espresso - '
959
-                        . str_replace(
960
-                            ['EE_', 'EEM_', 'EED_', 'EES_', 'EEW_'],
961
-                            '',
962
-                            $class_name
963
-                        ) . '</b></li>';
964
-            }
965
-            $msg .= '</ul>';
966
-            $msg .= esc_html__(
967
-                'Compatibility issues can be avoided and/or resolved by keeping addons and plugins updated to the latest version.',
968
-                'event_espresso'
969
-            );
970
-            // save list of incompatible addons to wp-options for later use
971
-            add_option('ee_incompatible_addons', $class_names, '', 'no');
972
-            if (is_admin()) {
973
-                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
974
-            }
975
-        }
976
-    }
977
-
978
-
979
-    /**
980
-     * brew_espresso
981
-     * begins the process of setting hooks for initializing EE in the correct order
982
-     * This is happening on the 'AHEE__EE_Bootstrap__brew_espresso' hook point
983
-     * which runs during the WP 'plugins_loaded' action at priority 9
984
-     *
985
-     * @return void
986
-     * @throws Exception
987
-     */
988
-    public function brew_espresso()
989
-    {
990
-        do_action('AHEE__EE_System__brew_espresso__begin', $this);
991
-        // load some final core systems
992
-        add_action('init', [$this, 'set_hooks_for_core'], 1);
993
-        add_action('init', [$this, 'perform_activations_upgrades_and_migrations'], 3);
994
-        add_action('init', [$this, 'load_CPTs_and_session'], 5);
995
-        add_action('init', [$this, 'load_controllers'], 7);
996
-        add_action('init', [$this, 'core_loaded_and_ready'], 9);
997
-        add_action('init', [$this, 'initialize'], 10);
998
-        add_action('init', [$this, 'initialize_last'], 100);
999
-        $this->router->brewEspresso();
1000
-        do_action('AHEE__EE_System__brew_espresso__complete', $this);
1001
-    }
1002
-
1003
-
1004
-    /**
1005
-     *    set_hooks_for_core
1006
-     *
1007
-     * @access public
1008
-     * @return    void
1009
-     * @throws EE_Error
1010
-     */
1011
-    public function set_hooks_for_core()
1012
-    {
1013
-        $this->_deactivate_incompatible_addons();
1014
-        do_action('AHEE__EE_System__set_hooks_for_core');
1015
-        $this->loader->getShared('EventEspresso\core\domain\values\session\SessionLifespan');
1016
-        // caps need to be initialized on every request so that capability maps are set.
1017
-        // @see https://events.codebasehq.com/projects/event-espresso/tickets/8674
1018
-        $this->registry->CAP->init_caps();
1019
-    }
1020
-
1021
-
1022
-    /**
1023
-     * Using the information gathered in EE_System::_incompatible_addon_error,
1024
-     * deactivates any addons considered incompatible with the current version of EE
1025
-     */
1026
-    private function _deactivate_incompatible_addons()
1027
-    {
1028
-        $incompatible_addons = get_option('ee_incompatible_addons', []);
1029
-        if (! empty($incompatible_addons)) {
1030
-            $active_plugins = get_option('active_plugins', []);
1031
-            foreach ($active_plugins as $active_plugin) {
1032
-                foreach ($incompatible_addons as $incompatible_addon) {
1033
-                    if (strpos($active_plugin, $incompatible_addon) !== false) {
1034
-                        $this->request->unSetRequestParams(['activate'], true);
1035
-                        espresso_deactivate_plugin($active_plugin);
1036
-                    }
1037
-                }
1038
-            }
1039
-        }
1040
-    }
1041
-
1042
-
1043
-    /**
1044
-     *    perform_activations_upgrades_and_migrations
1045
-     *
1046
-     * @access public
1047
-     * @return    void
1048
-     */
1049
-    public function perform_activations_upgrades_and_migrations()
1050
-    {
1051
-        do_action('AHEE__EE_System__perform_activations_upgrades_and_migrations');
1052
-    }
1053
-
1054
-
1055
-    /**
1056
-     * @return void
1057
-     * @throws DomainException
1058
-     */
1059
-    public function load_CPTs_and_session()
1060
-    {
1061
-        do_action('AHEE__EE_System__load_CPTs_and_session__start');
1062
-        $this->register_custom_taxonomies->registerCustomTaxonomies();
1063
-        $this->register_custom_post_types->registerCustomPostTypes();
1064
-        $this->register_custom_taxonomy_terms->registerCustomTaxonomyTerms();
1065
-        // load legacy Custom Post Types and Taxonomies
1066
-        $this->loader->getShared('EE_Register_CPTs');
1067
-        do_action('AHEE__EE_System__load_CPTs_and_session__complete');
1068
-    }
1069
-
1070
-
1071
-    /**
1072
-     * load_controllers
1073
-     * this is the best place to load any additional controllers that needs access to EE core.
1074
-     * it is expected that all basic core EE systems, that are not dependant on the current request are loaded at this
1075
-     * time
1076
-     *
1077
-     * @access public
1078
-     * @return void
1079
-     * @throws Exception
1080
-     */
1081
-    public function load_controllers()
1082
-    {
1083
-        do_action('AHEE__EE_System__load_controllers__start');
1084
-        $this->router->loadControllers();
1085
-        do_action('AHEE__EE_System__load_controllers__complete');
1086
-    }
1087
-
1088
-
1089
-    /**
1090
-     * core_loaded_and_ready
1091
-     * all of the basic EE core should be loaded at this point and available regardless of M-Mode
1092
-     *
1093
-     * @access public
1094
-     * @return void
1095
-     * @throws Exception
1096
-     */
1097
-    public function core_loaded_and_ready()
1098
-    {
1099
-        $this->router->coreLoadedAndReady();
1100
-        do_action('AHEE__EE_System__core_loaded_and_ready');
1101
-        // always load template tags, because it's faster than checking if it's a front-end request, and many page
1102
-        // builders require these even on the front-end
1103
-        require_once EE_PUBLIC . 'template_tags.php';
1104
-        do_action('AHEE__EE_System__set_hooks_for_shortcodes_modules_and_addons');
1105
-    }
1106
-
1107
-
1108
-    /**
1109
-     * initialize
1110
-     * this is the best place to begin initializing client code
1111
-     *
1112
-     * @access public
1113
-     * @return void
1114
-     */
1115
-    public function initialize()
1116
-    {
1117
-        do_action('AHEE__EE_System__initialize');
1118
-        add_filter(
1119
-            'safe_style_css',
1120
-            function ($styles) {
1121
-                $styles[] = 'display';
1122
-                $styles[] = 'visibility';
1123
-                return $styles;
1124
-            }
1125
-        );
1126
-    }
1127
-
1128
-
1129
-    /**
1130
-     * initialize_last
1131
-     * this is run really late during the WP init hook point, and ensures that mostly everything else that needs to
1132
-     * initialize has done so
1133
-     *
1134
-     * @access public
1135
-     * @return void
1136
-     * @throws Exception
1137
-     */
1138
-    public function initialize_last()
1139
-    {
1140
-        $this->router->initializeLast();
1141
-        do_action('AHEE__EE_System__initialize_last');
1142
-        /** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
1143
-        $rewrite_rules = $this->loader->getShared(
1144
-            'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
1145
-        );
1146
-        $rewrite_rules->flushRewriteRules();
1147
-        add_action('admin_bar_init', [$this, 'addEspressoToolbar']);
1148
-    }
1149
-
1150
-
1151
-    /**
1152
-     * @return void
1153
-     */
1154
-    public function addEspressoToolbar()
1155
-    {
1156
-        $this->loader->getShared(
1157
-            'EventEspresso\core\domain\services\admin\AdminToolBar',
1158
-            [$this->registry->CAP]
1159
-        );
1160
-    }
1161
-
1162
-
1163
-    /**
1164
-     * do_not_cache
1165
-     * sets no cache headers and defines no cache constants for WP plugins
1166
-     *
1167
-     * @access public
1168
-     * @return void
1169
-     */
1170
-    public static function do_not_cache()
1171
-    {
1172
-        // set no cache constants
1173
-        if (! defined('DONOTCACHEPAGE')) {
1174
-            define('DONOTCACHEPAGE', true);
1175
-        }
1176
-        if (! defined('DONOTCACHCEOBJECT')) {
1177
-            define('DONOTCACHCEOBJECT', true);
1178
-        }
1179
-        if (! defined('DONOTCACHEDB')) {
1180
-            define('DONOTCACHEDB', true);
1181
-        }
1182
-        // add no cache headers
1183
-        add_action('send_headers', ['EE_System', 'nocache_headers'], 10);
1184
-        // plus a little extra for nginx and Google Chrome
1185
-        add_filter('nocache_headers', ['EE_System', 'extra_nocache_headers'], 10, 1);
1186
-        // prevent browsers from prefetching of the rel='next' link, because it may contain content that interferes with the registration process
1187
-        remove_action('wp_head', 'adjacent_posts_rel_link_wp_head');
1188
-    }
1189
-
1190
-
1191
-    /**
1192
-     *    extra_nocache_headers
1193
-     *
1194
-     * @access    public
1195
-     * @param $headers
1196
-     * @return    array
1197
-     */
1198
-    public static function extra_nocache_headers($headers): array
1199
-    {
1200
-        // for NGINX
1201
-        $headers['X-Accel-Expires'] = 0;
1202
-        // plus extra for Google Chrome since it doesn't seem to respect "no-cache", but WILL respect "no-store"
1203
-        $headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0';
1204
-        return $headers;
1205
-    }
1206
-
1207
-
1208
-    /**
1209
-     *    nocache_headers
1210
-     *
1211
-     * @access    public
1212
-     * @return    void
1213
-     */
1214
-    public static function nocache_headers()
1215
-    {
1216
-        nocache_headers();
1217
-    }
1218
-
1219
-
1220
-    /**
1221
-     * simply hooks into "wp_list_pages_exclude" filter (for wp_list_pages method) and makes sure EE critical pages are
1222
-     * never returned with the function.
1223
-     *
1224
-     * @param array $exclude_array any existing pages being excluded are in this array.
1225
-     * @return array
1226
-     */
1227
-    public function remove_pages_from_wp_list_pages(array $exclude_array): array
1228
-    {
1229
-        return array_merge($exclude_array, $this->registry->CFG->core->get_critical_pages_array());
1230
-    }
27
+	/**
28
+	 * indicates this is a 'normal' request. Ie, not activation, nor upgrade, nor activation.
29
+	 * So examples of this would be a normal GET request on the frontend or backend, or a POST, etc
30
+	 */
31
+	const req_type_normal = 0;
32
+
33
+	/**
34
+	 * Indicates this is a brand new installation of EE so we should install
35
+	 * tables and default data etc
36
+	 */
37
+	const req_type_new_activation = 1;
38
+
39
+	/**
40
+	 * we've detected that EE has been reactivated (or EE was activated during maintenance mode,
41
+	 * and we just exited maintenance mode). We MUST check the database is setup properly
42
+	 * and that default data is setup too
43
+	 */
44
+	const req_type_reactivation = 2;
45
+
46
+	/**
47
+	 * indicates that EE has been upgraded since its previous request.
48
+	 * We may have data migration scripts to call and will want to trigger maintenance mode
49
+	 */
50
+	const req_type_upgrade = 3;
51
+
52
+	/**
53
+	 * TODO  will detect that EE has been DOWNGRADED. We probably don't want to run in this case...
54
+	 */
55
+	const req_type_downgrade = 4;
56
+
57
+	/**
58
+	 * @deprecated since version 4.6.0.dev.006
59
+	 * Now whenever a new_activation is detected the request type is still just
60
+	 * new_activation (same for reactivation, upgrade, downgrade etc), but if we're in maintenance mode
61
+	 * EE_System::initialize_db_if_no_migrations_required and EE_Addon::initialize_db_if_no_migrations_required
62
+	 * will instead enqueue that EE plugin's db initialization for when we're taken out of maintenance mode.
63
+	 * (Specifically, when the migration manager indicates migrations are finished
64
+	 * EE_Data_Migration_Manager::initialize_db_for_enqueued_ee_plugins() will be called)
65
+	 */
66
+	const req_type_activation_but_not_installed = 5;
67
+
68
+	/**
69
+	 * option prefix for recording the activation history (like core's "espresso_db_update") of addons
70
+	 */
71
+	const addon_activation_history_option_prefix = 'ee_addon_activation_history_';
72
+
73
+	/**
74
+	 * @var AddonManager $addon_manager
75
+	 */
76
+	private $addon_manager;
77
+
78
+	/**
79
+	 * @var EE_System $_instance
80
+	 */
81
+	private static $_instance;
82
+
83
+	/**
84
+	 * @var EE_Registry $registry
85
+	 */
86
+	private $registry;
87
+
88
+	/**
89
+	 * @var LoaderInterface $loader
90
+	 */
91
+	private $loader;
92
+
93
+	/**
94
+	 * @var EE_Capabilities $capabilities
95
+	 */
96
+	private $capabilities;
97
+
98
+	/**
99
+	 * @var EE_Maintenance_Mode $maintenance_mode
100
+	 */
101
+	private $maintenance_mode;
102
+
103
+	/**
104
+	 * @var RequestInterface $request
105
+	 */
106
+	private $request;
107
+
108
+	/**
109
+	 * Stores which type of request this is, options being one of the constants on EE_System starting with req_type_*.
110
+	 * It can be a brand-new activation, a reactivation, an upgrade, a downgrade, or a normal request.
111
+	 *
112
+	 * @var int $_req_type
113
+	 */
114
+	private $_req_type;
115
+
116
+	/**
117
+	 * Whether or not there was a non-micro version change in EE core version during this request
118
+	 *
119
+	 * @var boolean $_major_version_change
120
+	 */
121
+	private $_major_version_change = false;
122
+
123
+	/**
124
+	 * @var Router $router
125
+	 */
126
+	private $router;
127
+
128
+	/**
129
+	 * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes
130
+	 */
131
+	private $register_custom_post_types;
132
+
133
+	/**
134
+	 * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies
135
+	 */
136
+	private $register_custom_taxonomies;
137
+
138
+	/**
139
+	 * @param EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms
140
+	 */
141
+	private $register_custom_taxonomy_terms;
142
+
143
+	/**
144
+	 * @singleton method used to instantiate class object
145
+	 * @param LoaderInterface|null     $loader
146
+	 * @param EE_Maintenance_Mode|null $maintenance_mode
147
+	 * @param EE_Registry|null         $registry
148
+	 * @param RequestInterface|null    $request
149
+	 * @param Router|null              $router
150
+	 * @return EE_System
151
+	 */
152
+	public static function instance(
153
+		LoaderInterface $loader = null,
154
+		EE_Maintenance_Mode $maintenance_mode = null,
155
+		EE_Registry $registry = null,
156
+		RequestInterface $request = null,
157
+		Router $router = null
158
+	): EE_System {
159
+		// check if class object is instantiated
160
+		if (! self::$_instance instanceof EE_System) {
161
+			self::$_instance = new self($loader, $maintenance_mode, $registry, $request, $router);
162
+		}
163
+		return self::$_instance;
164
+	}
165
+
166
+
167
+	/**
168
+	 * resets the instance and returns it
169
+	 *
170
+	 * @return EE_System
171
+	 */
172
+	public static function reset(): EE_System
173
+	{
174
+		self::$_instance->_req_type = null;
175
+		// make sure none of the old hooks are left hanging around
176
+		remove_all_actions('AHEE__EE_System__perform_activations_upgrades_and_migrations');
177
+		// we need to reset the migration manager in order for it to detect DMSs properly
178
+		EE_Data_Migration_Manager::reset();
179
+		self::instance()->detect_activations_or_upgrades();
180
+		self::instance()->perform_activations_upgrades_and_migrations();
181
+		return self::instance();
182
+	}
183
+
184
+
185
+	/**
186
+	 * sets hooks for running rest of system
187
+	 * provides "AHEE__EE_System__construct__complete" hook for EE Addons to use as their starting point
188
+	 * starting EE Addons from any other point may lead to problems
189
+	 *
190
+	 * @param LoaderInterface     $loader
191
+	 * @param EE_Maintenance_Mode $maintenance_mode
192
+	 * @param EE_Registry         $registry
193
+	 * @param RequestInterface    $request
194
+	 * @param Router              $router
195
+	 */
196
+	private function __construct(
197
+		LoaderInterface $loader,
198
+		EE_Maintenance_Mode $maintenance_mode,
199
+		EE_Registry $registry,
200
+		RequestInterface $request,
201
+		Router $router
202
+	) {
203
+		$this->registry         = $registry;
204
+		$this->loader           = $loader;
205
+		$this->request          = $request;
206
+		$this->router           = $router;
207
+		$this->maintenance_mode = $maintenance_mode;
208
+		do_action('AHEE__EE_System__construct__begin', $this);
209
+		add_action(
210
+			'AHEE__EE_Bootstrap__load_espresso_addons',
211
+			[$this, 'loadCapabilities'],
212
+			5
213
+		);
214
+		add_action(
215
+			'AHEE__EE_Bootstrap__load_espresso_addons',
216
+			[$this, 'loadCommandBus'],
217
+			7
218
+		);
219
+		add_action(
220
+			'AHEE__EE_Bootstrap__load_espresso_addons',
221
+			[$this, 'loadPluginApi'],
222
+			9
223
+		);
224
+		// allow addons to load first so that they can register autoloaders, set hooks for running DMS's, etc
225
+		add_action(
226
+			'AHEE__EE_Bootstrap__load_espresso_addons',
227
+			[$this, 'load_espresso_addons']
228
+		);
229
+		// when an ee addon is activated, we want to call the core hook(s) again
230
+		// because the newly-activated addon didn't get a chance to run at all
231
+		add_action('activate_plugin', [$this, 'load_espresso_addons'], 1);
232
+		// detect whether install or upgrade
233
+		add_action(
234
+			'AHEE__EE_Bootstrap__detect_activations_or_upgrades',
235
+			[$this, 'detect_activations_or_upgrades'],
236
+			3
237
+		);
238
+		// load EE_Config, EE_Textdomain, etc
239
+		add_action(
240
+			'AHEE__EE_Bootstrap__load_core_configuration',
241
+			[$this, 'load_core_configuration'],
242
+			5
243
+		);
244
+		// load specifications for matching routes to current request
245
+		add_action(
246
+			'AHEE__EE_Bootstrap__load_core_configuration',
247
+			[$this, 'loadRouteMatchSpecifications']
248
+		);
249
+		// load specifications for custom post types
250
+		add_action(
251
+			'AHEE__EE_Bootstrap__load_core_configuration',
252
+			array($this, 'loadCustomPostTypes')
253
+		);
254
+		// load specifications for custom post types
255
+		add_action(
256
+			'AHEE__EE_Bootstrap__load_core_configuration',
257
+			array($this, 'loadCustomPostTypes')
258
+		);
259
+		// load EE_Config, EE_Textdomain, etc
260
+		add_action(
261
+			'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets',
262
+			[$this, 'register_shortcodes_modules_and_widgets'],
263
+			7
264
+		);
265
+		// you wanna get going? I wanna get going... let's get going!
266
+		add_action(
267
+			'AHEE__EE_Bootstrap__brew_espresso',
268
+			[$this, 'brew_espresso'],
269
+			9
270
+		);
271
+		// other housekeeping
272
+		// exclude EE critical pages from wp_list_pages
273
+		add_filter(
274
+			'wp_list_pages_excludes',
275
+			[$this, 'remove_pages_from_wp_list_pages'],
276
+			10
277
+		);
278
+		// ALL EE Addons should use the following hook point to attach their initial setup too
279
+		// it's extremely important for EE Addons to register any class autoloaders so that they can be available when the EE_Config loads
280
+		do_action('AHEE__EE_System__construct__complete', $this);
281
+	}
282
+
283
+
284
+	/**
285
+	 * load and setup EE_Capabilities
286
+	 *
287
+	 * @return void
288
+	 */
289
+	public function loadCapabilities()
290
+	{
291
+		$this->capabilities = $this->loader->getShared('EE_Capabilities');
292
+		add_action(
293
+			'AHEE__EE_Capabilities__init_caps__before_initialization',
294
+			function () {
295
+				LoaderFactory::getLoader()->getShared('EE_Payment_Method_Manager');
296
+			}
297
+		);
298
+	}
299
+
300
+
301
+	/**
302
+	 * create and cache the CommandBus, and also add middleware
303
+	 * The CapChecker middleware requires the use of EE_Capabilities
304
+	 * which is why we need to load the CommandBus after Caps are set up
305
+	 *
306
+	 * @return void
307
+	 */
308
+	public function loadCommandBus()
309
+	{
310
+		$this->loader->getShared(
311
+			'CommandBusInterface',
312
+			[
313
+				null,
314
+				apply_filters(
315
+					'FHEE__EE_Load_Espresso_Core__handle_request__CommandBus_middleware',
316
+					[
317
+						$this->loader->getShared('EventEspresso\core\services\commands\middleware\CapChecker'),
318
+						$this->loader->getShared('EventEspresso\core\services\commands\middleware\AddActionHook'),
319
+					]
320
+				),
321
+			]
322
+		);
323
+	}
324
+
325
+
326
+	/**
327
+	 * @return void
328
+	 * @throws Exception
329
+	 */
330
+	public function loadPluginApi()
331
+	{
332
+		$this->addon_manager = $this->loader->getShared(AddonManager::class);
333
+		$this->addon_manager->initialize();
334
+		$this->loader->getShared('EE_Request_Handler');
335
+	}
336
+
337
+
338
+	/**
339
+	 * load_espresso_addons
340
+	 * allow addons to load first so that they can set hooks for running DMS's, etc
341
+	 * this is hooked into both:
342
+	 *    'AHEE__EE_Bootstrap__load_core_configuration'
343
+	 *        which runs during the WP 'plugins_loaded' action at priority 5
344
+	 *    and the WP 'activate_plugin' hook point
345
+	 *
346
+	 * @return void
347
+	 * @throws Exception
348
+	 */
349
+	public function load_espresso_addons()
350
+	{
351
+		// looking for hooks? they've been moved into the AddonManager to maintain compatibility
352
+		$this->addon_manager->loadAddons();
353
+	}
354
+
355
+
356
+	/**
357
+	 * detect_activations_or_upgrades
358
+	 * Checks for activation or upgrade of core first;
359
+	 * then also checks if any registered addons have been activated or upgraded
360
+	 * This is hooked into 'AHEE__EE_Bootstrap__detect_activations_or_upgrades'
361
+	 * which runs during the WP 'plugins_loaded' action at priority 3
362
+	 *
363
+	 * @access public
364
+	 * @return void
365
+	 */
366
+	public function detect_activations_or_upgrades()
367
+	{
368
+		// first off: let's make sure to handle core
369
+		$this->detect_if_activation_or_upgrade();
370
+		foreach ($this->registry->addons as $addon) {
371
+			if ($addon instanceof EE_Addon) {
372
+				// detect teh request type for that addon
373
+				$addon->detect_req_type();
374
+			}
375
+		}
376
+	}
377
+
378
+
379
+	/**
380
+	 * detect_if_activation_or_upgrade
381
+	 * Takes care of detecting whether this is a brand new install or code upgrade,
382
+	 * and either setting up the DB or setting up maintenance mode etc.
383
+	 *
384
+	 * @access public
385
+	 * @return void
386
+	 */
387
+	public function detect_if_activation_or_upgrade()
388
+	{
389
+		do_action('AHEE__EE_System___detect_if_activation_or_upgrade__begin');
390
+		// check if db has been updated, or if its a brand-new installation
391
+		$espresso_db_update = $this->fix_espresso_db_upgrade_option();
392
+		$request_type       = $this->detect_req_type($espresso_db_update);
393
+		// EEH_Debug_Tools::printr( $request_type, '$request_type', __FILE__, __LINE__ );
394
+		switch ($request_type) {
395
+			case EE_System::req_type_new_activation:
396
+				do_action('AHEE__EE_System__detect_if_activation_or_upgrade__new_activation');
397
+				$this->_handle_core_version_change($espresso_db_update);
398
+				break;
399
+			case EE_System::req_type_reactivation:
400
+				do_action('AHEE__EE_System__detect_if_activation_or_upgrade__reactivation');
401
+				$this->_handle_core_version_change($espresso_db_update);
402
+				break;
403
+			case EE_System::req_type_upgrade:
404
+				do_action('AHEE__EE_System__detect_if_activation_or_upgrade__upgrade');
405
+				// migrations may be required now that we've upgraded
406
+				$this->maintenance_mode->set_maintenance_mode_if_db_old();
407
+				$this->_handle_core_version_change($espresso_db_update);
408
+				break;
409
+			case EE_System::req_type_downgrade:
410
+				do_action('AHEE__EE_System__detect_if_activation_or_upgrade__downgrade');
411
+				// its possible migrations are no longer required
412
+				$this->maintenance_mode->set_maintenance_mode_if_db_old();
413
+				$this->_handle_core_version_change($espresso_db_update);
414
+				break;
415
+			case EE_System::req_type_normal:
416
+			default:
417
+				break;
418
+		}
419
+		do_action('AHEE__EE_System__detect_if_activation_or_upgrade__complete');
420
+	}
421
+
422
+
423
+	/**
424
+	 * Updates the list of installed versions and sets hooks for
425
+	 * initializing the database later during the request
426
+	 *
427
+	 * @param array $espresso_db_update
428
+	 */
429
+	private function _handle_core_version_change(array $espresso_db_update)
430
+	{
431
+		$this->update_list_of_installed_versions($espresso_db_update);
432
+		// get ready to verify the DB is ok (provided we aren't in maintenance mode, of course)
433
+		add_action(
434
+			'AHEE__EE_System__perform_activations_upgrades_and_migrations',
435
+			[$this, 'initialize_db_if_no_migrations_required']
436
+		);
437
+	}
438
+
439
+
440
+	/**
441
+	 * standardizes the wp option 'espresso_db_upgrade' which actually stores
442
+	 * information about what versions of EE have been installed and activated,
443
+	 * NOT necessarily the state of the database
444
+	 *
445
+	 * @param mixed $espresso_db_update           the value of the WordPress option.
446
+	 *                                            If not supplied, fetches it from the options table
447
+	 * @return array the correct value of 'espresso_db_upgrade', after saving it, if it needed correction
448
+	 */
449
+	private function fix_espresso_db_upgrade_option($espresso_db_update = null): array
450
+	{
451
+		do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__begin', $espresso_db_update);
452
+		if (! $espresso_db_update) {
453
+			$espresso_db_update = get_option('espresso_db_update');
454
+		}
455
+		// check that option is an array
456
+		if (! is_array($espresso_db_update)) {
457
+			// if option is FALSE, then it never existed
458
+			if ($espresso_db_update === false) {
459
+				// make $espresso_db_update an array and save option with autoload OFF
460
+				$espresso_db_update = [];
461
+				add_option('espresso_db_update', $espresso_db_update, '', 'no');
462
+			} else {
463
+				// option is NOT FALSE but also is NOT an array, so make it an array and save it
464
+				$espresso_db_update = [$espresso_db_update => []];
465
+				update_option('espresso_db_update', $espresso_db_update);
466
+			}
467
+		} else {
468
+			$corrected_db_update = [];
469
+			// if IS an array, but is it an array where KEYS are version numbers, and values are arrays?
470
+			foreach ($espresso_db_update as $should_be_version_string => $should_be_array) {
471
+				if (is_int($should_be_version_string) && ! is_array($should_be_array)) {
472
+					// the key is an int, and the value IS NOT an array
473
+					// so it must be numerically-indexed, where values are versions installed...
474
+					// fix it!
475
+					$version_string                         = $should_be_array;
476
+					$corrected_db_update[ $version_string ] = ['unknown-date'];
477
+				} else {
478
+					// ok it checks out
479
+					$corrected_db_update[ $should_be_version_string ] = $should_be_array;
480
+				}
481
+			}
482
+			$espresso_db_update = $corrected_db_update;
483
+			update_option('espresso_db_update', $espresso_db_update);
484
+		}
485
+		do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__complete', $espresso_db_update);
486
+		return ! empty($espresso_db_update) ? $espresso_db_update : [];
487
+	}
488
+
489
+
490
+	/**
491
+	 * Does the traditional work of setting up the plugin's database and adding default data.
492
+	 * If migration script/process did not exist, this is what would happen on every activation/reactivation/upgrade.
493
+	 * NOTE: if we're in maintenance mode (which would be the case if we detect there are data
494
+	 * migration scripts that need to be run and a version change happens), enqueues core for database initialization,
495
+	 * so that it will be done when migrations are finished
496
+	 *
497
+	 * @param boolean $initialize_addons_too if true, we double-check addons' database tables etc too;
498
+	 * @param boolean $verify_schema         if true will re-check the database tables have the correct schema.
499
+	 *                                       This is a resource-intensive job
500
+	 *                                       so we prefer to only do it when necessary
501
+	 * @return void
502
+	 * @throws EE_Error
503
+	 * @throws ReflectionException
504
+	 */
505
+	public function initialize_db_if_no_migrations_required($initialize_addons_too = false, $verify_schema = true)
506
+	{
507
+		$request_type = $this->detect_req_type();
508
+		// only initialize system if we're not in maintenance mode.
509
+		if ($this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance) {
510
+			/** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
511
+			$rewrite_rules = $this->loader->getShared(
512
+				'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
513
+			);
514
+			$rewrite_rules->flush();
515
+			if ($verify_schema) {
516
+				EEH_Activation::initialize_db_and_folders();
517
+			}
518
+			EEH_Activation::initialize_db_content();
519
+			EEH_Activation::system_initialization();
520
+			if ($initialize_addons_too) {
521
+				$this->initialize_addons();
522
+			}
523
+		} else {
524
+			EE_Data_Migration_Manager::instance()->enqueue_db_initialization_for('Core');
525
+		}
526
+		if (
527
+			$request_type === EE_System::req_type_new_activation
528
+			|| $request_type === EE_System::req_type_reactivation
529
+			|| (
530
+				$request_type === EE_System::req_type_upgrade
531
+				&& $this->is_major_version_change()
532
+			)
533
+		) {
534
+			add_action('AHEE__EE_System__initialize_last', [$this, 'redirect_to_about_ee'], 9);
535
+		}
536
+	}
537
+
538
+
539
+	/**
540
+	 * Initializes the db for all registered addons
541
+	 *
542
+	 * @throws EE_Error
543
+	 * @throws ReflectionException
544
+	 */
545
+	public function initialize_addons()
546
+	{
547
+		// foreach registered addon, make sure its db is up-to-date too
548
+		foreach ($this->registry->addons as $addon) {
549
+			if ($addon instanceof EE_Addon) {
550
+				$addon->initialize_db_if_no_migrations_required();
551
+			}
552
+		}
553
+	}
554
+
555
+
556
+	/**
557
+	 * Adds the current code version to the saved wp option which stores a list of all ee versions ever installed.
558
+	 *
559
+	 * @param array  $version_history
560
+	 * @param string $current_version_to_add version to be added to the version history
561
+	 * @return    boolean success as to whether or not this option was changed
562
+	 */
563
+	public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null): bool
564
+	{
565
+		if (! $version_history) {
566
+			$version_history = $this->fix_espresso_db_upgrade_option($version_history);
567
+		}
568
+		if ($current_version_to_add === null) {
569
+			$current_version_to_add = espresso_version();
570
+		}
571
+		$version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
572
+		// re-save
573
+		return update_option('espresso_db_update', $version_history);
574
+	}
575
+
576
+
577
+	/**
578
+	 * Detects if the current version indicated in the has existed in the list of
579
+	 * previously-installed versions of EE (espresso_db_update). Does NOT modify it (ie, no side-effect)
580
+	 *
581
+	 * @param array $espresso_db_update array from the wp option stored under the name 'espresso_db_update'.
582
+	 *                                  If not supplied, fetches it from the options table.
583
+	 *                                  Also, caches its result so later parts of the code can also know whether
584
+	 *                                  there's been an update or not. This way we can add the current version to
585
+	 *                                  espresso_db_update, but still know if this is a new install or not
586
+	 * @return int one of the constants on EE_System::req_type_
587
+	 */
588
+	public function detect_req_type($espresso_db_update = null): int
589
+	{
590
+		if ($this->_req_type === null) {
591
+			$espresso_db_update          = ! empty($espresso_db_update)
592
+				? $espresso_db_update
593
+				: $this->fix_espresso_db_upgrade_option();
594
+			$this->_req_type             = EE_System::detect_req_type_given_activation_history(
595
+				$espresso_db_update,
596
+				'ee_espresso_activation',
597
+				espresso_version()
598
+			);
599
+			$this->_major_version_change = $this->_detect_major_version_change($espresso_db_update);
600
+			$this->request->setIsActivation($this->_req_type !== EE_System::req_type_normal);
601
+		}
602
+		return $this->_req_type;
603
+	}
604
+
605
+
606
+	/**
607
+	 * Returns whether or not there was a non-micro version change (ie, change in either
608
+	 * the first or second number in the version. Eg 4.9.0.rc.001 to 4.10.0.rc.000,
609
+	 * but not 4.9.0.rc.0001 to 4.9.1.rc.0001
610
+	 *
611
+	 * @param $activation_history
612
+	 * @return bool
613
+	 */
614
+	private function _detect_major_version_change($activation_history): bool
615
+	{
616
+		$previous_version       = EE_System::getMostRecentlyActiveVersion($activation_history);
617
+		$previous_version_parts = explode('.', $previous_version);
618
+		$current_version_parts  = explode('.', espresso_version());
619
+		return isset(
620
+			$previous_version_parts[0],
621
+			$previous_version_parts[1],
622
+			$current_version_parts[0],
623
+			$current_version_parts[1]
624
+		) && (
625
+			$previous_version_parts[0] !== $current_version_parts[0]
626
+			|| $previous_version_parts[1] !== $current_version_parts[1]
627
+		);
628
+	}
629
+
630
+
631
+	/**
632
+	 * Returns true if either the major or minor version of EE changed during this request.
633
+	 * Eg 4.9.0.rc.001 to 4.10.0.rc.000, but not 4.9.0.rc.0001 to 4.9.1.rc.0001
634
+	 *
635
+	 * @return bool
636
+	 */
637
+	public function is_major_version_change(): bool
638
+	{
639
+		return $this->_major_version_change;
640
+	}
641
+
642
+
643
+	/**
644
+	 * Determines the request type for any ee addon, given three piece of info: the current array of activation
645
+	 * histories (for core that' 'espresso_db_update' wp option); the name of the WordPress option which is temporarily
646
+	 * set upon activation of the plugin (for core it's 'ee_espresso_activation'); and the version that this plugin was
647
+	 * just activated to (for core that will always be espresso_version())
648
+	 *
649
+	 * @param array|null $activation_history             the option's value which stores the activation history for
650
+	 *                                                 this
651
+	 *                                                 ee plugin. for core that's 'espresso_db_update'
652
+	 * @param string $activation_indicator_option_name the name of the WordPress option that is temporarily set to
653
+	 *                                                 indicate that this plugin was just activated
654
+	 * @param string $current_version                  the version that was just upgraded to (for core that will be
655
+	 *                                                 espresso_version())
656
+	 * @return int one of the constants on EE_System::req_type_
657
+	 */
658
+	public static function detect_req_type_given_activation_history(
659
+		array $activation_history,
660
+		string $activation_indicator_option_name,
661
+		string $current_version
662
+	): int {
663
+		$version_change = self::compareVersionWithPrevious($activation_history, $current_version);
664
+		$is_activation  = get_option($activation_indicator_option_name, false);
665
+		$req_type       = self::getRequestType($activation_history, $version_change, $is_activation);
666
+		if ($is_activation) {
667
+			// cleanup in aisle 6
668
+			delete_option($activation_indicator_option_name);
669
+		}
670
+		return $req_type;
671
+	}
672
+
673
+
674
+	/**
675
+	 * @param array  $activation_history
676
+	 * @param int    $version_change
677
+	 * @param bool   $is_activation
678
+	 * @return int
679
+	 * @since $VID:$
680
+	 */
681
+	private static function getRequestType(array $activation_history, int $version_change, bool $is_activation): int
682
+	{
683
+		// if no previous activation history exists, then this is a brand new install
684
+		if (empty($activation_history)) {
685
+			return EE_System::req_type_new_activation;
686
+		}
687
+		// current version is higher than previous version, so it's an upgrade
688
+		if ($version_change === 1) {
689
+			return EE_System::req_type_upgrade;
690
+		}
691
+		// current version is lower than previous version, so it's a downgrade
692
+		if ($version_change === -1) {
693
+			return EE_System::req_type_downgrade;
694
+		}
695
+		// version hasn't changed since last version so check if the activation indicator is set
696
+		// to determine if it's a reactivation, or just a normal request
697
+		return $is_activation
698
+			? EE_System::req_type_reactivation
699
+			: EE_System::req_type_normal;
700
+	}
701
+
702
+
703
+	/**
704
+	 * Detects if the $version_to_upgrade_to is higher than the most recent version in
705
+	 * the $activation_history_for_addon
706
+	 *
707
+	 * @param array  $activation_history    array where keys are versions,
708
+	 *                                      values are arrays of times activated (sometimes 'unknown-date')
709
+	 * @param string $current_version
710
+	 * @return int results of version_compare( $version_to_upgrade_to, $most_recently_active_version ).
711
+	 *                                      -1 if $version_to_upgrade_to is LOWER (downgrade);
712
+	 *                                      0 if $version_to_upgrade_to MATCHES (reactivation or normal request);
713
+	 *                                      1 if $version_to_upgrade_to is HIGHER (upgrade) ;
714
+	 */
715
+	private static function compareVersionWithPrevious(array $activation_history, string $current_version): int
716
+	{
717
+		// find the most recently-activated version
718
+		$most_recently_active_version = EE_System::getMostRecentlyActiveVersion($activation_history);
719
+		return version_compare($current_version, $most_recently_active_version);
720
+	}
721
+
722
+
723
+	/**
724
+	 * Gets the most recently active version listed in the activation history,
725
+	 * and if none are found (ie, it's a brand new install) returns '0.0.0.dev.000'.
726
+	 *
727
+	 * @param array $activation_history  (keys are versions, values are arrays of times activated,
728
+	 *                                   sometimes containing 'unknown-date'
729
+	 * @return string
730
+	 */
731
+	private static function getMostRecentlyActiveVersion(array $activation_history): string
732
+	{
733
+		$most_recent_activation_date  = '1970-01-01 00:00:00';
734
+		$most_recently_active_version = '0.0.0.dev.000';
735
+		if (is_array($activation_history)) {
736
+			foreach ($activation_history as $version => $activation_dates) {
737
+				// check there is a record of when this version was activated.
738
+				// Otherwise, mark it as unknown
739
+				if (! $activation_dates) {
740
+					$activation_dates = ['unknown-date'];
741
+				}
742
+				$activation_dates = is_string($activation_dates) ? [$activation_dates] : $activation_dates;
743
+				foreach ($activation_dates as $activation_date) {
744
+					if ($activation_date !== 'unknown-date' && $activation_date > $most_recent_activation_date) {
745
+						$most_recently_active_version = $version;
746
+						$most_recent_activation_date  = $activation_date;
747
+					}
748
+				}
749
+			}
750
+		}
751
+		return $most_recently_active_version;
752
+	}
753
+
754
+
755
+	/**
756
+	 * This redirects to the about EE page after activation
757
+	 *
758
+	 * @return void
759
+	 */
760
+	public function redirect_to_about_ee()
761
+	{
762
+		$notices = EE_Error::get_notices(false);
763
+		// if current user is an admin and it's not an ajax or rest request
764
+		if (
765
+			! isset($notices['errors'])
766
+			&& $this->request->isAdmin()
767
+			&& apply_filters(
768
+				'FHEE__EE_System__redirect_to_about_ee__do_redirect',
769
+				$this->capabilities->current_user_can('manage_options', 'espresso_about_default')
770
+			)
771
+		) {
772
+			$query_params = ['page' => 'espresso_about'];
773
+			if (EE_System::instance()->detect_req_type() === EE_System::req_type_new_activation) {
774
+				$query_params['new_activation'] = true;
775
+			}
776
+			if (EE_System::instance()->detect_req_type() === EE_System::req_type_reactivation) {
777
+				$query_params['reactivation'] = true;
778
+			}
779
+			$url = add_query_arg($query_params, admin_url('admin.php'));
780
+			EEH_URL::safeRedirectAndExit($url);
781
+		}
782
+	}
783
+
784
+
785
+	/**
786
+	 * load_core_configuration
787
+	 * this is hooked into 'AHEE__EE_Bootstrap__load_core_configuration'
788
+	 * which runs during the WP 'plugins_loaded' action at priority 5
789
+	 *
790
+	 * @return void
791
+	 * @throws ReflectionException
792
+	 * @throws Exception
793
+	 */
794
+	public function load_core_configuration()
795
+	{
796
+		do_action('AHEE__EE_System__load_core_configuration__begin', $this);
797
+		$this->loader->getShared('EE_Load_Textdomain');
798
+		// load textdomain
799
+		EE_Load_Textdomain::load_textdomain();
800
+		// load caf stuff a chance to play during the activation process too.
801
+		$this->_maybe_brew_regular();
802
+		// load and setup EE_Config and EE_Network_Config
803
+		$config = $this->loader->getShared('EE_Config');
804
+		$this->loader->getShared('EE_Network_Config');
805
+		// setup autoloaders
806
+		// enable logging?
807
+		if ($config->admin->use_remote_logging) {
808
+			$this->loader->getShared('EE_Log');
809
+		}
810
+		// check for activation errors
811
+		$activation_errors = get_option('ee_plugin_activation_errors', false);
812
+		if ($activation_errors) {
813
+			EE_Error::add_error($activation_errors, __FILE__, __FUNCTION__, __LINE__);
814
+			update_option('ee_plugin_activation_errors', false);
815
+		}
816
+		// get model names
817
+		$this->_parse_model_names();
818
+		// configure custom post type definitions
819
+		$this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions');
820
+		$this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions');
821
+		do_action('AHEE__EE_System__load_core_configuration__complete', $this);
822
+	}
823
+
824
+
825
+	/**
826
+	 * cycles through all of the models/*.model.php files, and assembles an array of model names
827
+	 *
828
+	 * @return void
829
+	 * @throws ReflectionException
830
+	 */
831
+	private function _parse_model_names()
832
+	{
833
+		// get all the files in the EE_MODELS folder that end in .model.php
834
+		$models                 = glob(EE_MODELS . '*.model.php');
835
+		$model_names            = [];
836
+		$non_abstract_db_models = [];
837
+		foreach ($models as $model) {
838
+			// get model classname
839
+			$classname       = EEH_File::get_classname_from_filepath_with_standard_filename($model);
840
+			$short_name      = str_replace('EEM_', '', $classname);
841
+			$reflectionClass = new ReflectionClass($classname);
842
+			if ($reflectionClass->isSubclassOf('EEM_Base') && ! $reflectionClass->isAbstract()) {
843
+				$non_abstract_db_models[ $short_name ] = $classname;
844
+			}
845
+			$model_names[ $short_name ] = $classname;
846
+		}
847
+		$this->registry->models                 = apply_filters('FHEE__EE_System__parse_model_names', $model_names);
848
+		$this->registry->non_abstract_db_models = apply_filters(
849
+			'FHEE__EE_System__parse_implemented_model_names',
850
+			$non_abstract_db_models
851
+		);
852
+	}
853
+
854
+
855
+	/**
856
+	 * The purpose of this method is to simply check for a file named "caffeinated/brewing_regular.php" for any hooks
857
+	 * that need to be setup before our EE_System launches.
858
+	 *
859
+	 * @return void
860
+	 * @throws DomainException
861
+	 * @throws InvalidArgumentException
862
+	 * @throws InvalidDataTypeException
863
+	 * @throws InvalidInterfaceException
864
+	 * @throws InvalidClassException
865
+	 * @throws InvalidFilePathException
866
+	 */
867
+	private function _maybe_brew_regular()
868
+	{
869
+		/** @var Domain $domain */
870
+		$domain = DomainFactory::getEventEspressoCoreDomain();
871
+		if ($domain->isCaffeinated()) {
872
+			require_once EE_CAFF_PATH . 'brewing_regular.php';
873
+		}
874
+	}
875
+
876
+
877
+	/**
878
+	 * @throws Exception
879
+	 * @since 4.9.71.p
880
+	 */
881
+	public function loadRouteMatchSpecifications()
882
+	{
883
+		try {
884
+			$this->loader->getShared('EventEspresso\core\services\routing\RouteMatchSpecificationManager');
885
+			$this->loader->getShared('EventEspresso\core\services\routing\RouteCollection');
886
+			$this->router->loadPrimaryRoutes();
887
+		} catch (Exception $exception) {
888
+			new ExceptionStackTraceDisplay($exception);
889
+		}
890
+		do_action('AHEE__EE_System__loadRouteMatchSpecifications');
891
+	}
892
+
893
+
894
+	/**
895
+	 * loading CPT related classes earlier so that their definitions are available
896
+	 * but not performing any actual registration with WP core until load_CPTs_and_session() is called
897
+	 *
898
+	 * @since   4.10.21.p
899
+	 */
900
+	public function loadCustomPostTypes()
901
+	{
902
+		$this->register_custom_taxonomies = $this->loader->getShared(
903
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'
904
+		);
905
+		$this->register_custom_post_types = $this->loader->getShared(
906
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'
907
+		);
908
+		$this->register_custom_taxonomy_terms = $this->loader->getShared(
909
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms'
910
+		);
911
+		// integrate WP_Query with the EE models
912
+		$this->loader->getShared('EE_CPT_Strategy');
913
+		// load legacy EE_Request_Handler in case add-ons still need it
914
+		$this->loader->getShared('EE_Request_Handler');
915
+	}
916
+
917
+
918
+	/**
919
+	 * register_shortcodes_modules_and_widgets
920
+	 * generate lists of shortcodes and modules, then verify paths and classes
921
+	 * This is hooked into 'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets'
922
+	 * which runs during the WP 'plugins_loaded' action at priority 7
923
+	 *
924
+	 * @access public
925
+	 * @return void
926
+	 * @throws Exception
927
+	 */
928
+	public function register_shortcodes_modules_and_widgets()
929
+	{
930
+		$this->router->registerShortcodesModulesAndWidgets();
931
+		do_action('AHEE__EE_System__register_shortcodes_modules_and_widgets');
932
+		// check for addons using old hook point
933
+		if (has_action('AHEE__EE_System__register_shortcodes_modules_and_addons')) {
934
+			$this->_incompatible_addon_error();
935
+		}
936
+	}
937
+
938
+
939
+	/**
940
+	 * _incompatible_addon_error
941
+	 *
942
+	 * @access public
943
+	 * @return void
944
+	 */
945
+	private function _incompatible_addon_error()
946
+	{
947
+		// get array of classes hooking into here
948
+		$class_names = EEH_Class_Tools::get_class_names_for_all_callbacks_on_hook(
949
+			'AHEE__EE_System__register_shortcodes_modules_and_addons'
950
+		);
951
+		if (! empty($class_names)) {
952
+			$msg = esc_html__(
953
+				'The following plugins, addons, or modules appear to be incompatible with this version of Event Espresso and were automatically deactivated to avoid fatal errors:',
954
+				'event_espresso'
955
+			);
956
+			$msg .= '<ul>';
957
+			foreach ($class_names as $class_name) {
958
+				$msg .= '<li><b>Event Espresso - '
959
+						. str_replace(
960
+							['EE_', 'EEM_', 'EED_', 'EES_', 'EEW_'],
961
+							'',
962
+							$class_name
963
+						) . '</b></li>';
964
+			}
965
+			$msg .= '</ul>';
966
+			$msg .= esc_html__(
967
+				'Compatibility issues can be avoided and/or resolved by keeping addons and plugins updated to the latest version.',
968
+				'event_espresso'
969
+			);
970
+			// save list of incompatible addons to wp-options for later use
971
+			add_option('ee_incompatible_addons', $class_names, '', 'no');
972
+			if (is_admin()) {
973
+				EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
974
+			}
975
+		}
976
+	}
977
+
978
+
979
+	/**
980
+	 * brew_espresso
981
+	 * begins the process of setting hooks for initializing EE in the correct order
982
+	 * This is happening on the 'AHEE__EE_Bootstrap__brew_espresso' hook point
983
+	 * which runs during the WP 'plugins_loaded' action at priority 9
984
+	 *
985
+	 * @return void
986
+	 * @throws Exception
987
+	 */
988
+	public function brew_espresso()
989
+	{
990
+		do_action('AHEE__EE_System__brew_espresso__begin', $this);
991
+		// load some final core systems
992
+		add_action('init', [$this, 'set_hooks_for_core'], 1);
993
+		add_action('init', [$this, 'perform_activations_upgrades_and_migrations'], 3);
994
+		add_action('init', [$this, 'load_CPTs_and_session'], 5);
995
+		add_action('init', [$this, 'load_controllers'], 7);
996
+		add_action('init', [$this, 'core_loaded_and_ready'], 9);
997
+		add_action('init', [$this, 'initialize'], 10);
998
+		add_action('init', [$this, 'initialize_last'], 100);
999
+		$this->router->brewEspresso();
1000
+		do_action('AHEE__EE_System__brew_espresso__complete', $this);
1001
+	}
1002
+
1003
+
1004
+	/**
1005
+	 *    set_hooks_for_core
1006
+	 *
1007
+	 * @access public
1008
+	 * @return    void
1009
+	 * @throws EE_Error
1010
+	 */
1011
+	public function set_hooks_for_core()
1012
+	{
1013
+		$this->_deactivate_incompatible_addons();
1014
+		do_action('AHEE__EE_System__set_hooks_for_core');
1015
+		$this->loader->getShared('EventEspresso\core\domain\values\session\SessionLifespan');
1016
+		// caps need to be initialized on every request so that capability maps are set.
1017
+		// @see https://events.codebasehq.com/projects/event-espresso/tickets/8674
1018
+		$this->registry->CAP->init_caps();
1019
+	}
1020
+
1021
+
1022
+	/**
1023
+	 * Using the information gathered in EE_System::_incompatible_addon_error,
1024
+	 * deactivates any addons considered incompatible with the current version of EE
1025
+	 */
1026
+	private function _deactivate_incompatible_addons()
1027
+	{
1028
+		$incompatible_addons = get_option('ee_incompatible_addons', []);
1029
+		if (! empty($incompatible_addons)) {
1030
+			$active_plugins = get_option('active_plugins', []);
1031
+			foreach ($active_plugins as $active_plugin) {
1032
+				foreach ($incompatible_addons as $incompatible_addon) {
1033
+					if (strpos($active_plugin, $incompatible_addon) !== false) {
1034
+						$this->request->unSetRequestParams(['activate'], true);
1035
+						espresso_deactivate_plugin($active_plugin);
1036
+					}
1037
+				}
1038
+			}
1039
+		}
1040
+	}
1041
+
1042
+
1043
+	/**
1044
+	 *    perform_activations_upgrades_and_migrations
1045
+	 *
1046
+	 * @access public
1047
+	 * @return    void
1048
+	 */
1049
+	public function perform_activations_upgrades_and_migrations()
1050
+	{
1051
+		do_action('AHEE__EE_System__perform_activations_upgrades_and_migrations');
1052
+	}
1053
+
1054
+
1055
+	/**
1056
+	 * @return void
1057
+	 * @throws DomainException
1058
+	 */
1059
+	public function load_CPTs_and_session()
1060
+	{
1061
+		do_action('AHEE__EE_System__load_CPTs_and_session__start');
1062
+		$this->register_custom_taxonomies->registerCustomTaxonomies();
1063
+		$this->register_custom_post_types->registerCustomPostTypes();
1064
+		$this->register_custom_taxonomy_terms->registerCustomTaxonomyTerms();
1065
+		// load legacy Custom Post Types and Taxonomies
1066
+		$this->loader->getShared('EE_Register_CPTs');
1067
+		do_action('AHEE__EE_System__load_CPTs_and_session__complete');
1068
+	}
1069
+
1070
+
1071
+	/**
1072
+	 * load_controllers
1073
+	 * this is the best place to load any additional controllers that needs access to EE core.
1074
+	 * it is expected that all basic core EE systems, that are not dependant on the current request are loaded at this
1075
+	 * time
1076
+	 *
1077
+	 * @access public
1078
+	 * @return void
1079
+	 * @throws Exception
1080
+	 */
1081
+	public function load_controllers()
1082
+	{
1083
+		do_action('AHEE__EE_System__load_controllers__start');
1084
+		$this->router->loadControllers();
1085
+		do_action('AHEE__EE_System__load_controllers__complete');
1086
+	}
1087
+
1088
+
1089
+	/**
1090
+	 * core_loaded_and_ready
1091
+	 * all of the basic EE core should be loaded at this point and available regardless of M-Mode
1092
+	 *
1093
+	 * @access public
1094
+	 * @return void
1095
+	 * @throws Exception
1096
+	 */
1097
+	public function core_loaded_and_ready()
1098
+	{
1099
+		$this->router->coreLoadedAndReady();
1100
+		do_action('AHEE__EE_System__core_loaded_and_ready');
1101
+		// always load template tags, because it's faster than checking if it's a front-end request, and many page
1102
+		// builders require these even on the front-end
1103
+		require_once EE_PUBLIC . 'template_tags.php';
1104
+		do_action('AHEE__EE_System__set_hooks_for_shortcodes_modules_and_addons');
1105
+	}
1106
+
1107
+
1108
+	/**
1109
+	 * initialize
1110
+	 * this is the best place to begin initializing client code
1111
+	 *
1112
+	 * @access public
1113
+	 * @return void
1114
+	 */
1115
+	public function initialize()
1116
+	{
1117
+		do_action('AHEE__EE_System__initialize');
1118
+		add_filter(
1119
+			'safe_style_css',
1120
+			function ($styles) {
1121
+				$styles[] = 'display';
1122
+				$styles[] = 'visibility';
1123
+				return $styles;
1124
+			}
1125
+		);
1126
+	}
1127
+
1128
+
1129
+	/**
1130
+	 * initialize_last
1131
+	 * this is run really late during the WP init hook point, and ensures that mostly everything else that needs to
1132
+	 * initialize has done so
1133
+	 *
1134
+	 * @access public
1135
+	 * @return void
1136
+	 * @throws Exception
1137
+	 */
1138
+	public function initialize_last()
1139
+	{
1140
+		$this->router->initializeLast();
1141
+		do_action('AHEE__EE_System__initialize_last');
1142
+		/** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
1143
+		$rewrite_rules = $this->loader->getShared(
1144
+			'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
1145
+		);
1146
+		$rewrite_rules->flushRewriteRules();
1147
+		add_action('admin_bar_init', [$this, 'addEspressoToolbar']);
1148
+	}
1149
+
1150
+
1151
+	/**
1152
+	 * @return void
1153
+	 */
1154
+	public function addEspressoToolbar()
1155
+	{
1156
+		$this->loader->getShared(
1157
+			'EventEspresso\core\domain\services\admin\AdminToolBar',
1158
+			[$this->registry->CAP]
1159
+		);
1160
+	}
1161
+
1162
+
1163
+	/**
1164
+	 * do_not_cache
1165
+	 * sets no cache headers and defines no cache constants for WP plugins
1166
+	 *
1167
+	 * @access public
1168
+	 * @return void
1169
+	 */
1170
+	public static function do_not_cache()
1171
+	{
1172
+		// set no cache constants
1173
+		if (! defined('DONOTCACHEPAGE')) {
1174
+			define('DONOTCACHEPAGE', true);
1175
+		}
1176
+		if (! defined('DONOTCACHCEOBJECT')) {
1177
+			define('DONOTCACHCEOBJECT', true);
1178
+		}
1179
+		if (! defined('DONOTCACHEDB')) {
1180
+			define('DONOTCACHEDB', true);
1181
+		}
1182
+		// add no cache headers
1183
+		add_action('send_headers', ['EE_System', 'nocache_headers'], 10);
1184
+		// plus a little extra for nginx and Google Chrome
1185
+		add_filter('nocache_headers', ['EE_System', 'extra_nocache_headers'], 10, 1);
1186
+		// prevent browsers from prefetching of the rel='next' link, because it may contain content that interferes with the registration process
1187
+		remove_action('wp_head', 'adjacent_posts_rel_link_wp_head');
1188
+	}
1189
+
1190
+
1191
+	/**
1192
+	 *    extra_nocache_headers
1193
+	 *
1194
+	 * @access    public
1195
+	 * @param $headers
1196
+	 * @return    array
1197
+	 */
1198
+	public static function extra_nocache_headers($headers): array
1199
+	{
1200
+		// for NGINX
1201
+		$headers['X-Accel-Expires'] = 0;
1202
+		// plus extra for Google Chrome since it doesn't seem to respect "no-cache", but WILL respect "no-store"
1203
+		$headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0';
1204
+		return $headers;
1205
+	}
1206
+
1207
+
1208
+	/**
1209
+	 *    nocache_headers
1210
+	 *
1211
+	 * @access    public
1212
+	 * @return    void
1213
+	 */
1214
+	public static function nocache_headers()
1215
+	{
1216
+		nocache_headers();
1217
+	}
1218
+
1219
+
1220
+	/**
1221
+	 * simply hooks into "wp_list_pages_exclude" filter (for wp_list_pages method) and makes sure EE critical pages are
1222
+	 * never returned with the function.
1223
+	 *
1224
+	 * @param array $exclude_array any existing pages being excluded are in this array.
1225
+	 * @return array
1226
+	 */
1227
+	public function remove_pages_from_wp_list_pages(array $exclude_array): array
1228
+	{
1229
+		return array_merge($exclude_array, $this->registry->CFG->core->get_critical_pages_array());
1230
+	}
1231 1231
 }
Please login to merge, or discard this patch.
Spacing   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
         Router $router = null
158 158
     ): EE_System {
159 159
         // check if class object is instantiated
160
-        if (! self::$_instance instanceof EE_System) {
160
+        if ( ! self::$_instance instanceof EE_System) {
161 161
             self::$_instance = new self($loader, $maintenance_mode, $registry, $request, $router);
162 162
         }
163 163
         return self::$_instance;
@@ -291,7 +291,7 @@  discard block
 block discarded – undo
291 291
         $this->capabilities = $this->loader->getShared('EE_Capabilities');
292 292
         add_action(
293 293
             'AHEE__EE_Capabilities__init_caps__before_initialization',
294
-            function () {
294
+            function() {
295 295
                 LoaderFactory::getLoader()->getShared('EE_Payment_Method_Manager');
296 296
             }
297 297
         );
@@ -449,11 +449,11 @@  discard block
 block discarded – undo
449 449
     private function fix_espresso_db_upgrade_option($espresso_db_update = null): array
450 450
     {
451 451
         do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__begin', $espresso_db_update);
452
-        if (! $espresso_db_update) {
452
+        if ( ! $espresso_db_update) {
453 453
             $espresso_db_update = get_option('espresso_db_update');
454 454
         }
455 455
         // check that option is an array
456
-        if (! is_array($espresso_db_update)) {
456
+        if ( ! is_array($espresso_db_update)) {
457 457
             // if option is FALSE, then it never existed
458 458
             if ($espresso_db_update === false) {
459 459
                 // make $espresso_db_update an array and save option with autoload OFF
@@ -473,10 +473,10 @@  discard block
 block discarded – undo
473 473
                     // so it must be numerically-indexed, where values are versions installed...
474 474
                     // fix it!
475 475
                     $version_string                         = $should_be_array;
476
-                    $corrected_db_update[ $version_string ] = ['unknown-date'];
476
+                    $corrected_db_update[$version_string] = ['unknown-date'];
477 477
                 } else {
478 478
                     // ok it checks out
479
-                    $corrected_db_update[ $should_be_version_string ] = $should_be_array;
479
+                    $corrected_db_update[$should_be_version_string] = $should_be_array;
480 480
                 }
481 481
             }
482 482
             $espresso_db_update = $corrected_db_update;
@@ -562,13 +562,13 @@  discard block
 block discarded – undo
562 562
      */
563 563
     public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null): bool
564 564
     {
565
-        if (! $version_history) {
565
+        if ( ! $version_history) {
566 566
             $version_history = $this->fix_espresso_db_upgrade_option($version_history);
567 567
         }
568 568
         if ($current_version_to_add === null) {
569 569
             $current_version_to_add = espresso_version();
570 570
         }
571
-        $version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
571
+        $version_history[$current_version_to_add][] = date('Y-m-d H:i:s', time());
572 572
         // re-save
573 573
         return update_option('espresso_db_update', $version_history);
574 574
     }
@@ -736,7 +736,7 @@  discard block
 block discarded – undo
736 736
             foreach ($activation_history as $version => $activation_dates) {
737 737
                 // check there is a record of when this version was activated.
738 738
                 // Otherwise, mark it as unknown
739
-                if (! $activation_dates) {
739
+                if ( ! $activation_dates) {
740 740
                     $activation_dates = ['unknown-date'];
741 741
                 }
742 742
                 $activation_dates = is_string($activation_dates) ? [$activation_dates] : $activation_dates;
@@ -831,7 +831,7 @@  discard block
 block discarded – undo
831 831
     private function _parse_model_names()
832 832
     {
833 833
         // get all the files in the EE_MODELS folder that end in .model.php
834
-        $models                 = glob(EE_MODELS . '*.model.php');
834
+        $models                 = glob(EE_MODELS.'*.model.php');
835 835
         $model_names            = [];
836 836
         $non_abstract_db_models = [];
837 837
         foreach ($models as $model) {
@@ -840,9 +840,9 @@  discard block
 block discarded – undo
840 840
             $short_name      = str_replace('EEM_', '', $classname);
841 841
             $reflectionClass = new ReflectionClass($classname);
842 842
             if ($reflectionClass->isSubclassOf('EEM_Base') && ! $reflectionClass->isAbstract()) {
843
-                $non_abstract_db_models[ $short_name ] = $classname;
843
+                $non_abstract_db_models[$short_name] = $classname;
844 844
             }
845
-            $model_names[ $short_name ] = $classname;
845
+            $model_names[$short_name] = $classname;
846 846
         }
847 847
         $this->registry->models                 = apply_filters('FHEE__EE_System__parse_model_names', $model_names);
848 848
         $this->registry->non_abstract_db_models = apply_filters(
@@ -869,7 +869,7 @@  discard block
 block discarded – undo
869 869
         /** @var Domain $domain */
870 870
         $domain = DomainFactory::getEventEspressoCoreDomain();
871 871
         if ($domain->isCaffeinated()) {
872
-            require_once EE_CAFF_PATH . 'brewing_regular.php';
872
+            require_once EE_CAFF_PATH.'brewing_regular.php';
873 873
         }
874 874
     }
875 875
 
@@ -948,7 +948,7 @@  discard block
 block discarded – undo
948 948
         $class_names = EEH_Class_Tools::get_class_names_for_all_callbacks_on_hook(
949 949
             'AHEE__EE_System__register_shortcodes_modules_and_addons'
950 950
         );
951
-        if (! empty($class_names)) {
951
+        if ( ! empty($class_names)) {
952 952
             $msg = esc_html__(
953 953
                 'The following plugins, addons, or modules appear to be incompatible with this version of Event Espresso and were automatically deactivated to avoid fatal errors:',
954 954
                 'event_espresso'
@@ -960,7 +960,7 @@  discard block
 block discarded – undo
960 960
                             ['EE_', 'EEM_', 'EED_', 'EES_', 'EEW_'],
961 961
                             '',
962 962
                             $class_name
963
-                        ) . '</b></li>';
963
+                        ).'</b></li>';
964 964
             }
965 965
             $msg .= '</ul>';
966 966
             $msg .= esc_html__(
@@ -1026,7 +1026,7 @@  discard block
 block discarded – undo
1026 1026
     private function _deactivate_incompatible_addons()
1027 1027
     {
1028 1028
         $incompatible_addons = get_option('ee_incompatible_addons', []);
1029
-        if (! empty($incompatible_addons)) {
1029
+        if ( ! empty($incompatible_addons)) {
1030 1030
             $active_plugins = get_option('active_plugins', []);
1031 1031
             foreach ($active_plugins as $active_plugin) {
1032 1032
                 foreach ($incompatible_addons as $incompatible_addon) {
@@ -1100,7 +1100,7 @@  discard block
 block discarded – undo
1100 1100
         do_action('AHEE__EE_System__core_loaded_and_ready');
1101 1101
         // always load template tags, because it's faster than checking if it's a front-end request, and many page
1102 1102
         // builders require these even on the front-end
1103
-        require_once EE_PUBLIC . 'template_tags.php';
1103
+        require_once EE_PUBLIC.'template_tags.php';
1104 1104
         do_action('AHEE__EE_System__set_hooks_for_shortcodes_modules_and_addons');
1105 1105
     }
1106 1106
 
@@ -1117,7 +1117,7 @@  discard block
 block discarded – undo
1117 1117
         do_action('AHEE__EE_System__initialize');
1118 1118
         add_filter(
1119 1119
             'safe_style_css',
1120
-            function ($styles) {
1120
+            function($styles) {
1121 1121
                 $styles[] = 'display';
1122 1122
                 $styles[] = 'visibility';
1123 1123
                 return $styles;
@@ -1170,13 +1170,13 @@  discard block
 block discarded – undo
1170 1170
     public static function do_not_cache()
1171 1171
     {
1172 1172
         // set no cache constants
1173
-        if (! defined('DONOTCACHEPAGE')) {
1173
+        if ( ! defined('DONOTCACHEPAGE')) {
1174 1174
             define('DONOTCACHEPAGE', true);
1175 1175
         }
1176
-        if (! defined('DONOTCACHCEOBJECT')) {
1176
+        if ( ! defined('DONOTCACHCEOBJECT')) {
1177 1177
             define('DONOTCACHCEOBJECT', true);
1178 1178
         }
1179
-        if (! defined('DONOTCACHEDB')) {
1179
+        if ( ! defined('DONOTCACHEDB')) {
1180 1180
             define('DONOTCACHEDB', true);
1181 1181
         }
1182 1182
         // add no cache headers
Please login to merge, or discard this patch.
core/domain/services/pue/Stats.php 1 patch
Indentation   +204 added lines, -204 removed lines patch added patch discarded remove patch
@@ -21,86 +21,86 @@  discard block
 block discarded – undo
21 21
  */
22 22
 class Stats
23 23
 {
24
-    const OPTIONS_KEY_EXPIRY_TIMESTAMP_FOR_SENDING_STATS = 'ee_uxip_stats_expiry';
25
-
26
-    /**
27
-     * @var Config
28
-     */
29
-    private $config;
30
-
31
-
32
-    /**
33
-     * @var StatsGatherer
34
-     */
35
-    private $stats_gatherer;
36
-
37
-
38
-    /**
39
-     * @var EE_Maintenance_Mode
40
-     */
41
-    private $maintenance_mode;
42
-
43
-    public function __construct(
44
-        Config $config,
45
-        EE_Maintenance_Mode $maintenance_mode,
46
-        StatsGatherer $stats_gatherer
47
-    ) {
48
-        $this->config = $config;
49
-        $this->maintenance_mode = $maintenance_mode;
50
-        $this->stats_gatherer = $stats_gatherer;
51
-        $this->setUxipNotices();
52
-    }
53
-
54
-
55
-    /**
56
-     * Displays uxip opt-in notice if necessary.
57
-     */
58
-    private function setUxipNotices()
59
-    {
60
-        if ($this->canDisplayNotices()) {
61
-            add_action('admin_notices', array($this, 'optinNotice'));
62
-            add_action('admin_enqueue_scripts', array($this, 'enqueueScripts'));
63
-            add_action('wp_ajax_espresso_data_optin', array($this, 'ajaxHandler'));
64
-        }
65
-    }
66
-
67
-
68
-    /**
69
-     * This returns the callback that PluginUpdateEngineChecker will use for getting any extra stats to send.
70
-     *
71
-     * @return Closure
72
-     */
73
-    public function statsCallback()
74
-    {
75
-        // returns a callback that can is used to retrieve the stats to send along to the pue server.
76
-        return function () {
77
-            // we only send stats one a week, so let's see if our stat timestamp has expired.
78
-            if (! $this->sendStats()) {
79
-                return array();
80
-            }
81
-            return $this->stats_gatherer->stats();
82
-        };
83
-    }
84
-
85
-
86
-    /**
87
-     * Return whether notices can be displayed or not
88
-     *
89
-     * @return bool
90
-     */
91
-    private function canDisplayNotices()
92
-    {
93
-        return ! $this->config->hasNotifiedForUxip()
94
-               && $this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance;
95
-    }
96
-
97
-
98
-    /**
99
-     * Callback for the admin_notices hook that outputs the UXIP optin-in notice.
100
-     */
101
-    public function optinNotice()
102
-    {
103
-        ?>
24
+	const OPTIONS_KEY_EXPIRY_TIMESTAMP_FOR_SENDING_STATS = 'ee_uxip_stats_expiry';
25
+
26
+	/**
27
+	 * @var Config
28
+	 */
29
+	private $config;
30
+
31
+
32
+	/**
33
+	 * @var StatsGatherer
34
+	 */
35
+	private $stats_gatherer;
36
+
37
+
38
+	/**
39
+	 * @var EE_Maintenance_Mode
40
+	 */
41
+	private $maintenance_mode;
42
+
43
+	public function __construct(
44
+		Config $config,
45
+		EE_Maintenance_Mode $maintenance_mode,
46
+		StatsGatherer $stats_gatherer
47
+	) {
48
+		$this->config = $config;
49
+		$this->maintenance_mode = $maintenance_mode;
50
+		$this->stats_gatherer = $stats_gatherer;
51
+		$this->setUxipNotices();
52
+	}
53
+
54
+
55
+	/**
56
+	 * Displays uxip opt-in notice if necessary.
57
+	 */
58
+	private function setUxipNotices()
59
+	{
60
+		if ($this->canDisplayNotices()) {
61
+			add_action('admin_notices', array($this, 'optinNotice'));
62
+			add_action('admin_enqueue_scripts', array($this, 'enqueueScripts'));
63
+			add_action('wp_ajax_espresso_data_optin', array($this, 'ajaxHandler'));
64
+		}
65
+	}
66
+
67
+
68
+	/**
69
+	 * This returns the callback that PluginUpdateEngineChecker will use for getting any extra stats to send.
70
+	 *
71
+	 * @return Closure
72
+	 */
73
+	public function statsCallback()
74
+	{
75
+		// returns a callback that can is used to retrieve the stats to send along to the pue server.
76
+		return function () {
77
+			// we only send stats one a week, so let's see if our stat timestamp has expired.
78
+			if (! $this->sendStats()) {
79
+				return array();
80
+			}
81
+			return $this->stats_gatherer->stats();
82
+		};
83
+	}
84
+
85
+
86
+	/**
87
+	 * Return whether notices can be displayed or not
88
+	 *
89
+	 * @return bool
90
+	 */
91
+	private function canDisplayNotices()
92
+	{
93
+		return ! $this->config->hasNotifiedForUxip()
94
+			   && $this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance;
95
+	}
96
+
97
+
98
+	/**
99
+	 * Callback for the admin_notices hook that outputs the UXIP optin-in notice.
100
+	 */
101
+	public function optinNotice()
102
+	{
103
+		?>
104 104
         <div class="espresso-notices updated data-collect-optin" id="espresso-data-collect-optin-container">
105 105
             <div id="data-collect-optin-options-container">
106 106
                 <span class="dashicons dashicons-admin-site"></span>
@@ -113,128 +113,128 @@  discard block
 block discarded – undo
113 113
             </div>
114 114
         </div>
115 115
         <?php
116
-    }
117
-
118
-
119
-    /**
120
-     * Retrieves the optin text (static so it can be used in multiple places as necessary).
121
-     *
122
-     * @param bool $extra
123
-     */
124
-    public static function optinText($extra = true)
125
-    {
126
-        if (! $extra) {
127
-            echo '<h2 class="ee-admin-settings-hdr" '
128
-                 . (! $extra ? 'id="UXIP_settings"' : '')
129
-                 . '>'
130
-                 . esc_html__('User eXperience Improvement Program (UXIP)', 'event_espresso')
131
-                 . EEH_Template::get_help_tab_link('organization_logo_info')
132
-                 . '</h2>';
133
-            printf(
134
-                esc_html__(
135
-                    '%1$sPlease help us make Event Espresso better and vote for your favorite features.%2$s The %3$sUser eXperience Improvement Program (UXIP)%4$s, has been created so when you use Event Espresso you are voting for the features and settings that are important to you. The UXIP helps us understand how you use our products and services, track problems and in what context. If you opt-out of the UXIP you essentially elect for us to disregard how you use Event Espresso as we build new features and make changes. Participation in the program is completely voluntary and it is disabled by default. The end results of the UXIP are software improvements to better meet your needs. The data we collect will never be sold, traded, or misused in any way. %5$sPlease see our %6$sPrivacy Policy%7$s for more information.',
136
-                    'event_espresso'
137
-                ),
138
-                '<p><em>',
139
-                '</em></p>',
140
-                '<a href="https://eventespresso.com/about/user-experience-improvement-program-uxip/" target="_blank">',
141
-                '</a>',
142
-                '<br><br>',
143
-                '<a href="https://eventespresso.com/about/privacy-policy/" target="_blank">',
144
-                '</a>'
145
-            );
146
-        } else {
147
-            $settings_url = EEH_URL::add_query_args_and_nonce(
148
-                array('action' => 'default'),
149
-                admin_url('admin.php?page=espresso_general_settings')
150
-            );
151
-            $settings_url .= '#UXIP_settings';
152
-            printf(
153
-                esc_html__(
154
-                    'The Event Espresso UXIP feature is not yet active on your site. For %1$smore info%2$s and to opt-in %3$sclick here%4$s.',
155
-                    'event_espresso'
156
-                ),
157
-                '<a href="https://eventespresso.com/about/user-experience-improvement-program-uxip/" target="_blank">',
158
-                '</a>',
159
-                '<a href="' . $settings_url . '" target="_blank">',
160
-                '</a>'
161
-            );
162
-        }
163
-    }
164
-
165
-
166
-    /**
167
-     * Callback for admin_enqueue_scripts that sets up the scripts and styles for the uxip notice
168
-     */
169
-    public function enqueueScripts()
170
-    {
171
-        wp_register_script(
172
-            'ee-data-optin-js',
173
-            EE_GLOBAL_ASSETS_URL . 'scripts/ee-data-optin.js',
174
-            array('jquery'),
175
-            EVENT_ESPRESSO_VERSION,
176
-            true
177
-        );
178
-        wp_register_style(
179
-            'ee-data-optin-css',
180
-            EE_GLOBAL_ASSETS_URL . 'css/ee-data-optin.css',
181
-            array(),
182
-            EVENT_ESPRESSO_VERSION
183
-        );
184
-
185
-        wp_enqueue_script('ee-data-optin-js');
186
-        wp_enqueue_style('ee-data-optin-css');
187
-    }
188
-
189
-
190
-    /**
191
-     * Callback for wp_ajax_espresso_data_optin that handles the ajax request
192
-     */
193
-    public function ajaxHandler()
194
-    {
195
-        /** @var RequestInterface $request */
196
-        $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
197
-        $nonce = $request->getRequestParam('nonce');
198
-        // verify nonce
199
-        if (! $nonce || ! wp_verify_nonce($nonce, 'ee-data-optin')) {
200
-            exit();
201
-        }
202
-
203
-        // update has notified option
204
-        $this->config->setHasNotifiedAboutUxip();
205
-        exit();
206
-    }
207
-
208
-
209
-    /**
210
-     * Used to determine whether additional stats are sent.
211
-     */
212
-    private function sendStats()
213
-    {
214
-        return $this->config->isOptedInForUxip()
215
-               && $this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance
216
-               && $this->statSendTimestampExpired();
217
-    }
218
-
219
-
220
-    /**
221
-     * Returns true when the timestamp used to track whether stats get sent (currently a weekly interval) is expired.
222
-     * Returns false otherwise.
223
-     *
224
-     * @return bool
225
-     */
226
-    private function statSendTimestampExpired()
227
-    {
228
-        $current_expiry = get_option(self::OPTIONS_KEY_EXPIRY_TIMESTAMP_FOR_SENDING_STATS, null);
229
-        if ($current_expiry === null) {
230
-            add_option(self::OPTIONS_KEY_EXPIRY_TIMESTAMP_FOR_SENDING_STATS, time() + WEEK_IN_SECONDS, '', 'no');
231
-            return true;
232
-        }
233
-
234
-        if (time() > (int) $current_expiry) {
235
-            update_option(self::OPTIONS_KEY_EXPIRY_TIMESTAMP_FOR_SENDING_STATS, time() + WEEK_IN_SECONDS);
236
-            return true;
237
-        }
238
-        return false;
239
-    }
116
+	}
117
+
118
+
119
+	/**
120
+	 * Retrieves the optin text (static so it can be used in multiple places as necessary).
121
+	 *
122
+	 * @param bool $extra
123
+	 */
124
+	public static function optinText($extra = true)
125
+	{
126
+		if (! $extra) {
127
+			echo '<h2 class="ee-admin-settings-hdr" '
128
+				 . (! $extra ? 'id="UXIP_settings"' : '')
129
+				 . '>'
130
+				 . esc_html__('User eXperience Improvement Program (UXIP)', 'event_espresso')
131
+				 . EEH_Template::get_help_tab_link('organization_logo_info')
132
+				 . '</h2>';
133
+			printf(
134
+				esc_html__(
135
+					'%1$sPlease help us make Event Espresso better and vote for your favorite features.%2$s The %3$sUser eXperience Improvement Program (UXIP)%4$s, has been created so when you use Event Espresso you are voting for the features and settings that are important to you. The UXIP helps us understand how you use our products and services, track problems and in what context. If you opt-out of the UXIP you essentially elect for us to disregard how you use Event Espresso as we build new features and make changes. Participation in the program is completely voluntary and it is disabled by default. The end results of the UXIP are software improvements to better meet your needs. The data we collect will never be sold, traded, or misused in any way. %5$sPlease see our %6$sPrivacy Policy%7$s for more information.',
136
+					'event_espresso'
137
+				),
138
+				'<p><em>',
139
+				'</em></p>',
140
+				'<a href="https://eventespresso.com/about/user-experience-improvement-program-uxip/" target="_blank">',
141
+				'</a>',
142
+				'<br><br>',
143
+				'<a href="https://eventespresso.com/about/privacy-policy/" target="_blank">',
144
+				'</a>'
145
+			);
146
+		} else {
147
+			$settings_url = EEH_URL::add_query_args_and_nonce(
148
+				array('action' => 'default'),
149
+				admin_url('admin.php?page=espresso_general_settings')
150
+			);
151
+			$settings_url .= '#UXIP_settings';
152
+			printf(
153
+				esc_html__(
154
+					'The Event Espresso UXIP feature is not yet active on your site. For %1$smore info%2$s and to opt-in %3$sclick here%4$s.',
155
+					'event_espresso'
156
+				),
157
+				'<a href="https://eventespresso.com/about/user-experience-improvement-program-uxip/" target="_blank">',
158
+				'</a>',
159
+				'<a href="' . $settings_url . '" target="_blank">',
160
+				'</a>'
161
+			);
162
+		}
163
+	}
164
+
165
+
166
+	/**
167
+	 * Callback for admin_enqueue_scripts that sets up the scripts and styles for the uxip notice
168
+	 */
169
+	public function enqueueScripts()
170
+	{
171
+		wp_register_script(
172
+			'ee-data-optin-js',
173
+			EE_GLOBAL_ASSETS_URL . 'scripts/ee-data-optin.js',
174
+			array('jquery'),
175
+			EVENT_ESPRESSO_VERSION,
176
+			true
177
+		);
178
+		wp_register_style(
179
+			'ee-data-optin-css',
180
+			EE_GLOBAL_ASSETS_URL . 'css/ee-data-optin.css',
181
+			array(),
182
+			EVENT_ESPRESSO_VERSION
183
+		);
184
+
185
+		wp_enqueue_script('ee-data-optin-js');
186
+		wp_enqueue_style('ee-data-optin-css');
187
+	}
188
+
189
+
190
+	/**
191
+	 * Callback for wp_ajax_espresso_data_optin that handles the ajax request
192
+	 */
193
+	public function ajaxHandler()
194
+	{
195
+		/** @var RequestInterface $request */
196
+		$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
197
+		$nonce = $request->getRequestParam('nonce');
198
+		// verify nonce
199
+		if (! $nonce || ! wp_verify_nonce($nonce, 'ee-data-optin')) {
200
+			exit();
201
+		}
202
+
203
+		// update has notified option
204
+		$this->config->setHasNotifiedAboutUxip();
205
+		exit();
206
+	}
207
+
208
+
209
+	/**
210
+	 * Used to determine whether additional stats are sent.
211
+	 */
212
+	private function sendStats()
213
+	{
214
+		return $this->config->isOptedInForUxip()
215
+			   && $this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance
216
+			   && $this->statSendTimestampExpired();
217
+	}
218
+
219
+
220
+	/**
221
+	 * Returns true when the timestamp used to track whether stats get sent (currently a weekly interval) is expired.
222
+	 * Returns false otherwise.
223
+	 *
224
+	 * @return bool
225
+	 */
226
+	private function statSendTimestampExpired()
227
+	{
228
+		$current_expiry = get_option(self::OPTIONS_KEY_EXPIRY_TIMESTAMP_FOR_SENDING_STATS, null);
229
+		if ($current_expiry === null) {
230
+			add_option(self::OPTIONS_KEY_EXPIRY_TIMESTAMP_FOR_SENDING_STATS, time() + WEEK_IN_SECONDS, '', 'no');
231
+			return true;
232
+		}
233
+
234
+		if (time() > (int) $current_expiry) {
235
+			update_option(self::OPTIONS_KEY_EXPIRY_TIMESTAMP_FOR_SENDING_STATS, time() + WEEK_IN_SECONDS);
236
+			return true;
237
+		}
238
+		return false;
239
+	}
240 240
 }
Please login to merge, or discard this patch.
core/domain/services/admin/registrations/DatetimesForEventCheckIn.php 2 patches
Indentation   +155 added lines, -155 removed lines patch added patch discarded remove patch
@@ -12,159 +12,159 @@
 block discarded – undo
12 12
 
13 13
 class DatetimesForEventCheckIn
14 14
 {
15
-    /**
16
-     * @var EE_Capabilities $caps
17
-     */
18
-    public $caps;
19
-
20
-    /**
21
-     * @var EE_Event
22
-     */
23
-    protected $event;
24
-
25
-    /**
26
-     * @var EE_Event[]
27
-     */
28
-    protected $all_events;
29
-
30
-    /**
31
-     * @var EE_Datetime[][]
32
-     */
33
-    protected $datetimes;
34
-
35
-    /**
36
-     * @var int
37
-     */
38
-    private $start_date_offset;
39
-
40
-
41
-    /**
42
-     * @param EE_Capabilities $capabilities
43
-     * @param EE_Event|null   $event
44
-     */
45
-    public function __construct(EE_Capabilities $capabilities, ?EE_Event $event = null)
46
-    {
47
-        $this->event = $event;
48
-        $this->caps = $capabilities;
49
-        $this->start_date_offset = absint(
50
-            apply_filters(
51
-                'FHEE__EventEspresso_core_domain_services_admin_registrations_DatetimesForEventCheckIn__start_date_offset',
52
-                HOUR_IN_SECONDS * 2,
53
-                $this->event
54
-            )
55
-        );
56
-    }
57
-
58
-
59
-    /**
60
-     * @param int $event_id
61
-     * @return DatetimesForEventCheckIn
62
-     * @throws EE_Error
63
-     * @throws ReflectionException
64
-     */
65
-    public static function fromEventID(int $event_id): DatetimesForEventCheckIn
66
-    {
67
-        /** @var EE_Event $event */
68
-        $event = EEM_Event::instance()->get_one_by_ID($event_id);
69
-        return new DatetimesForEventCheckIn(EE_Capabilities::instance(), $event);
70
-    }
71
-
72
-
73
-    /**
74
-     * @param EE_Event $event
75
-     * @return DatetimesForEventCheckIn
76
-     */
77
-    public static function fromEvent(EE_Event $event): DatetimesForEventCheckIn
78
-    {
79
-        return new DatetimesForEventCheckIn(EE_Capabilities::instance(), $event);
80
-    }
81
-
82
-
83
-    /**
84
-     * @param EE_Registration $registration
85
-     * @return DatetimesForEventCheckIn
86
-     * @throws EE_Error
87
-     */
88
-    public static function fromRegistration(EE_Registration $registration): DatetimesForEventCheckIn
89
-    {
90
-        return new DatetimesForEventCheckIn(EE_Capabilities::instance(), $registration->event());
91
-    }
92
-
93
-
94
-    /**
95
-     * @return EE_Event
96
-     */
97
-    public function event(): EE_Event
98
-    {
99
-        return $this->event;
100
-    }
101
-
102
-
103
-    /**
104
-     * @return array
105
-     * @throws EE_Error
106
-     * @throws ReflectionException
107
-     */
108
-    public function getAllActiveDatetimesForAllEvents(): array
109
-    {
110
-        if ($this->all_events === null) {
111
-            $where = [
112
-                'Registration.REG_ID' => ['!=', null]
113
-            ];
114
-            if (! $this->caps->current_user_can('ee_read_private_events', 'get_events')) {
115
-                $where['status**'] = ['!=', 'private'];
116
-            }
117
-            if (! $this->caps->current_user_can('ee_read_others_events', 'get_events')) {
118
-                $where['EVT_wp_user'] = get_current_user_id();
119
-            }
120
-            $this->all_events = EEM_Event::instance()->get_all(
121
-                [
122
-                    $where,
123
-                    'order_by' => ['Datetime.DTT_EVT_start' => 'DESC'],
124
-                ]
125
-            );
126
-        }
127
-        return $this->all_events;
128
-    }
129
-
130
-
131
-    /**
132
-     * @return array
133
-     * @throws EE_Error
134
-     * @throws ReflectionException
135
-     */
136
-    public function getAllActiveDatetimesForEvent(bool $active = true): array
137
-    {
138
-        $start_date = $active ? time() - $this->start_date_offset : null;
139
-        return $this->event instanceof EE_Event
140
-            ? $this->event->activeDatetimes($start_date)
141
-            : [];
142
-    }
143
-
144
-
145
-    /**
146
-     * @param int|null $DTD_ID If specific datetime ID is supplied, will return that date, but only if it is active.
147
-     *                         If no ID is supplied but event only has one related datetime, then it will be returned.
148
-     *                         If the above conditions are not met, then function will return null.
149
-     * @param bool $active
150
-     * @return EE_Datetime|null
151
-     * @throws EE_Error
152
-     * @throws ReflectionException
153
-     */
154
-    public function getOneActiveDatetimeForEvent(?int $DTD_ID = 0, bool $active = true): ?EE_Datetime
155
-    {
156
-        $key = $active ? 1 : 0;
157
-        if (! isset($this->datetimes[ $key ]) || $this->datetimes[ $key ] === null) {
158
-            $this->datetimes[ $key ] = $this->getAllActiveDatetimesForEvent($active);
159
-        }
160
-        if ($DTD_ID) {
161
-            foreach ($this->datetimes[ $key ] as $datetime) {
162
-                if ($datetime instanceof EE_Datetime && $datetime->ID() === $DTD_ID) {
163
-                    return $datetime;
164
-                }
165
-            }
166
-            return null;
167
-        }
168
-        return count($this->datetimes[ $key ]) === 1 ? reset($this->datetimes[ $key ]) : null;
169
-    }
15
+	/**
16
+	 * @var EE_Capabilities $caps
17
+	 */
18
+	public $caps;
19
+
20
+	/**
21
+	 * @var EE_Event
22
+	 */
23
+	protected $event;
24
+
25
+	/**
26
+	 * @var EE_Event[]
27
+	 */
28
+	protected $all_events;
29
+
30
+	/**
31
+	 * @var EE_Datetime[][]
32
+	 */
33
+	protected $datetimes;
34
+
35
+	/**
36
+	 * @var int
37
+	 */
38
+	private $start_date_offset;
39
+
40
+
41
+	/**
42
+	 * @param EE_Capabilities $capabilities
43
+	 * @param EE_Event|null   $event
44
+	 */
45
+	public function __construct(EE_Capabilities $capabilities, ?EE_Event $event = null)
46
+	{
47
+		$this->event = $event;
48
+		$this->caps = $capabilities;
49
+		$this->start_date_offset = absint(
50
+			apply_filters(
51
+				'FHEE__EventEspresso_core_domain_services_admin_registrations_DatetimesForEventCheckIn__start_date_offset',
52
+				HOUR_IN_SECONDS * 2,
53
+				$this->event
54
+			)
55
+		);
56
+	}
57
+
58
+
59
+	/**
60
+	 * @param int $event_id
61
+	 * @return DatetimesForEventCheckIn
62
+	 * @throws EE_Error
63
+	 * @throws ReflectionException
64
+	 */
65
+	public static function fromEventID(int $event_id): DatetimesForEventCheckIn
66
+	{
67
+		/** @var EE_Event $event */
68
+		$event = EEM_Event::instance()->get_one_by_ID($event_id);
69
+		return new DatetimesForEventCheckIn(EE_Capabilities::instance(), $event);
70
+	}
71
+
72
+
73
+	/**
74
+	 * @param EE_Event $event
75
+	 * @return DatetimesForEventCheckIn
76
+	 */
77
+	public static function fromEvent(EE_Event $event): DatetimesForEventCheckIn
78
+	{
79
+		return new DatetimesForEventCheckIn(EE_Capabilities::instance(), $event);
80
+	}
81
+
82
+
83
+	/**
84
+	 * @param EE_Registration $registration
85
+	 * @return DatetimesForEventCheckIn
86
+	 * @throws EE_Error
87
+	 */
88
+	public static function fromRegistration(EE_Registration $registration): DatetimesForEventCheckIn
89
+	{
90
+		return new DatetimesForEventCheckIn(EE_Capabilities::instance(), $registration->event());
91
+	}
92
+
93
+
94
+	/**
95
+	 * @return EE_Event
96
+	 */
97
+	public function event(): EE_Event
98
+	{
99
+		return $this->event;
100
+	}
101
+
102
+
103
+	/**
104
+	 * @return array
105
+	 * @throws EE_Error
106
+	 * @throws ReflectionException
107
+	 */
108
+	public function getAllActiveDatetimesForAllEvents(): array
109
+	{
110
+		if ($this->all_events === null) {
111
+			$where = [
112
+				'Registration.REG_ID' => ['!=', null]
113
+			];
114
+			if (! $this->caps->current_user_can('ee_read_private_events', 'get_events')) {
115
+				$where['status**'] = ['!=', 'private'];
116
+			}
117
+			if (! $this->caps->current_user_can('ee_read_others_events', 'get_events')) {
118
+				$where['EVT_wp_user'] = get_current_user_id();
119
+			}
120
+			$this->all_events = EEM_Event::instance()->get_all(
121
+				[
122
+					$where,
123
+					'order_by' => ['Datetime.DTT_EVT_start' => 'DESC'],
124
+				]
125
+			);
126
+		}
127
+		return $this->all_events;
128
+	}
129
+
130
+
131
+	/**
132
+	 * @return array
133
+	 * @throws EE_Error
134
+	 * @throws ReflectionException
135
+	 */
136
+	public function getAllActiveDatetimesForEvent(bool $active = true): array
137
+	{
138
+		$start_date = $active ? time() - $this->start_date_offset : null;
139
+		return $this->event instanceof EE_Event
140
+			? $this->event->activeDatetimes($start_date)
141
+			: [];
142
+	}
143
+
144
+
145
+	/**
146
+	 * @param int|null $DTD_ID If specific datetime ID is supplied, will return that date, but only if it is active.
147
+	 *                         If no ID is supplied but event only has one related datetime, then it will be returned.
148
+	 *                         If the above conditions are not met, then function will return null.
149
+	 * @param bool $active
150
+	 * @return EE_Datetime|null
151
+	 * @throws EE_Error
152
+	 * @throws ReflectionException
153
+	 */
154
+	public function getOneActiveDatetimeForEvent(?int $DTD_ID = 0, bool $active = true): ?EE_Datetime
155
+	{
156
+		$key = $active ? 1 : 0;
157
+		if (! isset($this->datetimes[ $key ]) || $this->datetimes[ $key ] === null) {
158
+			$this->datetimes[ $key ] = $this->getAllActiveDatetimesForEvent($active);
159
+		}
160
+		if ($DTD_ID) {
161
+			foreach ($this->datetimes[ $key ] as $datetime) {
162
+				if ($datetime instanceof EE_Datetime && $datetime->ID() === $DTD_ID) {
163
+					return $datetime;
164
+				}
165
+			}
166
+			return null;
167
+		}
168
+		return count($this->datetimes[ $key ]) === 1 ? reset($this->datetimes[ $key ]) : null;
169
+	}
170 170
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -111,10 +111,10 @@  discard block
 block discarded – undo
111 111
             $where = [
112 112
                 'Registration.REG_ID' => ['!=', null]
113 113
             ];
114
-            if (! $this->caps->current_user_can('ee_read_private_events', 'get_events')) {
114
+            if ( ! $this->caps->current_user_can('ee_read_private_events', 'get_events')) {
115 115
                 $where['status**'] = ['!=', 'private'];
116 116
             }
117
-            if (! $this->caps->current_user_can('ee_read_others_events', 'get_events')) {
117
+            if ( ! $this->caps->current_user_can('ee_read_others_events', 'get_events')) {
118 118
                 $where['EVT_wp_user'] = get_current_user_id();
119 119
             }
120 120
             $this->all_events = EEM_Event::instance()->get_all(
@@ -154,17 +154,17 @@  discard block
 block discarded – undo
154 154
     public function getOneActiveDatetimeForEvent(?int $DTD_ID = 0, bool $active = true): ?EE_Datetime
155 155
     {
156 156
         $key = $active ? 1 : 0;
157
-        if (! isset($this->datetimes[ $key ]) || $this->datetimes[ $key ] === null) {
158
-            $this->datetimes[ $key ] = $this->getAllActiveDatetimesForEvent($active);
157
+        if ( ! isset($this->datetimes[$key]) || $this->datetimes[$key] === null) {
158
+            $this->datetimes[$key] = $this->getAllActiveDatetimesForEvent($active);
159 159
         }
160 160
         if ($DTD_ID) {
161
-            foreach ($this->datetimes[ $key ] as $datetime) {
161
+            foreach ($this->datetimes[$key] as $datetime) {
162 162
                 if ($datetime instanceof EE_Datetime && $datetime->ID() === $DTD_ID) {
163 163
                     return $datetime;
164 164
                 }
165 165
             }
166 166
             return null;
167 167
         }
168
-        return count($this->datetimes[ $key ]) === 1 ? reset($this->datetimes[ $key ]) : null;
168
+        return count($this->datetimes[$key]) === 1 ? reset($this->datetimes[$key]) : null;
169 169
     }
170 170
 }
Please login to merge, or discard this patch.
core/services/request/middleware/PreProductionVersionWarning.php 1 patch
Indentation   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -18,91 +18,91 @@
 block discarded – undo
18 18
  */
19 19
 class PreProductionVersionWarning extends Middleware
20 20
 {
21
-    /**
22
-     * converts a Request to a Response
23
-     *
24
-     * @param RequestInterface  $request
25
-     * @param ResponseInterface $response
26
-     * @return ResponseInterface
27
-     */
28
-    public function handleRequest(RequestInterface $request, ResponseInterface $response)
29
-    {
30
-        $this->request = $request;
31
-        $this->response = $response;
32
-        $this->displayPreProductionVersionWarning();
33
-        $this->response = $this->processRequestStack($this->request, $this->response);
34
-        return $this->response;
35
-    }
21
+	/**
22
+	 * converts a Request to a Response
23
+	 *
24
+	 * @param RequestInterface  $request
25
+	 * @param ResponseInterface $response
26
+	 * @return ResponseInterface
27
+	 */
28
+	public function handleRequest(RequestInterface $request, ResponseInterface $response)
29
+	{
30
+		$this->request = $request;
31
+		$this->response = $response;
32
+		$this->displayPreProductionVersionWarning();
33
+		$this->response = $this->processRequestStack($this->request, $this->response);
34
+		return $this->response;
35
+	}
36 36
 
37 37
 
38
-    /**
39
-     * displays message on frontend of site notifying admin that EE has been temporarily placed into maintenance mode
40
-     *
41
-     * @return void
42
-     */
43
-    public function displayPreProductionVersionWarning()
44
-    {
45
-        // skip AJAX requests
46
-        if ($this->request->isAjax()) {
47
-            return;
48
-        }
49
-        // skip stable releases
50
-        if (substr(EVENT_ESPRESSO_VERSION, -5) !== '.beta') {
51
-            return;
52
-        }
53
-        // site admin has authorized use of non-stable release candidate for production
54
-        if (defined('ALLOW_NON_STABLE_RELEASE_ON_LIVE_SITE') && ALLOW_NON_STABLE_RELEASE_ON_LIVE_SITE) {
55
-            return;
56
-        }
57
-        // post release candidate warning
58
-        if ($this->request->isAdmin()) {
59
-            add_action('admin_notices', array($this, 'preProductionVersionAdminNotice'), -999);
60
-        } else {
61
-            add_action('shutdown', array($this, 'preProductionVersionWarningNotice'), 10);
62
-        }
63
-    }
38
+	/**
39
+	 * displays message on frontend of site notifying admin that EE has been temporarily placed into maintenance mode
40
+	 *
41
+	 * @return void
42
+	 */
43
+	public function displayPreProductionVersionWarning()
44
+	{
45
+		// skip AJAX requests
46
+		if ($this->request->isAjax()) {
47
+			return;
48
+		}
49
+		// skip stable releases
50
+		if (substr(EVENT_ESPRESSO_VERSION, -5) !== '.beta') {
51
+			return;
52
+		}
53
+		// site admin has authorized use of non-stable release candidate for production
54
+		if (defined('ALLOW_NON_STABLE_RELEASE_ON_LIVE_SITE') && ALLOW_NON_STABLE_RELEASE_ON_LIVE_SITE) {
55
+			return;
56
+		}
57
+		// post release candidate warning
58
+		if ($this->request->isAdmin()) {
59
+			add_action('admin_notices', array($this, 'preProductionVersionAdminNotice'), -999);
60
+		} else {
61
+			add_action('shutdown', array($this, 'preProductionVersionWarningNotice'), 10);
62
+		}
63
+	}
64 64
 
65 65
 
66
-    /**
67
-     * displays admin notice that current version of EE is not a stable release
68
-     *
69
-     * @return void
70
-     * @throws InvalidDataTypeException
71
-     */
72
-    public function preProductionVersionAdminNotice()
73
-    {
74
-        new PersistentAdminNotice(
75
-            'preProductionVersionAdminNotice_' . EVENT_ESPRESSO_VERSION,
76
-            $this->warningNotice()
77
-        );
78
-    }
66
+	/**
67
+	 * displays admin notice that current version of EE is not a stable release
68
+	 *
69
+	 * @return void
70
+	 * @throws InvalidDataTypeException
71
+	 */
72
+	public function preProductionVersionAdminNotice()
73
+	{
74
+		new PersistentAdminNotice(
75
+			'preProductionVersionAdminNotice_' . EVENT_ESPRESSO_VERSION,
76
+			$this->warningNotice()
77
+		);
78
+	}
79 79
 
80 80
 
81
-    /**
82
-     * displays message on frontend of site notifying admin that current version of EE is not a stable release
83
-     *
84
-     * @return void
85
-     */
86
-    public function preProductionVersionWarningNotice()
87
-    {
88
-        echo '<div id="ee-release-candidate-notice-dv" class="ee-really-important-notice-dv"><p>';
89
-        echo wp_kses($this->warningNotice(), AllowedTags::getAllowedTags());
90
-        echo '</p></div>';
91
-    }
81
+	/**
82
+	 * displays message on frontend of site notifying admin that current version of EE is not a stable release
83
+	 *
84
+	 * @return void
85
+	 */
86
+	public function preProductionVersionWarningNotice()
87
+	{
88
+		echo '<div id="ee-release-candidate-notice-dv" class="ee-really-important-notice-dv"><p>';
89
+		echo wp_kses($this->warningNotice(), AllowedTags::getAllowedTags());
90
+		echo '</p></div>';
91
+	}
92 92
 
93 93
 
94
-    /**
95
-     * @return string
96
-     */
97
-    private function warningNotice()
98
-    {
99
-        return sprintf(
100
-            esc_html__(
101
-                'This version of Event Espresso is for testing and/or evaluation purposes only. It is %1$snot%2$s considered a stable release and should therefore %1$snot%2$s be activated on a live or production website.',
102
-                'event_espresso'
103
-            ),
104
-            '<strong>',
105
-            '</strong>'
106
-        );
107
-    }
94
+	/**
95
+	 * @return string
96
+	 */
97
+	private function warningNotice()
98
+	{
99
+		return sprintf(
100
+			esc_html__(
101
+				'This version of Event Espresso is for testing and/or evaluation purposes only. It is %1$snot%2$s considered a stable release and should therefore %1$snot%2$s be activated on a live or production website.',
102
+				'event_espresso'
103
+			),
104
+			'<strong>',
105
+			'</strong>'
106
+		);
107
+	}
108 108
 }
Please login to merge, or discard this patch.
core/services/notices/AdminNotice.php 1 patch
Indentation   +111 added lines, -111 removed lines patch added patch discarded remove patch
@@ -14,115 +14,115 @@
 block discarded – undo
14 14
  */
15 15
 class AdminNotice
16 16
 {
17
-    const ERROR = 'notice-error';
18
-
19
-    const WARNING = 'notice-warning';
20
-
21
-    const SUCCESS = 'notice-success';
22
-
23
-    const INFORMATION = 'notice-info';
24
-
25
-    const DISMISSABLE = ' is-dismissible';
26
-
27
-    /**
28
-     * generic system notice to be converted into a WP admin notice
29
-     *
30
-     * @var NoticeInterface $notice
31
-     */
32
-    private $notice;
33
-
34
-
35
-    /**
36
-     * AdminNotice constructor.
37
-     *
38
-     * @param NoticeInterface $notice
39
-     * @param bool            $display_now
40
-     */
41
-    public function __construct(NoticeInterface $notice, $display_now = true)
42
-    {
43
-        $this->notice = $notice;
44
-        if (! did_action('admin_notices')) {
45
-            add_action('admin_notices', array($this, 'displayNotice'));
46
-        } elseif ($display_now) {
47
-            $this->displayNotice();
48
-        }
49
-    }
50
-
51
-
52
-    /**
53
-     * @return void
54
-     */
55
-    public function displayNotice()
56
-    {
57
-        echo wp_kses($this->getNotice(), AllowedTags::getAllowedTags());
58
-    }
59
-
60
-
61
-    /**
62
-     * produces something  like:
63
-     *  <div class="notice notice-success is-dismissible event-espresso-admin-notice">
64
-     *      <p>YOU DID IT!</p>
65
-     *      <button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this
66
-     *      notice.</span></button>
67
-     *  </div>
68
-     *
69
-     * @return string
70
-     */
71
-    public function getNotice()
72
-    {
73
-        return sprintf(
74
-            '<div class="notice %1$s%2$s event-espresso-admin-notice"><p>%3$s</p></div>',
75
-            $this->getType(),
76
-            $this->notice->isDismissible() ? AdminNotice::DISMISSABLE : '',
77
-            $this->getMessage()
78
-        );
79
-    }
80
-
81
-
82
-    /**
83
-     * @return string
84
-     */
85
-    private function getType()
86
-    {
87
-        switch ($this->notice->type()) {
88
-            case Notice::ERROR:
89
-                return AdminNotice::ERROR;
90
-            case Notice::ATTENTION:
91
-                return AdminNotice::WARNING;
92
-            case Notice::SUCCESS:
93
-                return AdminNotice::SUCCESS;
94
-            case Notice::INFORMATION:
95
-            default:
96
-                return AdminNotice::INFORMATION;
97
-        }
98
-    }
99
-
100
-
101
-    /**
102
-     * @return string
103
-     */
104
-    protected function getMessage()
105
-    {
106
-        $message = $this->notice->message();
107
-        if (WP_DEBUG && $this->getType() === AdminNotice::ERROR) {
108
-            $message .= '<br/><span class="tiny-text">' . $this->generateErrorCode() . '</span>';
109
-        }
110
-        return $message;
111
-    }
112
-
113
-
114
-    /**
115
-     * create error code from filepath, function name,
116
-     * and line number where notice was generated
117
-     *
118
-     * @return string
119
-     */
120
-    protected function generateErrorCode()
121
-    {
122
-        $file = explode('.', basename($this->notice->file()));
123
-        $error_code = ! empty($file[0]) ? $file[0] : '';
124
-        $error_code .= ! empty($error_code) ? ' - ' . $this->notice->func() : $this->notice->func();
125
-        $error_code .= ' - ' . $this->notice->line();
126
-        return $error_code;
127
-    }
17
+	const ERROR = 'notice-error';
18
+
19
+	const WARNING = 'notice-warning';
20
+
21
+	const SUCCESS = 'notice-success';
22
+
23
+	const INFORMATION = 'notice-info';
24
+
25
+	const DISMISSABLE = ' is-dismissible';
26
+
27
+	/**
28
+	 * generic system notice to be converted into a WP admin notice
29
+	 *
30
+	 * @var NoticeInterface $notice
31
+	 */
32
+	private $notice;
33
+
34
+
35
+	/**
36
+	 * AdminNotice constructor.
37
+	 *
38
+	 * @param NoticeInterface $notice
39
+	 * @param bool            $display_now
40
+	 */
41
+	public function __construct(NoticeInterface $notice, $display_now = true)
42
+	{
43
+		$this->notice = $notice;
44
+		if (! did_action('admin_notices')) {
45
+			add_action('admin_notices', array($this, 'displayNotice'));
46
+		} elseif ($display_now) {
47
+			$this->displayNotice();
48
+		}
49
+	}
50
+
51
+
52
+	/**
53
+	 * @return void
54
+	 */
55
+	public function displayNotice()
56
+	{
57
+		echo wp_kses($this->getNotice(), AllowedTags::getAllowedTags());
58
+	}
59
+
60
+
61
+	/**
62
+	 * produces something  like:
63
+	 *  <div class="notice notice-success is-dismissible event-espresso-admin-notice">
64
+	 *      <p>YOU DID IT!</p>
65
+	 *      <button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this
66
+	 *      notice.</span></button>
67
+	 *  </div>
68
+	 *
69
+	 * @return string
70
+	 */
71
+	public function getNotice()
72
+	{
73
+		return sprintf(
74
+			'<div class="notice %1$s%2$s event-espresso-admin-notice"><p>%3$s</p></div>',
75
+			$this->getType(),
76
+			$this->notice->isDismissible() ? AdminNotice::DISMISSABLE : '',
77
+			$this->getMessage()
78
+		);
79
+	}
80
+
81
+
82
+	/**
83
+	 * @return string
84
+	 */
85
+	private function getType()
86
+	{
87
+		switch ($this->notice->type()) {
88
+			case Notice::ERROR:
89
+				return AdminNotice::ERROR;
90
+			case Notice::ATTENTION:
91
+				return AdminNotice::WARNING;
92
+			case Notice::SUCCESS:
93
+				return AdminNotice::SUCCESS;
94
+			case Notice::INFORMATION:
95
+			default:
96
+				return AdminNotice::INFORMATION;
97
+		}
98
+	}
99
+
100
+
101
+	/**
102
+	 * @return string
103
+	 */
104
+	protected function getMessage()
105
+	{
106
+		$message = $this->notice->message();
107
+		if (WP_DEBUG && $this->getType() === AdminNotice::ERROR) {
108
+			$message .= '<br/><span class="tiny-text">' . $this->generateErrorCode() . '</span>';
109
+		}
110
+		return $message;
111
+	}
112
+
113
+
114
+	/**
115
+	 * create error code from filepath, function name,
116
+	 * and line number where notice was generated
117
+	 *
118
+	 * @return string
119
+	 */
120
+	protected function generateErrorCode()
121
+	{
122
+		$file = explode('.', basename($this->notice->file()));
123
+		$error_code = ! empty($file[0]) ? $file[0] : '';
124
+		$error_code .= ! empty($error_code) ? ' - ' . $this->notice->func() : $this->notice->func();
125
+		$error_code .= ' - ' . $this->notice->line();
126
+		return $error_code;
127
+	}
128 128
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Attendee.class.php 1 patch
Indentation   +768 added lines, -768 removed lines patch added patch discarded remove patch
@@ -24,772 +24,772 @@
 block discarded – undo
24 24
  */
25 25
 class EE_Attendee extends EE_CPT_Base implements EEI_Contact, EEI_Address, EEI_Admin_Links, EEI_Attendee
26 26
 {
27
-    /**
28
-     * Sets some dynamic defaults
29
-     *
30
-     * @param array  $fieldValues
31
-     * @param bool   $bydb
32
-     * @param string $timezone
33
-     * @param array  $date_formats
34
-     * @throws EE_Error
35
-     */
36
-    protected function __construct($fieldValues = null, $bydb = false, $timezone = null, $date_formats = array())
37
-    {
38
-        if (! isset($fieldValues['ATT_full_name'])) {
39
-            $fname = isset($fieldValues['ATT_fname']) ? $fieldValues['ATT_fname'] . ' ' : '';
40
-            $lname = isset($fieldValues['ATT_lname']) ? $fieldValues['ATT_lname'] : '';
41
-            $fieldValues['ATT_full_name'] = $fname . $lname;
42
-        }
43
-        if (! isset($fieldValues['ATT_slug'])) {
44
-            // $fieldValues['ATT_slug'] = sanitize_key(wp_generate_password(20));
45
-            $fieldValues['ATT_slug'] = sanitize_title($fieldValues['ATT_full_name']);
46
-        }
47
-        if (! isset($fieldValues['ATT_short_bio']) && isset($fieldValues['ATT_bio'])) {
48
-            $fieldValues['ATT_short_bio'] = substr($fieldValues['ATT_bio'], 0, 50);
49
-        }
50
-        parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
51
-    }
52
-
53
-
54
-    /**
55
-     * @param array  $props_n_values          incoming values
56
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
57
-     *                                        used.)
58
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
59
-     *                                        date_format and the second value is the time format
60
-     * @return EE_Attendee
61
-     * @throws EE_Error
62
-     */
63
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
64
-    {
65
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
66
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
67
-    }
68
-
69
-
70
-    /**
71
-     * @param array  $props_n_values  incoming values from the database
72
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
73
-     *                                the website will be used.
74
-     * @return EE_Attendee
75
-     */
76
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
77
-    {
78
-        return new self($props_n_values, true, $timezone);
79
-    }
80
-
81
-
82
-    /**
83
-     *        Set Attendee First Name
84
-     *
85
-     * @access        public
86
-     * @param string $fname
87
-     * @throws EE_Error
88
-     */
89
-    public function set_fname($fname = '')
90
-    {
91
-        $this->set('ATT_fname', $fname);
92
-    }
93
-
94
-
95
-    /**
96
-     *        Set Attendee Last Name
97
-     *
98
-     * @access        public
99
-     * @param string $lname
100
-     * @throws EE_Error
101
-     */
102
-    public function set_lname($lname = '')
103
-    {
104
-        $this->set('ATT_lname', $lname);
105
-    }
106
-
107
-
108
-    /**
109
-     *        Set Attendee Address
110
-     *
111
-     * @access        public
112
-     * @param string $address
113
-     * @throws EE_Error
114
-     */
115
-    public function set_address($address = '')
116
-    {
117
-        $this->set('ATT_address', $address);
118
-    }
119
-
120
-
121
-    /**
122
-     *        Set Attendee Address2
123
-     *
124
-     * @access        public
125
-     * @param        string $address2
126
-     * @throws EE_Error
127
-     */
128
-    public function set_address2($address2 = '')
129
-    {
130
-        $this->set('ATT_address2', $address2);
131
-    }
132
-
133
-
134
-    /**
135
-     *        Set Attendee City
136
-     *
137
-     * @access        public
138
-     * @param        string $city
139
-     * @throws EE_Error
140
-     */
141
-    public function set_city($city = '')
142
-    {
143
-        $this->set('ATT_city', $city);
144
-    }
145
-
146
-
147
-    /**
148
-     *        Set Attendee State ID
149
-     *
150
-     * @access        public
151
-     * @param        int $STA_ID
152
-     * @throws EE_Error
153
-     */
154
-    public function set_state($STA_ID = 0)
155
-    {
156
-        $this->set('STA_ID', $STA_ID);
157
-    }
158
-
159
-
160
-    /**
161
-     *        Set Attendee Country ISO Code
162
-     *
163
-     * @access        public
164
-     * @param        string $CNT_ISO
165
-     * @throws EE_Error
166
-     */
167
-    public function set_country($CNT_ISO = '')
168
-    {
169
-        $this->set('CNT_ISO', $CNT_ISO);
170
-    }
171
-
172
-
173
-    /**
174
-     *        Set Attendee Zip/Postal Code
175
-     *
176
-     * @access        public
177
-     * @param        string $zip
178
-     * @throws EE_Error
179
-     */
180
-    public function set_zip($zip = '')
181
-    {
182
-        $this->set('ATT_zip', $zip);
183
-    }
184
-
185
-
186
-    /**
187
-     *        Set Attendee Email Address
188
-     *
189
-     * @access        public
190
-     * @param        string $email
191
-     * @throws EE_Error
192
-     */
193
-    public function set_email($email = '')
194
-    {
195
-        $this->set('ATT_email', $email);
196
-    }
197
-
198
-
199
-    /**
200
-     *        Set Attendee Phone
201
-     *
202
-     * @access        public
203
-     * @param        string $phone
204
-     * @throws EE_Error
205
-     */
206
-    public function set_phone($phone = '')
207
-    {
208
-        $this->set('ATT_phone', $phone);
209
-    }
210
-
211
-
212
-    /**
213
-     *        set deleted
214
-     *
215
-     * @access        public
216
-     * @param        bool $ATT_deleted
217
-     * @throws EE_Error
218
-     */
219
-    public function set_deleted($ATT_deleted = false)
220
-    {
221
-        $this->set('ATT_deleted', $ATT_deleted);
222
-    }
223
-
224
-
225
-    /**
226
-     * Returns the value for the post_author id saved with the cpt
227
-     *
228
-     * @since 4.5.0
229
-     * @return int
230
-     * @throws EE_Error
231
-     */
232
-    public function wp_user()
233
-    {
234
-        return $this->get('ATT_author');
235
-    }
236
-
237
-
238
-    /**
239
-     *        get Attendee First Name
240
-     *
241
-     * @access        public
242
-     * @return string
243
-     * @throws EE_Error
244
-     */
245
-    public function fname()
246
-    {
247
-        return $this->get('ATT_fname');
248
-    }
249
-
250
-
251
-    /**
252
-     * echoes out the attendee's first name
253
-     *
254
-     * @return void
255
-     * @throws EE_Error
256
-     */
257
-    public function e_full_name()
258
-    {
259
-        echo esc_html($this->full_name());
260
-    }
261
-
262
-
263
-    /**
264
-     * Returns the first and last name concatenated together with a space.
265
-     *
266
-     * @param bool $apply_html_entities
267
-     * @return string
268
-     * @throws EE_Error
269
-     */
270
-    public function full_name($apply_html_entities = false)
271
-    {
272
-        $full_name = array(
273
-            $this->fname(),
274
-            $this->lname(),
275
-        );
276
-        $full_name = array_filter($full_name);
277
-        $full_name = implode(' ', $full_name);
278
-        return $apply_html_entities ? htmlentities($full_name, ENT_QUOTES, 'UTF-8') : $full_name;
279
-    }
280
-
281
-
282
-    /**
283
-     * This returns the value of the `ATT_full_name` field which is usually equivalent to calling `full_name()` unless
284
-     * the post_title field has been directly modified in the db for the post (espresso_attendees post type) for this
285
-     * attendee.
286
-     *
287
-     * @param bool $apply_html_entities
288
-     * @return string
289
-     * @throws EE_Error
290
-     */
291
-    public function ATT_full_name($apply_html_entities = false)
292
-    {
293
-        return $apply_html_entities
294
-            ? htmlentities($this->get('ATT_full_name'), ENT_QUOTES, 'UTF-8')
295
-            : $this->get('ATT_full_name');
296
-    }
297
-
298
-
299
-    /**
300
-     *        get Attendee Last Name
301
-     *
302
-     * @access        public
303
-     * @return string
304
-     * @throws EE_Error
305
-     */
306
-    public function lname()
307
-    {
308
-        return $this->get('ATT_lname');
309
-    }
310
-
311
-
312
-    /**
313
-     * get Attendee bio
314
-     *
315
-     * @access public
316
-     * @return string
317
-     * @throws EE_Error
318
-     */
319
-    public function bio()
320
-    {
321
-        return $this->get('ATT_bio');
322
-    }
323
-
324
-
325
-    /**
326
-     * get Attendee short bio
327
-     *
328
-     * @access public
329
-     * @return string
330
-     * @throws EE_Error
331
-     */
332
-    public function short_bio()
333
-    {
334
-        return $this->get('ATT_short_bio');
335
-    }
336
-
337
-
338
-    /**
339
-     * Gets the attendee's full address as an array so client code can decide hwo to display it
340
-     *
341
-     * @return array numerically indexed, with each part of the address that is known.
342
-     * Eg, if the user only responded to state and country,
343
-     * it would be array(0=>'Alabama',1=>'USA')
344
-     * @return array
345
-     * @throws EE_Error
346
-     */
347
-    public function full_address_as_array()
348
-    {
349
-        $full_address_array = array();
350
-        $initial_address_fields = array('ATT_address', 'ATT_address2', 'ATT_city',);
351
-        foreach ($initial_address_fields as $address_field_name) {
352
-            $address_fields_value = $this->get($address_field_name);
353
-            if (! empty($address_fields_value)) {
354
-                $full_address_array[] = $address_fields_value;
355
-            }
356
-        }
357
-        // now handle state and country
358
-        $state_obj = $this->state_obj();
359
-        if ($state_obj instanceof EE_State) {
360
-            $full_address_array[] = $state_obj->name();
361
-        }
362
-        $country_obj = $this->country_obj();
363
-        if ($country_obj instanceof EE_Country) {
364
-            $full_address_array[] = $country_obj->name();
365
-        }
366
-        // lastly get the xip
367
-        $zip_value = $this->zip();
368
-        if (! empty($zip_value)) {
369
-            $full_address_array[] = $zip_value;
370
-        }
371
-        return $full_address_array;
372
-    }
373
-
374
-
375
-    /**
376
-     *        get Attendee Address
377
-     *
378
-     * @return string
379
-     * @throws EE_Error
380
-     */
381
-    public function address()
382
-    {
383
-        return $this->get('ATT_address');
384
-    }
385
-
386
-
387
-    /**
388
-     *        get Attendee Address2
389
-     *
390
-     * @return string
391
-     * @throws EE_Error
392
-     */
393
-    public function address2()
394
-    {
395
-        return $this->get('ATT_address2');
396
-    }
397
-
398
-
399
-    /**
400
-     *        get Attendee City
401
-     *
402
-     * @return string
403
-     * @throws EE_Error
404
-     */
405
-    public function city()
406
-    {
407
-        return $this->get('ATT_city');
408
-    }
409
-
410
-
411
-    /**
412
-     *        get Attendee State ID
413
-     *
414
-     * @return string
415
-     * @throws EE_Error
416
-     */
417
-    public function state_ID()
418
-    {
419
-        return $this->get('STA_ID');
420
-    }
421
-
422
-
423
-    /**
424
-     * @return string
425
-     * @throws EE_Error
426
-     */
427
-    public function state_abbrev()
428
-    {
429
-        return $this->state_obj() instanceof EE_State ? $this->state_obj()->abbrev() : '';
430
-    }
431
-
432
-
433
-    /**
434
-     * Gets the state set to this attendee
435
-     *
436
-     * @return EE_State
437
-     * @throws EE_Error
438
-     */
439
-    public function state_obj()
440
-    {
441
-        return $this->get_first_related('State');
442
-    }
443
-
444
-
445
-    /**
446
-     * Returns the state's name, otherwise 'Unknown'
447
-     *
448
-     * @return string
449
-     * @throws EE_Error
450
-     */
451
-    public function state_name()
452
-    {
453
-        if ($this->state_obj()) {
454
-            return $this->state_obj()->name();
455
-        } else {
456
-            return '';
457
-        }
458
-    }
459
-
460
-
461
-    /**
462
-     * either displays the state abbreviation or the state name, as determined
463
-     * by the "FHEE__EEI_Address__state__use_abbreviation" filter.
464
-     * defaults to abbreviation
465
-     *
466
-     * @return string
467
-     * @throws EE_Error
468
-     */
469
-    public function state()
470
-    {
471
-        if (apply_filters('FHEE__EEI_Address__state__use_abbreviation', true, $this->state_obj())) {
472
-            return $this->state_abbrev();
473
-        }
474
-        return $this->state_name();
475
-    }
476
-
477
-
478
-    /**
479
-     *    get Attendee Country ISO Code
480
-     *
481
-     * @return string
482
-     * @throws EE_Error
483
-     */
484
-    public function country_ID()
485
-    {
486
-        return $this->get('CNT_ISO');
487
-    }
488
-
489
-
490
-    /**
491
-     * Gets country set for this attendee
492
-     *
493
-     * @return EE_Country
494
-     * @throws EE_Error
495
-     */
496
-    public function country_obj()
497
-    {
498
-        return $this->get_first_related('Country');
499
-    }
500
-
501
-
502
-    /**
503
-     * Returns the country's name if known, otherwise 'Unknown'
504
-     *
505
-     * @return string
506
-     * @throws EE_Error
507
-     */
508
-    public function country_name()
509
-    {
510
-        if ($this->country_obj()) {
511
-            return $this->country_obj()->name();
512
-        }
513
-        return '';
514
-    }
515
-
516
-
517
-    /**
518
-     * either displays the country ISO2 code or the country name, as determined
519
-     * by the "FHEE__EEI_Address__country__use_abbreviation" filter.
520
-     * defaults to abbreviation
521
-     *
522
-     * @return string
523
-     * @throws EE_Error
524
-     */
525
-    public function country()
526
-    {
527
-        if (apply_filters('FHEE__EEI_Address__country__use_abbreviation', true, $this->country_obj())) {
528
-            return $this->country_ID();
529
-        }
530
-        return $this->country_name();
531
-    }
532
-
533
-
534
-    /**
535
-     *        get Attendee Zip/Postal Code
536
-     *
537
-     * @return string
538
-     * @throws EE_Error
539
-     */
540
-    public function zip()
541
-    {
542
-        return $this->get('ATT_zip');
543
-    }
544
-
545
-
546
-    /**
547
-     *        get Attendee Email Address
548
-     *
549
-     * @return string
550
-     * @throws EE_Error
551
-     */
552
-    public function email()
553
-    {
554
-        return $this->get('ATT_email');
555
-    }
556
-
557
-
558
-    /**
559
-     *        get Attendee Phone #
560
-     *
561
-     * @return string
562
-     * @throws EE_Error
563
-     */
564
-    public function phone()
565
-    {
566
-        return $this->get('ATT_phone');
567
-    }
568
-
569
-
570
-    /**
571
-     *    get deleted
572
-     *
573
-     * @return        bool
574
-     * @throws EE_Error
575
-     */
576
-    public function deleted()
577
-    {
578
-        return $this->get('ATT_deleted');
579
-    }
580
-
581
-
582
-    /**
583
-     * Gets registrations of this attendee
584
-     *
585
-     * @param array $query_params
586
-     * @return EE_Registration[]
587
-     * @throws EE_Error
588
-     */
589
-    public function get_registrations($query_params = array())
590
-    {
591
-        return $this->get_many_related('Registration', $query_params);
592
-    }
593
-
594
-
595
-    /**
596
-     * Gets the most recent registration of this attendee
597
-     *
598
-     * @return EE_Registration
599
-     * @throws EE_Error
600
-     */
601
-    public function get_most_recent_registration()
602
-    {
603
-        return $this->get_first_related(
604
-            'Registration',
605
-            array('order_by' => array('REG_date' => 'DESC'))
606
-        ); // null, 'REG_date', 'DESC', '=', 'OBJECT_K');
607
-    }
608
-
609
-
610
-    /**
611
-     * Gets the most recent registration for this attend at this event
612
-     *
613
-     * @param int $event_id
614
-     * @return EE_Registration
615
-     * @throws EE_Error
616
-     */
617
-    public function get_most_recent_registration_for_event($event_id)
618
-    {
619
-        return $this->get_first_related(
620
-            'Registration',
621
-            array(array('EVT_ID' => $event_id), 'order_by' => array('REG_date' => 'DESC'))
622
-        );
623
-    }
624
-
625
-
626
-    /**
627
-     * returns any events attached to this attendee ($_Event property);
628
-     *
629
-     * @return array
630
-     * @throws EE_Error
631
-     */
632
-    public function events()
633
-    {
634
-        return $this->get_many_related('Event');
635
-    }
636
-
637
-
638
-    /**
639
-     * Gets the billing info array where keys match espresso_reg_page_billing_inputs(),
640
-     * and keys are their cleaned values. @see EE_Attendee::save_and_clean_billing_info_for_payment_method() which was
641
-     * used to save the billing info
642
-     *
643
-     * @param EE_Payment_Method $payment_method the _gateway_name property on the gateway class
644
-     * @return EE_Form_Section_Proper|null
645
-     * @throws EE_Error
646
-     */
647
-    public function billing_info_for_payment_method($payment_method)
648
-    {
649
-        $pm_type = $payment_method->type_obj();
650
-        if (! $pm_type instanceof EE_PMT_Base) {
651
-            return null;
652
-        }
653
-        $billing_info = $this->get_post_meta($this->get_billing_info_postmeta_name($payment_method), true);
654
-        if (! $billing_info) {
655
-            return null;
656
-        }
657
-        $billing_form = $pm_type->billing_form();
658
-        // double-check the form isn't totally hidden, in which case pretend there is no form
659
-        $form_totally_hidden = true;
660
-        foreach ($billing_form->inputs_in_subsections() as $input) {
661
-            if (! $input->get_display_strategy() instanceof EE_Hidden_Display_Strategy) {
662
-                $form_totally_hidden = false;
663
-                break;
664
-            }
665
-        }
666
-        if ($form_totally_hidden) {
667
-            return null;
668
-        }
669
-        if ($billing_form instanceof EE_Form_Section_Proper) {
670
-            $billing_form->receive_form_submission(array($billing_form->name() => $billing_info), false);
671
-        }
672
-
673
-        return $billing_form;
674
-    }
675
-
676
-
677
-    /**
678
-     * Gets the postmeta key that holds this attendee's billing info for the
679
-     * specified payment method
680
-     *
681
-     * @param EE_Payment_Method $payment_method
682
-     * @return string
683
-     * @throws EE_Error
684
-     */
685
-    public function get_billing_info_postmeta_name($payment_method)
686
-    {
687
-        if ($payment_method->type_obj() instanceof EE_PMT_Base) {
688
-            return 'billing_info_' . $payment_method->type_obj()->system_name();
689
-        }
690
-        return null;
691
-    }
692
-
693
-
694
-    /**
695
-     * Saves the billing info to the attendee. @see EE_Attendee::billing_info_for_payment_method() which is used to
696
-     * retrieve it
697
-     *
698
-     * @param EE_Billing_Attendee_Info_Form $billing_form
699
-     * @param EE_Payment_Method             $payment_method
700
-     * @return boolean
701
-     * @throws EE_Error
702
-     */
703
-    public function save_and_clean_billing_info_for_payment_method($billing_form, $payment_method)
704
-    {
705
-        if (! $billing_form instanceof EE_Billing_Attendee_Info_Form) {
706
-            EE_Error::add_error(esc_html__('Cannot save billing info because there is none.', 'event_espresso'));
707
-            return false;
708
-        }
709
-        $billing_form->clean_sensitive_data();
710
-        return update_post_meta(
711
-            $this->ID(),
712
-            $this->get_billing_info_postmeta_name($payment_method),
713
-            $billing_form->input_values(true)
714
-        );
715
-    }
716
-
717
-
718
-    /**
719
-     * Return the link to the admin details for the object.
720
-     *
721
-     * @return string
722
-     * @throws EE_Error
723
-     * @throws InvalidArgumentException
724
-     * @throws InvalidDataTypeException
725
-     * @throws InvalidInterfaceException
726
-     * @throws ReflectionException
727
-     */
728
-    public function get_admin_details_link()
729
-    {
730
-        return $this->get_admin_edit_link();
731
-    }
732
-
733
-
734
-    /**
735
-     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
736
-     *
737
-     * @return string
738
-     * @throws EE_Error
739
-     * @throws InvalidArgumentException
740
-     * @throws ReflectionException
741
-     * @throws InvalidDataTypeException
742
-     * @throws InvalidInterfaceException
743
-     */
744
-    public function get_admin_edit_link()
745
-    {
746
-        EE_Registry::instance()->load_helper('URL');
747
-        return EEH_URL::add_query_args_and_nonce(
748
-            array(
749
-                'page'   => 'espresso_registrations',
750
-                'action' => 'edit_attendee',
751
-                'post'   => $this->ID(),
752
-            ),
753
-            admin_url('admin.php')
754
-        );
755
-    }
756
-
757
-
758
-    /**
759
-     * Returns the link to a settings page for the object.
760
-     *
761
-     * @return string
762
-     * @throws EE_Error
763
-     * @throws InvalidArgumentException
764
-     * @throws InvalidDataTypeException
765
-     * @throws InvalidInterfaceException
766
-     * @throws ReflectionException
767
-     */
768
-    public function get_admin_settings_link()
769
-    {
770
-        return $this->get_admin_edit_link();
771
-    }
772
-
773
-
774
-    /**
775
-     * Returns the link to the "overview" for the object (typically the "list table" view).
776
-     *
777
-     * @return string
778
-     * @throws EE_Error
779
-     * @throws InvalidArgumentException
780
-     * @throws ReflectionException
781
-     * @throws InvalidDataTypeException
782
-     * @throws InvalidInterfaceException
783
-     */
784
-    public function get_admin_overview_link()
785
-    {
786
-        EE_Registry::instance()->load_helper('URL');
787
-        return EEH_URL::add_query_args_and_nonce(
788
-            array(
789
-                'page'   => 'espresso_registrations',
790
-                'action' => 'contact_list',
791
-            ),
792
-            admin_url('admin.php')
793
-        );
794
-    }
27
+	/**
28
+	 * Sets some dynamic defaults
29
+	 *
30
+	 * @param array  $fieldValues
31
+	 * @param bool   $bydb
32
+	 * @param string $timezone
33
+	 * @param array  $date_formats
34
+	 * @throws EE_Error
35
+	 */
36
+	protected function __construct($fieldValues = null, $bydb = false, $timezone = null, $date_formats = array())
37
+	{
38
+		if (! isset($fieldValues['ATT_full_name'])) {
39
+			$fname = isset($fieldValues['ATT_fname']) ? $fieldValues['ATT_fname'] . ' ' : '';
40
+			$lname = isset($fieldValues['ATT_lname']) ? $fieldValues['ATT_lname'] : '';
41
+			$fieldValues['ATT_full_name'] = $fname . $lname;
42
+		}
43
+		if (! isset($fieldValues['ATT_slug'])) {
44
+			// $fieldValues['ATT_slug'] = sanitize_key(wp_generate_password(20));
45
+			$fieldValues['ATT_slug'] = sanitize_title($fieldValues['ATT_full_name']);
46
+		}
47
+		if (! isset($fieldValues['ATT_short_bio']) && isset($fieldValues['ATT_bio'])) {
48
+			$fieldValues['ATT_short_bio'] = substr($fieldValues['ATT_bio'], 0, 50);
49
+		}
50
+		parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
51
+	}
52
+
53
+
54
+	/**
55
+	 * @param array  $props_n_values          incoming values
56
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
57
+	 *                                        used.)
58
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
59
+	 *                                        date_format and the second value is the time format
60
+	 * @return EE_Attendee
61
+	 * @throws EE_Error
62
+	 */
63
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
64
+	{
65
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
66
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
67
+	}
68
+
69
+
70
+	/**
71
+	 * @param array  $props_n_values  incoming values from the database
72
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
73
+	 *                                the website will be used.
74
+	 * @return EE_Attendee
75
+	 */
76
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
77
+	{
78
+		return new self($props_n_values, true, $timezone);
79
+	}
80
+
81
+
82
+	/**
83
+	 *        Set Attendee First Name
84
+	 *
85
+	 * @access        public
86
+	 * @param string $fname
87
+	 * @throws EE_Error
88
+	 */
89
+	public function set_fname($fname = '')
90
+	{
91
+		$this->set('ATT_fname', $fname);
92
+	}
93
+
94
+
95
+	/**
96
+	 *        Set Attendee Last Name
97
+	 *
98
+	 * @access        public
99
+	 * @param string $lname
100
+	 * @throws EE_Error
101
+	 */
102
+	public function set_lname($lname = '')
103
+	{
104
+		$this->set('ATT_lname', $lname);
105
+	}
106
+
107
+
108
+	/**
109
+	 *        Set Attendee Address
110
+	 *
111
+	 * @access        public
112
+	 * @param string $address
113
+	 * @throws EE_Error
114
+	 */
115
+	public function set_address($address = '')
116
+	{
117
+		$this->set('ATT_address', $address);
118
+	}
119
+
120
+
121
+	/**
122
+	 *        Set Attendee Address2
123
+	 *
124
+	 * @access        public
125
+	 * @param        string $address2
126
+	 * @throws EE_Error
127
+	 */
128
+	public function set_address2($address2 = '')
129
+	{
130
+		$this->set('ATT_address2', $address2);
131
+	}
132
+
133
+
134
+	/**
135
+	 *        Set Attendee City
136
+	 *
137
+	 * @access        public
138
+	 * @param        string $city
139
+	 * @throws EE_Error
140
+	 */
141
+	public function set_city($city = '')
142
+	{
143
+		$this->set('ATT_city', $city);
144
+	}
145
+
146
+
147
+	/**
148
+	 *        Set Attendee State ID
149
+	 *
150
+	 * @access        public
151
+	 * @param        int $STA_ID
152
+	 * @throws EE_Error
153
+	 */
154
+	public function set_state($STA_ID = 0)
155
+	{
156
+		$this->set('STA_ID', $STA_ID);
157
+	}
158
+
159
+
160
+	/**
161
+	 *        Set Attendee Country ISO Code
162
+	 *
163
+	 * @access        public
164
+	 * @param        string $CNT_ISO
165
+	 * @throws EE_Error
166
+	 */
167
+	public function set_country($CNT_ISO = '')
168
+	{
169
+		$this->set('CNT_ISO', $CNT_ISO);
170
+	}
171
+
172
+
173
+	/**
174
+	 *        Set Attendee Zip/Postal Code
175
+	 *
176
+	 * @access        public
177
+	 * @param        string $zip
178
+	 * @throws EE_Error
179
+	 */
180
+	public function set_zip($zip = '')
181
+	{
182
+		$this->set('ATT_zip', $zip);
183
+	}
184
+
185
+
186
+	/**
187
+	 *        Set Attendee Email Address
188
+	 *
189
+	 * @access        public
190
+	 * @param        string $email
191
+	 * @throws EE_Error
192
+	 */
193
+	public function set_email($email = '')
194
+	{
195
+		$this->set('ATT_email', $email);
196
+	}
197
+
198
+
199
+	/**
200
+	 *        Set Attendee Phone
201
+	 *
202
+	 * @access        public
203
+	 * @param        string $phone
204
+	 * @throws EE_Error
205
+	 */
206
+	public function set_phone($phone = '')
207
+	{
208
+		$this->set('ATT_phone', $phone);
209
+	}
210
+
211
+
212
+	/**
213
+	 *        set deleted
214
+	 *
215
+	 * @access        public
216
+	 * @param        bool $ATT_deleted
217
+	 * @throws EE_Error
218
+	 */
219
+	public function set_deleted($ATT_deleted = false)
220
+	{
221
+		$this->set('ATT_deleted', $ATT_deleted);
222
+	}
223
+
224
+
225
+	/**
226
+	 * Returns the value for the post_author id saved with the cpt
227
+	 *
228
+	 * @since 4.5.0
229
+	 * @return int
230
+	 * @throws EE_Error
231
+	 */
232
+	public function wp_user()
233
+	{
234
+		return $this->get('ATT_author');
235
+	}
236
+
237
+
238
+	/**
239
+	 *        get Attendee First Name
240
+	 *
241
+	 * @access        public
242
+	 * @return string
243
+	 * @throws EE_Error
244
+	 */
245
+	public function fname()
246
+	{
247
+		return $this->get('ATT_fname');
248
+	}
249
+
250
+
251
+	/**
252
+	 * echoes out the attendee's first name
253
+	 *
254
+	 * @return void
255
+	 * @throws EE_Error
256
+	 */
257
+	public function e_full_name()
258
+	{
259
+		echo esc_html($this->full_name());
260
+	}
261
+
262
+
263
+	/**
264
+	 * Returns the first and last name concatenated together with a space.
265
+	 *
266
+	 * @param bool $apply_html_entities
267
+	 * @return string
268
+	 * @throws EE_Error
269
+	 */
270
+	public function full_name($apply_html_entities = false)
271
+	{
272
+		$full_name = array(
273
+			$this->fname(),
274
+			$this->lname(),
275
+		);
276
+		$full_name = array_filter($full_name);
277
+		$full_name = implode(' ', $full_name);
278
+		return $apply_html_entities ? htmlentities($full_name, ENT_QUOTES, 'UTF-8') : $full_name;
279
+	}
280
+
281
+
282
+	/**
283
+	 * This returns the value of the `ATT_full_name` field which is usually equivalent to calling `full_name()` unless
284
+	 * the post_title field has been directly modified in the db for the post (espresso_attendees post type) for this
285
+	 * attendee.
286
+	 *
287
+	 * @param bool $apply_html_entities
288
+	 * @return string
289
+	 * @throws EE_Error
290
+	 */
291
+	public function ATT_full_name($apply_html_entities = false)
292
+	{
293
+		return $apply_html_entities
294
+			? htmlentities($this->get('ATT_full_name'), ENT_QUOTES, 'UTF-8')
295
+			: $this->get('ATT_full_name');
296
+	}
297
+
298
+
299
+	/**
300
+	 *        get Attendee Last Name
301
+	 *
302
+	 * @access        public
303
+	 * @return string
304
+	 * @throws EE_Error
305
+	 */
306
+	public function lname()
307
+	{
308
+		return $this->get('ATT_lname');
309
+	}
310
+
311
+
312
+	/**
313
+	 * get Attendee bio
314
+	 *
315
+	 * @access public
316
+	 * @return string
317
+	 * @throws EE_Error
318
+	 */
319
+	public function bio()
320
+	{
321
+		return $this->get('ATT_bio');
322
+	}
323
+
324
+
325
+	/**
326
+	 * get Attendee short bio
327
+	 *
328
+	 * @access public
329
+	 * @return string
330
+	 * @throws EE_Error
331
+	 */
332
+	public function short_bio()
333
+	{
334
+		return $this->get('ATT_short_bio');
335
+	}
336
+
337
+
338
+	/**
339
+	 * Gets the attendee's full address as an array so client code can decide hwo to display it
340
+	 *
341
+	 * @return array numerically indexed, with each part of the address that is known.
342
+	 * Eg, if the user only responded to state and country,
343
+	 * it would be array(0=>'Alabama',1=>'USA')
344
+	 * @return array
345
+	 * @throws EE_Error
346
+	 */
347
+	public function full_address_as_array()
348
+	{
349
+		$full_address_array = array();
350
+		$initial_address_fields = array('ATT_address', 'ATT_address2', 'ATT_city',);
351
+		foreach ($initial_address_fields as $address_field_name) {
352
+			$address_fields_value = $this->get($address_field_name);
353
+			if (! empty($address_fields_value)) {
354
+				$full_address_array[] = $address_fields_value;
355
+			}
356
+		}
357
+		// now handle state and country
358
+		$state_obj = $this->state_obj();
359
+		if ($state_obj instanceof EE_State) {
360
+			$full_address_array[] = $state_obj->name();
361
+		}
362
+		$country_obj = $this->country_obj();
363
+		if ($country_obj instanceof EE_Country) {
364
+			$full_address_array[] = $country_obj->name();
365
+		}
366
+		// lastly get the xip
367
+		$zip_value = $this->zip();
368
+		if (! empty($zip_value)) {
369
+			$full_address_array[] = $zip_value;
370
+		}
371
+		return $full_address_array;
372
+	}
373
+
374
+
375
+	/**
376
+	 *        get Attendee Address
377
+	 *
378
+	 * @return string
379
+	 * @throws EE_Error
380
+	 */
381
+	public function address()
382
+	{
383
+		return $this->get('ATT_address');
384
+	}
385
+
386
+
387
+	/**
388
+	 *        get Attendee Address2
389
+	 *
390
+	 * @return string
391
+	 * @throws EE_Error
392
+	 */
393
+	public function address2()
394
+	{
395
+		return $this->get('ATT_address2');
396
+	}
397
+
398
+
399
+	/**
400
+	 *        get Attendee City
401
+	 *
402
+	 * @return string
403
+	 * @throws EE_Error
404
+	 */
405
+	public function city()
406
+	{
407
+		return $this->get('ATT_city');
408
+	}
409
+
410
+
411
+	/**
412
+	 *        get Attendee State ID
413
+	 *
414
+	 * @return string
415
+	 * @throws EE_Error
416
+	 */
417
+	public function state_ID()
418
+	{
419
+		return $this->get('STA_ID');
420
+	}
421
+
422
+
423
+	/**
424
+	 * @return string
425
+	 * @throws EE_Error
426
+	 */
427
+	public function state_abbrev()
428
+	{
429
+		return $this->state_obj() instanceof EE_State ? $this->state_obj()->abbrev() : '';
430
+	}
431
+
432
+
433
+	/**
434
+	 * Gets the state set to this attendee
435
+	 *
436
+	 * @return EE_State
437
+	 * @throws EE_Error
438
+	 */
439
+	public function state_obj()
440
+	{
441
+		return $this->get_first_related('State');
442
+	}
443
+
444
+
445
+	/**
446
+	 * Returns the state's name, otherwise 'Unknown'
447
+	 *
448
+	 * @return string
449
+	 * @throws EE_Error
450
+	 */
451
+	public function state_name()
452
+	{
453
+		if ($this->state_obj()) {
454
+			return $this->state_obj()->name();
455
+		} else {
456
+			return '';
457
+		}
458
+	}
459
+
460
+
461
+	/**
462
+	 * either displays the state abbreviation or the state name, as determined
463
+	 * by the "FHEE__EEI_Address__state__use_abbreviation" filter.
464
+	 * defaults to abbreviation
465
+	 *
466
+	 * @return string
467
+	 * @throws EE_Error
468
+	 */
469
+	public function state()
470
+	{
471
+		if (apply_filters('FHEE__EEI_Address__state__use_abbreviation', true, $this->state_obj())) {
472
+			return $this->state_abbrev();
473
+		}
474
+		return $this->state_name();
475
+	}
476
+
477
+
478
+	/**
479
+	 *    get Attendee Country ISO Code
480
+	 *
481
+	 * @return string
482
+	 * @throws EE_Error
483
+	 */
484
+	public function country_ID()
485
+	{
486
+		return $this->get('CNT_ISO');
487
+	}
488
+
489
+
490
+	/**
491
+	 * Gets country set for this attendee
492
+	 *
493
+	 * @return EE_Country
494
+	 * @throws EE_Error
495
+	 */
496
+	public function country_obj()
497
+	{
498
+		return $this->get_first_related('Country');
499
+	}
500
+
501
+
502
+	/**
503
+	 * Returns the country's name if known, otherwise 'Unknown'
504
+	 *
505
+	 * @return string
506
+	 * @throws EE_Error
507
+	 */
508
+	public function country_name()
509
+	{
510
+		if ($this->country_obj()) {
511
+			return $this->country_obj()->name();
512
+		}
513
+		return '';
514
+	}
515
+
516
+
517
+	/**
518
+	 * either displays the country ISO2 code or the country name, as determined
519
+	 * by the "FHEE__EEI_Address__country__use_abbreviation" filter.
520
+	 * defaults to abbreviation
521
+	 *
522
+	 * @return string
523
+	 * @throws EE_Error
524
+	 */
525
+	public function country()
526
+	{
527
+		if (apply_filters('FHEE__EEI_Address__country__use_abbreviation', true, $this->country_obj())) {
528
+			return $this->country_ID();
529
+		}
530
+		return $this->country_name();
531
+	}
532
+
533
+
534
+	/**
535
+	 *        get Attendee Zip/Postal Code
536
+	 *
537
+	 * @return string
538
+	 * @throws EE_Error
539
+	 */
540
+	public function zip()
541
+	{
542
+		return $this->get('ATT_zip');
543
+	}
544
+
545
+
546
+	/**
547
+	 *        get Attendee Email Address
548
+	 *
549
+	 * @return string
550
+	 * @throws EE_Error
551
+	 */
552
+	public function email()
553
+	{
554
+		return $this->get('ATT_email');
555
+	}
556
+
557
+
558
+	/**
559
+	 *        get Attendee Phone #
560
+	 *
561
+	 * @return string
562
+	 * @throws EE_Error
563
+	 */
564
+	public function phone()
565
+	{
566
+		return $this->get('ATT_phone');
567
+	}
568
+
569
+
570
+	/**
571
+	 *    get deleted
572
+	 *
573
+	 * @return        bool
574
+	 * @throws EE_Error
575
+	 */
576
+	public function deleted()
577
+	{
578
+		return $this->get('ATT_deleted');
579
+	}
580
+
581
+
582
+	/**
583
+	 * Gets registrations of this attendee
584
+	 *
585
+	 * @param array $query_params
586
+	 * @return EE_Registration[]
587
+	 * @throws EE_Error
588
+	 */
589
+	public function get_registrations($query_params = array())
590
+	{
591
+		return $this->get_many_related('Registration', $query_params);
592
+	}
593
+
594
+
595
+	/**
596
+	 * Gets the most recent registration of this attendee
597
+	 *
598
+	 * @return EE_Registration
599
+	 * @throws EE_Error
600
+	 */
601
+	public function get_most_recent_registration()
602
+	{
603
+		return $this->get_first_related(
604
+			'Registration',
605
+			array('order_by' => array('REG_date' => 'DESC'))
606
+		); // null, 'REG_date', 'DESC', '=', 'OBJECT_K');
607
+	}
608
+
609
+
610
+	/**
611
+	 * Gets the most recent registration for this attend at this event
612
+	 *
613
+	 * @param int $event_id
614
+	 * @return EE_Registration
615
+	 * @throws EE_Error
616
+	 */
617
+	public function get_most_recent_registration_for_event($event_id)
618
+	{
619
+		return $this->get_first_related(
620
+			'Registration',
621
+			array(array('EVT_ID' => $event_id), 'order_by' => array('REG_date' => 'DESC'))
622
+		);
623
+	}
624
+
625
+
626
+	/**
627
+	 * returns any events attached to this attendee ($_Event property);
628
+	 *
629
+	 * @return array
630
+	 * @throws EE_Error
631
+	 */
632
+	public function events()
633
+	{
634
+		return $this->get_many_related('Event');
635
+	}
636
+
637
+
638
+	/**
639
+	 * Gets the billing info array where keys match espresso_reg_page_billing_inputs(),
640
+	 * and keys are their cleaned values. @see EE_Attendee::save_and_clean_billing_info_for_payment_method() which was
641
+	 * used to save the billing info
642
+	 *
643
+	 * @param EE_Payment_Method $payment_method the _gateway_name property on the gateway class
644
+	 * @return EE_Form_Section_Proper|null
645
+	 * @throws EE_Error
646
+	 */
647
+	public function billing_info_for_payment_method($payment_method)
648
+	{
649
+		$pm_type = $payment_method->type_obj();
650
+		if (! $pm_type instanceof EE_PMT_Base) {
651
+			return null;
652
+		}
653
+		$billing_info = $this->get_post_meta($this->get_billing_info_postmeta_name($payment_method), true);
654
+		if (! $billing_info) {
655
+			return null;
656
+		}
657
+		$billing_form = $pm_type->billing_form();
658
+		// double-check the form isn't totally hidden, in which case pretend there is no form
659
+		$form_totally_hidden = true;
660
+		foreach ($billing_form->inputs_in_subsections() as $input) {
661
+			if (! $input->get_display_strategy() instanceof EE_Hidden_Display_Strategy) {
662
+				$form_totally_hidden = false;
663
+				break;
664
+			}
665
+		}
666
+		if ($form_totally_hidden) {
667
+			return null;
668
+		}
669
+		if ($billing_form instanceof EE_Form_Section_Proper) {
670
+			$billing_form->receive_form_submission(array($billing_form->name() => $billing_info), false);
671
+		}
672
+
673
+		return $billing_form;
674
+	}
675
+
676
+
677
+	/**
678
+	 * Gets the postmeta key that holds this attendee's billing info for the
679
+	 * specified payment method
680
+	 *
681
+	 * @param EE_Payment_Method $payment_method
682
+	 * @return string
683
+	 * @throws EE_Error
684
+	 */
685
+	public function get_billing_info_postmeta_name($payment_method)
686
+	{
687
+		if ($payment_method->type_obj() instanceof EE_PMT_Base) {
688
+			return 'billing_info_' . $payment_method->type_obj()->system_name();
689
+		}
690
+		return null;
691
+	}
692
+
693
+
694
+	/**
695
+	 * Saves the billing info to the attendee. @see EE_Attendee::billing_info_for_payment_method() which is used to
696
+	 * retrieve it
697
+	 *
698
+	 * @param EE_Billing_Attendee_Info_Form $billing_form
699
+	 * @param EE_Payment_Method             $payment_method
700
+	 * @return boolean
701
+	 * @throws EE_Error
702
+	 */
703
+	public function save_and_clean_billing_info_for_payment_method($billing_form, $payment_method)
704
+	{
705
+		if (! $billing_form instanceof EE_Billing_Attendee_Info_Form) {
706
+			EE_Error::add_error(esc_html__('Cannot save billing info because there is none.', 'event_espresso'));
707
+			return false;
708
+		}
709
+		$billing_form->clean_sensitive_data();
710
+		return update_post_meta(
711
+			$this->ID(),
712
+			$this->get_billing_info_postmeta_name($payment_method),
713
+			$billing_form->input_values(true)
714
+		);
715
+	}
716
+
717
+
718
+	/**
719
+	 * Return the link to the admin details for the object.
720
+	 *
721
+	 * @return string
722
+	 * @throws EE_Error
723
+	 * @throws InvalidArgumentException
724
+	 * @throws InvalidDataTypeException
725
+	 * @throws InvalidInterfaceException
726
+	 * @throws ReflectionException
727
+	 */
728
+	public function get_admin_details_link()
729
+	{
730
+		return $this->get_admin_edit_link();
731
+	}
732
+
733
+
734
+	/**
735
+	 * Returns the link to the editor for the object.  Sometimes this is the same as the details.
736
+	 *
737
+	 * @return string
738
+	 * @throws EE_Error
739
+	 * @throws InvalidArgumentException
740
+	 * @throws ReflectionException
741
+	 * @throws InvalidDataTypeException
742
+	 * @throws InvalidInterfaceException
743
+	 */
744
+	public function get_admin_edit_link()
745
+	{
746
+		EE_Registry::instance()->load_helper('URL');
747
+		return EEH_URL::add_query_args_and_nonce(
748
+			array(
749
+				'page'   => 'espresso_registrations',
750
+				'action' => 'edit_attendee',
751
+				'post'   => $this->ID(),
752
+			),
753
+			admin_url('admin.php')
754
+		);
755
+	}
756
+
757
+
758
+	/**
759
+	 * Returns the link to a settings page for the object.
760
+	 *
761
+	 * @return string
762
+	 * @throws EE_Error
763
+	 * @throws InvalidArgumentException
764
+	 * @throws InvalidDataTypeException
765
+	 * @throws InvalidInterfaceException
766
+	 * @throws ReflectionException
767
+	 */
768
+	public function get_admin_settings_link()
769
+	{
770
+		return $this->get_admin_edit_link();
771
+	}
772
+
773
+
774
+	/**
775
+	 * Returns the link to the "overview" for the object (typically the "list table" view).
776
+	 *
777
+	 * @return string
778
+	 * @throws EE_Error
779
+	 * @throws InvalidArgumentException
780
+	 * @throws ReflectionException
781
+	 * @throws InvalidDataTypeException
782
+	 * @throws InvalidInterfaceException
783
+	 */
784
+	public function get_admin_overview_link()
785
+	{
786
+		EE_Registry::instance()->load_helper('URL');
787
+		return EEH_URL::add_query_args_and_nonce(
788
+			array(
789
+				'page'   => 'espresso_registrations',
790
+				'action' => 'contact_list',
791
+			),
792
+			admin_url('admin.php')
793
+		);
794
+	}
795 795
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Base_Class.class.php 1 patch
Indentation   +3368 added lines, -3368 removed lines patch added patch discarded remove patch
@@ -13,3384 +13,3384 @@
 block discarded – undo
13 13
  */
14 14
 abstract class EE_Base_Class
15 15
 {
16
-    /**
17
-     * This is an array of the original properties and values provided during construction
18
-     * of this model object. (keys are model field names, values are their values).
19
-     * This list is important to remember so that when we are merging data from the db, we know
20
-     * which values to override and which to not override.
21
-     *
22
-     * @var array
23
-     */
24
-    protected $_props_n_values_provided_in_constructor;
25
-
26
-    /**
27
-     * Timezone
28
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
29
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
30
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
31
-     * access to it.
32
-     *
33
-     * @var string
34
-     */
35
-    protected $_timezone;
36
-
37
-    /**
38
-     * date format
39
-     * pattern or format for displaying dates
40
-     *
41
-     * @var string $_dt_frmt
42
-     */
43
-    protected $_dt_frmt;
44
-
45
-    /**
46
-     * time format
47
-     * pattern or format for displaying time
48
-     *
49
-     * @var string $_tm_frmt
50
-     */
51
-    protected $_tm_frmt;
52
-
53
-    /**
54
-     * This property is for holding a cached array of object properties indexed by property name as the key.
55
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
56
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
57
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
58
-     *
59
-     * @var array
60
-     */
61
-    protected $_cached_properties = array();
62
-
63
-    /**
64
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
65
-     * single
66
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
67
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
68
-     * all others have an array)
69
-     *
70
-     * @var array
71
-     */
72
-    protected $_model_relations = array();
73
-
74
-    /**
75
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
76
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
77
-     *
78
-     * @var array
79
-     */
80
-    protected $_fields = array();
81
-
82
-    /**
83
-     * @var boolean indicating whether or not this model object is intended to ever be saved
84
-     * For example, we might create model objects intended to only be used for the duration
85
-     * of this request and to be thrown away, and if they were accidentally saved
86
-     * it would be a bug.
87
-     */
88
-    protected $_allow_persist = true;
89
-
90
-    /**
91
-     * @var boolean indicating whether or not this model object's properties have changed since construction
92
-     */
93
-    protected $_has_changes = false;
94
-
95
-    /**
96
-     * @var EEM_Base
97
-     */
98
-    protected $_model;
99
-
100
-    /**
101
-     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
102
-     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
103
-     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
104
-     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
105
-     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
106
-     * array as:
107
-     * array(
108
-     *  'Registration_Count' => 24
109
-     * );
110
-     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
111
-     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
112
-     * info)
113
-     *
114
-     * @var array
115
-     */
116
-    protected $custom_selection_results = array();
117
-
118
-
119
-    /**
120
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
121
-     * play nice
122
-     *
123
-     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
124
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
125
-     *                                                         TXN_amount, QST_name, etc) and values are their values
126
-     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
127
-     *                                                         corresponding db model or not.
128
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
129
-     *                                                         be in when instantiating a EE_Base_Class object.
130
-     * @param array   $date_formats                            An array of date formats to set on construct where first
131
-     *                                                         value is the date_format and second value is the time
132
-     *                                                         format.
133
-     * @throws InvalidArgumentException
134
-     * @throws InvalidInterfaceException
135
-     * @throws InvalidDataTypeException
136
-     * @throws EE_Error
137
-     * @throws ReflectionException
138
-     */
139
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
140
-    {
141
-        $className = get_class($this);
142
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
143
-        $model = $this->get_model();
144
-        $model_fields = $model->field_settings(false);
145
-        // ensure $fieldValues is an array
146
-        $fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
147
-        // verify client code has not passed any invalid field names
148
-        foreach ($fieldValues as $field_name => $field_value) {
149
-            if (! isset($model_fields[ $field_name ])) {
150
-                throw new EE_Error(
151
-                    sprintf(
152
-                        esc_html__(
153
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
154
-                            'event_espresso'
155
-                        ),
156
-                        $field_name,
157
-                        get_class($this),
158
-                        implode(', ', array_keys($model_fields))
159
-                    )
160
-                );
161
-            }
162
-        }
163
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
-        if (! empty($date_formats) && is_array($date_formats)) {
165
-            [$this->_dt_frmt, $this->_tm_frmt] = $date_formats;
166
-        } else {
167
-            // set default formats for date and time
168
-            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
169
-            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
170
-        }
171
-        // if db model is instantiating
172
-        if ($bydb) {
173
-            // client code has indicated these field values are from the database
174
-            foreach ($model_fields as $fieldName => $field) {
175
-                $this->set_from_db(
176
-                    $fieldName,
177
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
-                );
179
-            }
180
-        } else {
181
-            // we're constructing a brand
182
-            // new instance of the model object. Generally, this means we'll need to do more field validation
183
-            foreach ($model_fields as $fieldName => $field) {
184
-                $this->set(
185
-                    $fieldName,
186
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
-                    true
188
-                );
189
-            }
190
-        }
191
-        // remember what values were passed to this constructor
192
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
193
-        // remember in entity mapper
194
-        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
195
-            $model->add_to_entity_map($this);
196
-        }
197
-        // setup all the relations
198
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
199
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
-                $this->_model_relations[ $relation_name ] = null;
201
-            } else {
202
-                $this->_model_relations[ $relation_name ] = array();
203
-            }
204
-        }
205
-        /**
206
-         * Action done at the end of each model object construction
207
-         *
208
-         * @param EE_Base_Class $this the model object just created
209
-         */
210
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
211
-    }
212
-
213
-
214
-    /**
215
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
216
-     *
217
-     * @return boolean
218
-     */
219
-    public function allow_persist()
220
-    {
221
-        return $this->_allow_persist;
222
-    }
223
-
224
-
225
-    /**
226
-     * Sets whether or not this model object should be allowed to be saved to the DB.
227
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
228
-     * you got new information that somehow made you change your mind.
229
-     *
230
-     * @param boolean $allow_persist
231
-     * @return boolean
232
-     */
233
-    public function set_allow_persist($allow_persist)
234
-    {
235
-        return $this->_allow_persist = $allow_persist;
236
-    }
237
-
238
-
239
-    /**
240
-     * Gets the field's original value when this object was constructed during this request.
241
-     * This can be helpful when determining if a model object has changed or not
242
-     *
243
-     * @param string $field_name
244
-     * @return mixed|null
245
-     * @throws ReflectionException
246
-     * @throws InvalidArgumentException
247
-     * @throws InvalidInterfaceException
248
-     * @throws InvalidDataTypeException
249
-     * @throws EE_Error
250
-     */
251
-    public function get_original($field_name)
252
-    {
253
-        if (
254
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
255
-            && $field_settings = $this->get_model()->field_settings_for($field_name)
256
-        ) {
257
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
258
-        }
259
-        return null;
260
-    }
261
-
262
-
263
-    /**
264
-     * @param EE_Base_Class $obj
265
-     * @return string
266
-     */
267
-    public function get_class($obj)
268
-    {
269
-        return get_class($obj);
270
-    }
271
-
272
-
273
-    /**
274
-     * Overrides parent because parent expects old models.
275
-     * This also doesn't do any validation, and won't work for serialized arrays
276
-     *
277
-     * @param    string $field_name
278
-     * @param    mixed  $field_value
279
-     * @param bool      $use_default
280
-     * @throws InvalidArgumentException
281
-     * @throws InvalidInterfaceException
282
-     * @throws InvalidDataTypeException
283
-     * @throws EE_Error
284
-     * @throws ReflectionException
285
-     * @throws ReflectionException
286
-     * @throws ReflectionException
287
-     */
288
-    public function set($field_name, $field_value, $use_default = false)
289
-    {
290
-        // if not using default and nothing has changed, and object has already been setup (has ID),
291
-        // then don't do anything
292
-        if (
293
-            ! $use_default
294
-            && $this->_fields[ $field_name ] === $field_value
295
-            && $this->ID()
296
-        ) {
297
-            return;
298
-        }
299
-        $model = $this->get_model();
300
-        $this->_has_changes = true;
301
-        $field_obj = $model->field_settings_for($field_name);
302
-        if ($field_obj instanceof EE_Model_Field_Base) {
303
-            // if ( method_exists( $field_obj, 'set_timezone' )) {
304
-            if ($field_obj instanceof EE_Datetime_Field) {
305
-                $field_obj->set_timezone($this->_timezone);
306
-                $field_obj->set_date_format($this->_dt_frmt);
307
-                $field_obj->set_time_format($this->_tm_frmt);
308
-            }
309
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
310
-            // should the value be null?
311
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
312
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
313
-                /**
314
-                 * To save having to refactor all the models, if a default value is used for a
315
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
316
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
317
-                 * object.
318
-                 *
319
-                 * @since 4.6.10+
320
-                 */
321
-                if (
322
-                    $field_obj instanceof EE_Datetime_Field
323
-                    && $this->_fields[ $field_name ] !== null
324
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
325
-                ) {
326
-                    empty($this->_fields[ $field_name ])
327
-                        ? $this->set($field_name, time())
328
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
329
-                }
330
-            } else {
331
-                $this->_fields[ $field_name ] = $holder_of_value;
332
-            }
333
-            // if we're not in the constructor...
334
-            // now check if what we set was a primary key
335
-            if (
16
+	/**
17
+	 * This is an array of the original properties and values provided during construction
18
+	 * of this model object. (keys are model field names, values are their values).
19
+	 * This list is important to remember so that when we are merging data from the db, we know
20
+	 * which values to override and which to not override.
21
+	 *
22
+	 * @var array
23
+	 */
24
+	protected $_props_n_values_provided_in_constructor;
25
+
26
+	/**
27
+	 * Timezone
28
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
29
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
30
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
31
+	 * access to it.
32
+	 *
33
+	 * @var string
34
+	 */
35
+	protected $_timezone;
36
+
37
+	/**
38
+	 * date format
39
+	 * pattern or format for displaying dates
40
+	 *
41
+	 * @var string $_dt_frmt
42
+	 */
43
+	protected $_dt_frmt;
44
+
45
+	/**
46
+	 * time format
47
+	 * pattern or format for displaying time
48
+	 *
49
+	 * @var string $_tm_frmt
50
+	 */
51
+	protected $_tm_frmt;
52
+
53
+	/**
54
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
55
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
56
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
57
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
58
+	 *
59
+	 * @var array
60
+	 */
61
+	protected $_cached_properties = array();
62
+
63
+	/**
64
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
65
+	 * single
66
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
67
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
68
+	 * all others have an array)
69
+	 *
70
+	 * @var array
71
+	 */
72
+	protected $_model_relations = array();
73
+
74
+	/**
75
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
76
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
77
+	 *
78
+	 * @var array
79
+	 */
80
+	protected $_fields = array();
81
+
82
+	/**
83
+	 * @var boolean indicating whether or not this model object is intended to ever be saved
84
+	 * For example, we might create model objects intended to only be used for the duration
85
+	 * of this request and to be thrown away, and if they were accidentally saved
86
+	 * it would be a bug.
87
+	 */
88
+	protected $_allow_persist = true;
89
+
90
+	/**
91
+	 * @var boolean indicating whether or not this model object's properties have changed since construction
92
+	 */
93
+	protected $_has_changes = false;
94
+
95
+	/**
96
+	 * @var EEM_Base
97
+	 */
98
+	protected $_model;
99
+
100
+	/**
101
+	 * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
102
+	 * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
103
+	 * the db.  They also do not automatically update if there are any changes to the data that produced their results.
104
+	 * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
105
+	 * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
106
+	 * array as:
107
+	 * array(
108
+	 *  'Registration_Count' => 24
109
+	 * );
110
+	 * Note: if the custom select configuration for the query included a data type, the value will be in the data type
111
+	 * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
112
+	 * info)
113
+	 *
114
+	 * @var array
115
+	 */
116
+	protected $custom_selection_results = array();
117
+
118
+
119
+	/**
120
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
121
+	 * play nice
122
+	 *
123
+	 * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
124
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
125
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
126
+	 * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
127
+	 *                                                         corresponding db model or not.
128
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
129
+	 *                                                         be in when instantiating a EE_Base_Class object.
130
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
131
+	 *                                                         value is the date_format and second value is the time
132
+	 *                                                         format.
133
+	 * @throws InvalidArgumentException
134
+	 * @throws InvalidInterfaceException
135
+	 * @throws InvalidDataTypeException
136
+	 * @throws EE_Error
137
+	 * @throws ReflectionException
138
+	 */
139
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
140
+	{
141
+		$className = get_class($this);
142
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
143
+		$model = $this->get_model();
144
+		$model_fields = $model->field_settings(false);
145
+		// ensure $fieldValues is an array
146
+		$fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
147
+		// verify client code has not passed any invalid field names
148
+		foreach ($fieldValues as $field_name => $field_value) {
149
+			if (! isset($model_fields[ $field_name ])) {
150
+				throw new EE_Error(
151
+					sprintf(
152
+						esc_html__(
153
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
154
+							'event_espresso'
155
+						),
156
+						$field_name,
157
+						get_class($this),
158
+						implode(', ', array_keys($model_fields))
159
+					)
160
+				);
161
+			}
162
+		}
163
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
164
+		if (! empty($date_formats) && is_array($date_formats)) {
165
+			[$this->_dt_frmt, $this->_tm_frmt] = $date_formats;
166
+		} else {
167
+			// set default formats for date and time
168
+			$this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
169
+			$this->_tm_frmt = (string) get_option('time_format', 'g:i a');
170
+		}
171
+		// if db model is instantiating
172
+		if ($bydb) {
173
+			// client code has indicated these field values are from the database
174
+			foreach ($model_fields as $fieldName => $field) {
175
+				$this->set_from_db(
176
+					$fieldName,
177
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
178
+				);
179
+			}
180
+		} else {
181
+			// we're constructing a brand
182
+			// new instance of the model object. Generally, this means we'll need to do more field validation
183
+			foreach ($model_fields as $fieldName => $field) {
184
+				$this->set(
185
+					$fieldName,
186
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
187
+					true
188
+				);
189
+			}
190
+		}
191
+		// remember what values were passed to this constructor
192
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
193
+		// remember in entity mapper
194
+		if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
195
+			$model->add_to_entity_map($this);
196
+		}
197
+		// setup all the relations
198
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
199
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
200
+				$this->_model_relations[ $relation_name ] = null;
201
+			} else {
202
+				$this->_model_relations[ $relation_name ] = array();
203
+			}
204
+		}
205
+		/**
206
+		 * Action done at the end of each model object construction
207
+		 *
208
+		 * @param EE_Base_Class $this the model object just created
209
+		 */
210
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
211
+	}
212
+
213
+
214
+	/**
215
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
216
+	 *
217
+	 * @return boolean
218
+	 */
219
+	public function allow_persist()
220
+	{
221
+		return $this->_allow_persist;
222
+	}
223
+
224
+
225
+	/**
226
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
227
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
228
+	 * you got new information that somehow made you change your mind.
229
+	 *
230
+	 * @param boolean $allow_persist
231
+	 * @return boolean
232
+	 */
233
+	public function set_allow_persist($allow_persist)
234
+	{
235
+		return $this->_allow_persist = $allow_persist;
236
+	}
237
+
238
+
239
+	/**
240
+	 * Gets the field's original value when this object was constructed during this request.
241
+	 * This can be helpful when determining if a model object has changed or not
242
+	 *
243
+	 * @param string $field_name
244
+	 * @return mixed|null
245
+	 * @throws ReflectionException
246
+	 * @throws InvalidArgumentException
247
+	 * @throws InvalidInterfaceException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws EE_Error
250
+	 */
251
+	public function get_original($field_name)
252
+	{
253
+		if (
254
+			isset($this->_props_n_values_provided_in_constructor[ $field_name ])
255
+			&& $field_settings = $this->get_model()->field_settings_for($field_name)
256
+		) {
257
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
258
+		}
259
+		return null;
260
+	}
261
+
262
+
263
+	/**
264
+	 * @param EE_Base_Class $obj
265
+	 * @return string
266
+	 */
267
+	public function get_class($obj)
268
+	{
269
+		return get_class($obj);
270
+	}
271
+
272
+
273
+	/**
274
+	 * Overrides parent because parent expects old models.
275
+	 * This also doesn't do any validation, and won't work for serialized arrays
276
+	 *
277
+	 * @param    string $field_name
278
+	 * @param    mixed  $field_value
279
+	 * @param bool      $use_default
280
+	 * @throws InvalidArgumentException
281
+	 * @throws InvalidInterfaceException
282
+	 * @throws InvalidDataTypeException
283
+	 * @throws EE_Error
284
+	 * @throws ReflectionException
285
+	 * @throws ReflectionException
286
+	 * @throws ReflectionException
287
+	 */
288
+	public function set($field_name, $field_value, $use_default = false)
289
+	{
290
+		// if not using default and nothing has changed, and object has already been setup (has ID),
291
+		// then don't do anything
292
+		if (
293
+			! $use_default
294
+			&& $this->_fields[ $field_name ] === $field_value
295
+			&& $this->ID()
296
+		) {
297
+			return;
298
+		}
299
+		$model = $this->get_model();
300
+		$this->_has_changes = true;
301
+		$field_obj = $model->field_settings_for($field_name);
302
+		if ($field_obj instanceof EE_Model_Field_Base) {
303
+			// if ( method_exists( $field_obj, 'set_timezone' )) {
304
+			if ($field_obj instanceof EE_Datetime_Field) {
305
+				$field_obj->set_timezone($this->_timezone);
306
+				$field_obj->set_date_format($this->_dt_frmt);
307
+				$field_obj->set_time_format($this->_tm_frmt);
308
+			}
309
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
310
+			// should the value be null?
311
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
312
+				$this->_fields[ $field_name ] = $field_obj->get_default_value();
313
+				/**
314
+				 * To save having to refactor all the models, if a default value is used for a
315
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
316
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
317
+				 * object.
318
+				 *
319
+				 * @since 4.6.10+
320
+				 */
321
+				if (
322
+					$field_obj instanceof EE_Datetime_Field
323
+					&& $this->_fields[ $field_name ] !== null
324
+					&& ! $this->_fields[ $field_name ] instanceof DateTime
325
+				) {
326
+					empty($this->_fields[ $field_name ])
327
+						? $this->set($field_name, time())
328
+						: $this->set($field_name, $this->_fields[ $field_name ]);
329
+				}
330
+			} else {
331
+				$this->_fields[ $field_name ] = $holder_of_value;
332
+			}
333
+			// if we're not in the constructor...
334
+			// now check if what we set was a primary key
335
+			if (
336 336
 // note: props_n_values_provided_in_constructor is only set at the END of the constructor
337
-                $this->_props_n_values_provided_in_constructor
338
-                && $field_value
339
-                && $field_name === $model->primary_key_name()
340
-            ) {
341
-                // if so, we want all this object's fields to be filled either with
342
-                // what we've explicitly set on this model
343
-                // or what we have in the db
344
-                // echo "setting primary key!";
345
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
346
-                $obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
347
-                foreach ($fields_on_model as $field_obj) {
348
-                    if (
349
-                        ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
350
-                        && $field_obj->get_name() !== $field_name
351
-                    ) {
352
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
353
-                    }
354
-                }
355
-                // oh this model object has an ID? well make sure its in the entity mapper
356
-                $model->add_to_entity_map($this);
357
-            }
358
-            // let's unset any cache for this field_name from the $_cached_properties property.
359
-            $this->_clear_cached_property($field_name);
360
-        } else {
361
-            throw new EE_Error(
362
-                sprintf(
363
-                    esc_html__(
364
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
365
-                        'event_espresso'
366
-                    ),
367
-                    $field_name
368
-                )
369
-            );
370
-        }
371
-    }
372
-
373
-
374
-    /**
375
-     * Set custom select values for model.
376
-     *
377
-     * @param array $custom_select_values
378
-     */
379
-    public function setCustomSelectsValues(array $custom_select_values)
380
-    {
381
-        $this->custom_selection_results = $custom_select_values;
382
-    }
383
-
384
-
385
-    /**
386
-     * Returns the custom select value for the provided alias if its set.
387
-     * If not set, returns null.
388
-     *
389
-     * @param string $alias
390
-     * @return string|int|float|null
391
-     */
392
-    public function getCustomSelect($alias)
393
-    {
394
-        return isset($this->custom_selection_results[ $alias ])
395
-            ? $this->custom_selection_results[ $alias ]
396
-            : null;
397
-    }
398
-
399
-
400
-    /**
401
-     * This sets the field value on the db column if it exists for the given $column_name or
402
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
403
-     *
404
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
405
-     * @param string $field_name  Must be the exact column name.
406
-     * @param mixed  $field_value The value to set.
407
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
408
-     * @throws InvalidArgumentException
409
-     * @throws InvalidInterfaceException
410
-     * @throws InvalidDataTypeException
411
-     * @throws EE_Error
412
-     * @throws ReflectionException
413
-     */
414
-    public function set_field_or_extra_meta($field_name, $field_value)
415
-    {
416
-        if ($this->get_model()->has_field($field_name)) {
417
-            $this->set($field_name, $field_value);
418
-            return true;
419
-        }
420
-        // ensure this object is saved first so that extra meta can be properly related.
421
-        $this->save();
422
-        return $this->update_extra_meta($field_name, $field_value);
423
-    }
424
-
425
-
426
-    /**
427
-     * This retrieves the value of the db column set on this class or if that's not present
428
-     * it will attempt to retrieve from extra_meta if found.
429
-     * Example Usage:
430
-     * Via EE_Message child class:
431
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
432
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
433
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
434
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
435
-     * value for those extra fields dynamically via the EE_message object.
436
-     *
437
-     * @param  string $field_name expecting the fully qualified field name.
438
-     * @return mixed|null  value for the field if found.  null if not found.
439
-     * @throws ReflectionException
440
-     * @throws InvalidArgumentException
441
-     * @throws InvalidInterfaceException
442
-     * @throws InvalidDataTypeException
443
-     * @throws EE_Error
444
-     */
445
-    public function get_field_or_extra_meta($field_name)
446
-    {
447
-        if ($this->get_model()->has_field($field_name)) {
448
-            $column_value = $this->get($field_name);
449
-        } else {
450
-            // This isn't a column in the main table, let's see if it is in the extra meta.
451
-            $column_value = $this->get_extra_meta($field_name, true, null);
452
-        }
453
-        return $column_value;
454
-    }
455
-
456
-
457
-    /**
458
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
459
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
460
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
461
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
462
-     *
463
-     * @access public
464
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
465
-     * @return void
466
-     * @throws InvalidArgumentException
467
-     * @throws InvalidInterfaceException
468
-     * @throws InvalidDataTypeException
469
-     * @throws EE_Error
470
-     * @throws ReflectionException
471
-     */
472
-    public function set_timezone($timezone = '')
473
-    {
474
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
475
-        // make sure we clear all cached properties because they won't be relevant now
476
-        $this->_clear_cached_properties();
477
-        // make sure we update field settings and the date for all EE_Datetime_Fields
478
-        $model_fields = $this->get_model()->field_settings(false);
479
-        foreach ($model_fields as $field_name => $field_obj) {
480
-            if ($field_obj instanceof EE_Datetime_Field) {
481
-                $field_obj->set_timezone($this->_timezone);
482
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
483
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
484
-                }
485
-            }
486
-        }
487
-    }
488
-
489
-
490
-    /**
491
-     * This just returns whatever is set for the current timezone.
492
-     *
493
-     * @access public
494
-     * @return string timezone string
495
-     */
496
-    public function get_timezone()
497
-    {
498
-        return $this->_timezone;
499
-    }
500
-
501
-
502
-    /**
503
-     * This sets the internal date format to what is sent in to be used as the new default for the class
504
-     * internally instead of wp set date format options
505
-     *
506
-     * @since 4.6
507
-     * @param string $format should be a format recognizable by PHP date() functions.
508
-     */
509
-    public function set_date_format($format)
510
-    {
511
-        $this->_dt_frmt = $format;
512
-        // clear cached_properties because they won't be relevant now.
513
-        $this->_clear_cached_properties();
514
-    }
515
-
516
-
517
-    /**
518
-     * This sets the internal time format string to what is sent in to be used as the new default for the
519
-     * class internally instead of wp set time format options.
520
-     *
521
-     * @since 4.6
522
-     * @param string $format should be a format recognizable by PHP date() functions.
523
-     */
524
-    public function set_time_format($format)
525
-    {
526
-        $this->_tm_frmt = $format;
527
-        // clear cached_properties because they won't be relevant now.
528
-        $this->_clear_cached_properties();
529
-    }
530
-
531
-
532
-    /**
533
-     * This returns the current internal set format for the date and time formats.
534
-     *
535
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
536
-     *                             where the first value is the date format and the second value is the time format.
537
-     * @return mixed string|array
538
-     */
539
-    public function get_format($full = true)
540
-    {
541
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
542
-    }
543
-
544
-
545
-    /**
546
-     * cache
547
-     * stores the passed model object on the current model object.
548
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
549
-     *
550
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
551
-     *                                       'Registration' associated with this model object
552
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
553
-     *                                       that could be a payment or a registration)
554
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
555
-     *                                       items which will be stored in an array on this object
556
-     * @throws ReflectionException
557
-     * @throws InvalidArgumentException
558
-     * @throws InvalidInterfaceException
559
-     * @throws InvalidDataTypeException
560
-     * @throws EE_Error
561
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
562
-     *                                       related thing, no array)
563
-     */
564
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
565
-    {
566
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
567
-        if (! $object_to_cache instanceof EE_Base_Class) {
568
-            return false;
569
-        }
570
-        // also get "how" the object is related, or throw an error
571
-        if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
572
-            throw new EE_Error(
573
-                sprintf(
574
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
575
-                    $relationName,
576
-                    get_class($this)
577
-                )
578
-            );
579
-        }
580
-        // how many things are related ?
581
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
582
-            // if it's a "belongs to" relationship, then there's only one related model object
583
-            // eg, if this is a registration, there's only 1 attendee for it
584
-            // so for these model objects just set it to be cached
585
-            $this->_model_relations[ $relationName ] = $object_to_cache;
586
-            $return = true;
587
-        } else {
588
-            // otherwise, this is the "many" side of a one to many relationship,
589
-            // so we'll add the object to the array of related objects for that type.
590
-            // eg: if this is an event, there are many registrations for that event,
591
-            // so we cache the registrations in an array
592
-            if (! is_array($this->_model_relations[ $relationName ])) {
593
-                // if for some reason, the cached item is a model object,
594
-                // then stick that in the array, otherwise start with an empty array
595
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
596
-                                                           instanceof
597
-                                                           EE_Base_Class
598
-                    ? array($this->_model_relations[ $relationName ]) : array();
599
-            }
600
-            // first check for a cache_id which is normally empty
601
-            if (! empty($cache_id)) {
602
-                // if the cache_id exists, then it means we are purposely trying to cache this
603
-                // with a known key that can then be used to retrieve the object later on
604
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
605
-                $return = $cache_id;
606
-            } elseif ($object_to_cache->ID()) {
607
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
608
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
609
-                $return = $object_to_cache->ID();
610
-            } else {
611
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
612
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
613
-                // move the internal pointer to the end of the array
614
-                end($this->_model_relations[ $relationName ]);
615
-                // and grab the key so that we can return it
616
-                $return = key($this->_model_relations[ $relationName ]);
617
-            }
618
-        }
619
-        return $return;
620
-    }
621
-
622
-
623
-    /**
624
-     * For adding an item to the cached_properties property.
625
-     *
626
-     * @access protected
627
-     * @param string      $fieldname the property item the corresponding value is for.
628
-     * @param mixed       $value     The value we are caching.
629
-     * @param string|null $cache_type
630
-     * @return void
631
-     * @throws ReflectionException
632
-     * @throws InvalidArgumentException
633
-     * @throws InvalidInterfaceException
634
-     * @throws InvalidDataTypeException
635
-     * @throws EE_Error
636
-     */
637
-    protected function _set_cached_property($fieldname, $value, $cache_type = null)
638
-    {
639
-        // first make sure this property exists
640
-        $this->get_model()->field_settings_for($fieldname);
641
-        $cache_type = empty($cache_type) ? 'standard' : $cache_type;
642
-        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
643
-    }
644
-
645
-
646
-    /**
647
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
648
-     * This also SETS the cache if we return the actual property!
649
-     *
650
-     * @param string $fieldname        the name of the property we're trying to retrieve
651
-     * @param bool   $pretty
652
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
653
-     *                                 (in cases where the same property may be used for different outputs
654
-     *                                 - i.e. datetime, money etc.)
655
-     *                                 It can also accept certain pre-defined "schema" strings
656
-     *                                 to define how to output the property.
657
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
658
-     * @return mixed                   whatever the value for the property is we're retrieving
659
-     * @throws ReflectionException
660
-     * @throws InvalidArgumentException
661
-     * @throws InvalidInterfaceException
662
-     * @throws InvalidDataTypeException
663
-     * @throws EE_Error
664
-     */
665
-    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
666
-    {
667
-        // verify the field exists
668
-        $model = $this->get_model();
669
-        $model->field_settings_for($fieldname);
670
-        $cache_type = $pretty ? 'pretty' : 'standard';
671
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
672
-        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
673
-            return $this->_cached_properties[ $fieldname ][ $cache_type ];
674
-        }
675
-        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
676
-        $this->_set_cached_property($fieldname, $value, $cache_type);
677
-        return $value;
678
-    }
679
-
680
-
681
-    /**
682
-     * If the cache didn't fetch the needed item, this fetches it.
683
-     *
684
-     * @param string $fieldname
685
-     * @param bool   $pretty
686
-     * @param string $extra_cache_ref
687
-     * @return mixed
688
-     * @throws InvalidArgumentException
689
-     * @throws InvalidInterfaceException
690
-     * @throws InvalidDataTypeException
691
-     * @throws EE_Error
692
-     * @throws ReflectionException
693
-     */
694
-    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
695
-    {
696
-        $field_obj = $this->get_model()->field_settings_for($fieldname);
697
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
698
-        if ($field_obj instanceof EE_Datetime_Field) {
699
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
700
-        }
701
-        if (! isset($this->_fields[ $fieldname ])) {
702
-            $this->_fields[ $fieldname ] = null;
703
-        }
704
-        $value = $pretty
705
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
706
-            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
707
-        return $value;
708
-    }
709
-
710
-
711
-    /**
712
-     * set timezone, formats, and output for EE_Datetime_Field objects
713
-     *
714
-     * @param \EE_Datetime_Field $datetime_field
715
-     * @param bool               $pretty
716
-     * @param null               $date_or_time
717
-     * @return void
718
-     * @throws InvalidArgumentException
719
-     * @throws InvalidInterfaceException
720
-     * @throws InvalidDataTypeException
721
-     * @throws EE_Error
722
-     */
723
-    protected function _prepare_datetime_field(
724
-        EE_Datetime_Field $datetime_field,
725
-        $pretty = false,
726
-        $date_or_time = null
727
-    ) {
728
-        $datetime_field->set_timezone($this->_timezone);
729
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
730
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
731
-        // set the output returned
732
-        switch ($date_or_time) {
733
-            case 'D':
734
-                $datetime_field->set_date_time_output('date');
735
-                break;
736
-            case 'T':
737
-                $datetime_field->set_date_time_output('time');
738
-                break;
739
-            default:
740
-                $datetime_field->set_date_time_output();
741
-        }
742
-    }
743
-
744
-
745
-    /**
746
-     * This just takes care of clearing out the cached_properties
747
-     *
748
-     * @return void
749
-     */
750
-    protected function _clear_cached_properties()
751
-    {
752
-        $this->_cached_properties = array();
753
-    }
754
-
755
-
756
-    /**
757
-     * This just clears out ONE property if it exists in the cache
758
-     *
759
-     * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
760
-     * @return void
761
-     */
762
-    protected function _clear_cached_property($property_name)
763
-    {
764
-        if (isset($this->_cached_properties[ $property_name ])) {
765
-            unset($this->_cached_properties[ $property_name ]);
766
-        }
767
-    }
768
-
769
-
770
-    /**
771
-     * Ensures that this related thing is a model object.
772
-     *
773
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
774
-     * @param string $model_name   name of the related thing, eg 'Attendee',
775
-     * @return EE_Base_Class
776
-     * @throws ReflectionException
777
-     * @throws InvalidArgumentException
778
-     * @throws InvalidInterfaceException
779
-     * @throws InvalidDataTypeException
780
-     * @throws EE_Error
781
-     */
782
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
783
-    {
784
-        $other_model_instance = self::_get_model_instance_with_name(
785
-            self::_get_model_classname($model_name),
786
-            $this->_timezone
787
-        );
788
-        return $other_model_instance->ensure_is_obj($object_or_id);
789
-    }
790
-
791
-
792
-    /**
793
-     * Forgets the cached model of the given relation Name. So the next time we request it,
794
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
795
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
796
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
797
-     *
798
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
799
-     *                                                     Eg 'Registration'
800
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
801
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
802
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
803
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
804
-     *                                                     this is HasMany or HABTM.
805
-     * @throws ReflectionException
806
-     * @throws InvalidArgumentException
807
-     * @throws InvalidInterfaceException
808
-     * @throws InvalidDataTypeException
809
-     * @throws EE_Error
810
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
811
-     *                                                     relation from all
812
-     */
813
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
814
-    {
815
-        $relationship_to_model = $this->get_model()->related_settings_for($relationName);
816
-        $index_in_cache = '';
817
-        if (! $relationship_to_model) {
818
-            throw new EE_Error(
819
-                sprintf(
820
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
821
-                    $relationName,
822
-                    get_class($this)
823
-                )
824
-            );
825
-        }
826
-        if ($clear_all) {
827
-            $obj_removed = true;
828
-            $this->_model_relations[ $relationName ] = null;
829
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
830
-            $obj_removed = $this->_model_relations[ $relationName ];
831
-            $this->_model_relations[ $relationName ] = null;
832
-        } else {
833
-            if (
834
-                $object_to_remove_or_index_into_array instanceof EE_Base_Class
835
-                && $object_to_remove_or_index_into_array->ID()
836
-            ) {
837
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
838
-                if (
839
-                    is_array($this->_model_relations[ $relationName ])
840
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
841
-                ) {
842
-                    $index_found_at = null;
843
-                    // find this object in the array even though it has a different key
844
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
845
-                        /** @noinspection TypeUnsafeComparisonInspection */
846
-                        if (
847
-                            $obj instanceof EE_Base_Class
848
-                            && (
849
-                                $obj == $object_to_remove_or_index_into_array
850
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
851
-                            )
852
-                        ) {
853
-                            $index_found_at = $index;
854
-                            break;
855
-                        }
856
-                    }
857
-                    if ($index_found_at) {
858
-                        $index_in_cache = $index_found_at;
859
-                    } else {
860
-                        // it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
861
-                        // if it wasn't in it to begin with. So we're done
862
-                        return $object_to_remove_or_index_into_array;
863
-                    }
864
-                }
865
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
866
-                // so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
867
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
868
-                    /** @noinspection TypeUnsafeComparisonInspection */
869
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
870
-                        $index_in_cache = $index;
871
-                    }
872
-                }
873
-            } else {
874
-                $index_in_cache = $object_to_remove_or_index_into_array;
875
-            }
876
-            // supposedly we've found it. But it could just be that the client code
877
-            // provided a bad index/object
878
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
879
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
880
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
881
-            } else {
882
-                // that thing was never cached anyways.
883
-                $obj_removed = null;
884
-            }
885
-        }
886
-        return $obj_removed;
887
-    }
888
-
889
-
890
-    /**
891
-     * update_cache_after_object_save
892
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
893
-     * obtained after being saved to the db
894
-     *
895
-     * @param string        $relationName       - the type of object that is cached
896
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
897
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
898
-     * @return boolean TRUE on success, FALSE on fail
899
-     * @throws ReflectionException
900
-     * @throws InvalidArgumentException
901
-     * @throws InvalidInterfaceException
902
-     * @throws InvalidDataTypeException
903
-     * @throws EE_Error
904
-     */
905
-    public function update_cache_after_object_save(
906
-        $relationName,
907
-        EE_Base_Class $newly_saved_object,
908
-        $current_cache_id = ''
909
-    ) {
910
-        // verify that incoming object is of the correct type
911
-        $obj_class = 'EE_' . $relationName;
912
-        if ($newly_saved_object instanceof $obj_class) {
913
-            /* @type EE_Base_Class $newly_saved_object */
914
-            // now get the type of relation
915
-            $relationship_to_model = $this->get_model()->related_settings_for($relationName);
916
-            // if this is a 1:1 relationship
917
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
918
-                // then just replace the cached object with the newly saved object
919
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
920
-                return true;
921
-                // or if it's some kind of sordid feral polyamorous relationship...
922
-            }
923
-            if (
924
-                is_array($this->_model_relations[ $relationName ])
925
-                && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
926
-            ) {
927
-                // then remove the current cached item
928
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
929
-                // and cache the newly saved object using it's new ID
930
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
931
-                return true;
932
-            }
933
-        }
934
-        return false;
935
-    }
936
-
937
-
938
-    /**
939
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
940
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
941
-     *
942
-     * @param string $relationName
943
-     * @return EE_Base_Class
944
-     */
945
-    public function get_one_from_cache($relationName)
946
-    {
947
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
948
-            ? $this->_model_relations[ $relationName ]
949
-            : null;
950
-        if (is_array($cached_array_or_object)) {
951
-            return array_shift($cached_array_or_object);
952
-        }
953
-        return $cached_array_or_object;
954
-    }
955
-
956
-
957
-    /**
958
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
959
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
960
-     *
961
-     * @param string $relationName
962
-     * @throws ReflectionException
963
-     * @throws InvalidArgumentException
964
-     * @throws InvalidInterfaceException
965
-     * @throws InvalidDataTypeException
966
-     * @throws EE_Error
967
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
968
-     */
969
-    public function get_all_from_cache($relationName)
970
-    {
971
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
972
-        // if the result is not an array, but exists, make it an array
973
-        $objects = is_array($objects) ? $objects : array($objects);
974
-        // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
975
-        // basically, if this model object was stored in the session, and these cached model objects
976
-        // already have IDs, let's make sure they're in their model's entity mapper
977
-        // otherwise we will have duplicates next time we call
978
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
979
-        $model = EE_Registry::instance()->load_model($relationName);
980
-        foreach ($objects as $model_object) {
981
-            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
982
-                // ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
983
-                if ($model_object->ID()) {
984
-                    $model->add_to_entity_map($model_object);
985
-                }
986
-            } else {
987
-                throw new EE_Error(
988
-                    sprintf(
989
-                        esc_html__(
990
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
991
-                            'event_espresso'
992
-                        ),
993
-                        $relationName,
994
-                        gettype($model_object)
995
-                    )
996
-                );
997
-            }
998
-        }
999
-        return $objects;
1000
-    }
1001
-
1002
-
1003
-    /**
1004
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1005
-     * matching the given query conditions.
1006
-     *
1007
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1008
-     * @param int   $limit              How many objects to return.
1009
-     * @param array $query_params       Any additional conditions on the query.
1010
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1011
-     *                                  you can indicate just the columns you want returned
1012
-     * @return array|EE_Base_Class[]
1013
-     * @throws ReflectionException
1014
-     * @throws InvalidArgumentException
1015
-     * @throws InvalidInterfaceException
1016
-     * @throws InvalidDataTypeException
1017
-     * @throws EE_Error
1018
-     */
1019
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1020
-    {
1021
-        $model = $this->get_model();
1022
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1023
-            ? $model->get_primary_key_field()->get_name()
1024
-            : $field_to_order_by;
1025
-        $current_value = ! empty($field) ? $this->get($field) : null;
1026
-        if (empty($field) || empty($current_value)) {
1027
-            return array();
1028
-        }
1029
-        return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1030
-    }
1031
-
1032
-
1033
-    /**
1034
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1035
-     * matching the given query conditions.
1036
-     *
1037
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1038
-     * @param int   $limit              How many objects to return.
1039
-     * @param array $query_params       Any additional conditions on the query.
1040
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1041
-     *                                  you can indicate just the columns you want returned
1042
-     * @return array|EE_Base_Class[]
1043
-     * @throws ReflectionException
1044
-     * @throws InvalidArgumentException
1045
-     * @throws InvalidInterfaceException
1046
-     * @throws InvalidDataTypeException
1047
-     * @throws EE_Error
1048
-     */
1049
-    public function previous_x(
1050
-        $field_to_order_by = null,
1051
-        $limit = 1,
1052
-        $query_params = array(),
1053
-        $columns_to_select = null
1054
-    ) {
1055
-        $model = $this->get_model();
1056
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1057
-            ? $model->get_primary_key_field()->get_name()
1058
-            : $field_to_order_by;
1059
-        $current_value = ! empty($field) ? $this->get($field) : null;
1060
-        if (empty($field) || empty($current_value)) {
1061
-            return array();
1062
-        }
1063
-        return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1064
-    }
1065
-
1066
-
1067
-    /**
1068
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1069
-     * matching the given query conditions.
1070
-     *
1071
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1072
-     * @param array $query_params       Any additional conditions on the query.
1073
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1074
-     *                                  you can indicate just the columns you want returned
1075
-     * @return array|EE_Base_Class
1076
-     * @throws ReflectionException
1077
-     * @throws InvalidArgumentException
1078
-     * @throws InvalidInterfaceException
1079
-     * @throws InvalidDataTypeException
1080
-     * @throws EE_Error
1081
-     */
1082
-    public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1083
-    {
1084
-        $model = $this->get_model();
1085
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1086
-            ? $model->get_primary_key_field()->get_name()
1087
-            : $field_to_order_by;
1088
-        $current_value = ! empty($field) ? $this->get($field) : null;
1089
-        if (empty($field) || empty($current_value)) {
1090
-            return array();
1091
-        }
1092
-        return $model->next($current_value, $field, $query_params, $columns_to_select);
1093
-    }
1094
-
1095
-
1096
-    /**
1097
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1098
-     * matching the given query conditions.
1099
-     *
1100
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1101
-     * @param array $query_params       Any additional conditions on the query.
1102
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1103
-     *                                  you can indicate just the column you want returned
1104
-     * @return array|EE_Base_Class
1105
-     * @throws ReflectionException
1106
-     * @throws InvalidArgumentException
1107
-     * @throws InvalidInterfaceException
1108
-     * @throws InvalidDataTypeException
1109
-     * @throws EE_Error
1110
-     */
1111
-    public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1112
-    {
1113
-        $model = $this->get_model();
1114
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1115
-            ? $model->get_primary_key_field()->get_name()
1116
-            : $field_to_order_by;
1117
-        $current_value = ! empty($field) ? $this->get($field) : null;
1118
-        if (empty($field) || empty($current_value)) {
1119
-            return array();
1120
-        }
1121
-        return $model->previous($current_value, $field, $query_params, $columns_to_select);
1122
-    }
1123
-
1124
-
1125
-    /**
1126
-     * Overrides parent because parent expects old models.
1127
-     * This also doesn't do any validation, and won't work for serialized arrays
1128
-     *
1129
-     * @param string $field_name
1130
-     * @param mixed  $field_value_from_db
1131
-     * @throws ReflectionException
1132
-     * @throws InvalidArgumentException
1133
-     * @throws InvalidInterfaceException
1134
-     * @throws InvalidDataTypeException
1135
-     * @throws EE_Error
1136
-     */
1137
-    public function set_from_db($field_name, $field_value_from_db)
1138
-    {
1139
-        $field_obj = $this->get_model()->field_settings_for($field_name);
1140
-        if ($field_obj instanceof EE_Model_Field_Base) {
1141
-            // you would think the DB has no NULLs for non-null label fields right? wrong!
1142
-            // eg, a CPT model object could have an entry in the posts table, but no
1143
-            // entry in the meta table. Meaning that all its columns in the meta table
1144
-            // are null! yikes! so when we find one like that, use defaults for its meta columns
1145
-            if ($field_value_from_db === null) {
1146
-                if ($field_obj->is_nullable()) {
1147
-                    // if the field allows nulls, then let it be null
1148
-                    $field_value = null;
1149
-                } else {
1150
-                    $field_value = $field_obj->get_default_value();
1151
-                }
1152
-            } else {
1153
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1154
-            }
1155
-            $this->_fields[ $field_name ] = $field_value;
1156
-            $this->_clear_cached_property($field_name);
1157
-        }
1158
-    }
1159
-
1160
-
1161
-    /**
1162
-     * verifies that the specified field is of the correct type
1163
-     *
1164
-     * @param string $field_name
1165
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1166
-     *                                (in cases where the same property may be used for different outputs
1167
-     *                                - i.e. datetime, money etc.)
1168
-     * @return mixed
1169
-     * @throws ReflectionException
1170
-     * @throws InvalidArgumentException
1171
-     * @throws InvalidInterfaceException
1172
-     * @throws InvalidDataTypeException
1173
-     * @throws EE_Error
1174
-     */
1175
-    public function get($field_name, $extra_cache_ref = null)
1176
-    {
1177
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1178
-    }
1179
-
1180
-
1181
-    /**
1182
-     * This method simply returns the RAW unprocessed value for the given property in this class
1183
-     *
1184
-     * @param  string $field_name A valid fieldname
1185
-     * @return mixed              Whatever the raw value stored on the property is.
1186
-     * @throws ReflectionException
1187
-     * @throws InvalidArgumentException
1188
-     * @throws InvalidInterfaceException
1189
-     * @throws InvalidDataTypeException
1190
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1191
-     */
1192
-    public function get_raw($field_name)
1193
-    {
1194
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1195
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1196
-            ? $this->_fields[ $field_name ]->format('U')
1197
-            : $this->_fields[ $field_name ];
1198
-    }
1199
-
1200
-
1201
-    /**
1202
-     * This is used to return the internal DateTime object used for a field that is a
1203
-     * EE_Datetime_Field.
1204
-     *
1205
-     * @param string $field_name               The field name retrieving the DateTime object.
1206
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1207
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1208
-     *                                         EE_Datetime_Field and but the field value is null, then
1209
-     *                                         just null is returned (because that indicates that likely
1210
-     *                                         this field is nullable).
1211
-     * @throws InvalidArgumentException
1212
-     * @throws InvalidDataTypeException
1213
-     * @throws InvalidInterfaceException
1214
-     * @throws ReflectionException
1215
-     */
1216
-    public function get_DateTime_object($field_name)
1217
-    {
1218
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1219
-        if (! $field_settings instanceof EE_Datetime_Field) {
1220
-            EE_Error::add_error(
1221
-                sprintf(
1222
-                    esc_html__(
1223
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1224
-                        'event_espresso'
1225
-                    ),
1226
-                    $field_name
1227
-                ),
1228
-                __FILE__,
1229
-                __FUNCTION__,
1230
-                __LINE__
1231
-            );
1232
-            return false;
1233
-        }
1234
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1235
-            ? clone $this->_fields[ $field_name ]
1236
-            : null;
1237
-    }
1238
-
1239
-
1240
-    /**
1241
-     * To be used in template to immediately echo out the value, and format it for output.
1242
-     * Eg, should call stripslashes and whatnot before echoing
1243
-     *
1244
-     * @param string $field_name      the name of the field as it appears in the DB
1245
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1246
-     *                                (in cases where the same property may be used for different outputs
1247
-     *                                - i.e. datetime, money etc.)
1248
-     * @return void
1249
-     * @throws ReflectionException
1250
-     * @throws InvalidArgumentException
1251
-     * @throws InvalidInterfaceException
1252
-     * @throws InvalidDataTypeException
1253
-     * @throws EE_Error
1254
-     */
1255
-    public function e($field_name, $extra_cache_ref = null)
1256
-    {
1257
-        echo wp_kses($this->get_pretty($field_name, $extra_cache_ref), AllowedTags::getWithFormTags());
1258
-    }
1259
-
1260
-
1261
-    /**
1262
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1263
-     * can be easily used as the value of form input.
1264
-     *
1265
-     * @param string $field_name
1266
-     * @return void
1267
-     * @throws ReflectionException
1268
-     * @throws InvalidArgumentException
1269
-     * @throws InvalidInterfaceException
1270
-     * @throws InvalidDataTypeException
1271
-     * @throws EE_Error
1272
-     */
1273
-    public function f($field_name)
1274
-    {
1275
-        $this->e($field_name, 'form_input');
1276
-    }
1277
-
1278
-
1279
-    /**
1280
-     * Same as `f()` but just returns the value instead of echoing it
1281
-     *
1282
-     * @param string $field_name
1283
-     * @return string
1284
-     * @throws ReflectionException
1285
-     * @throws InvalidArgumentException
1286
-     * @throws InvalidInterfaceException
1287
-     * @throws InvalidDataTypeException
1288
-     * @throws EE_Error
1289
-     */
1290
-    public function get_f($field_name)
1291
-    {
1292
-        return (string) $this->get_pretty($field_name, 'form_input');
1293
-    }
1294
-
1295
-
1296
-    /**
1297
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1298
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1299
-     * to see what options are available.
1300
-     *
1301
-     * @param string $field_name
1302
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1303
-     *                                (in cases where the same property may be used for different outputs
1304
-     *                                - i.e. datetime, money etc.)
1305
-     * @return mixed
1306
-     * @throws ReflectionException
1307
-     * @throws InvalidArgumentException
1308
-     * @throws InvalidInterfaceException
1309
-     * @throws InvalidDataTypeException
1310
-     * @throws EE_Error
1311
-     */
1312
-    public function get_pretty($field_name, $extra_cache_ref = null)
1313
-    {
1314
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1315
-    }
1316
-
1317
-
1318
-    /**
1319
-     * This simply returns the datetime for the given field name
1320
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1321
-     * (and the equivalent e_date, e_time, e_datetime).
1322
-     *
1323
-     * @access   protected
1324
-     * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1325
-     * @param string   $dt_frmt      valid datetime format used for date
1326
-     *                               (if '' then we just use the default on the field,
1327
-     *                               if NULL we use the last-used format)
1328
-     * @param string   $tm_frmt      Same as above except this is for time format
1329
-     * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1330
-     * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1331
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1332
-     *                               if field is not a valid dtt field, or void if echoing
1333
-     * @throws ReflectionException
1334
-     * @throws InvalidArgumentException
1335
-     * @throws InvalidInterfaceException
1336
-     * @throws InvalidDataTypeException
1337
-     * @throws EE_Error
1338
-     */
1339
-    protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1340
-    {
1341
-        // clear cached property
1342
-        $this->_clear_cached_property($field_name);
1343
-        // reset format properties because they are used in get()
1344
-        $this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1345
-        $this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1346
-        if ($echo) {
1347
-            $this->e($field_name, $date_or_time);
1348
-            return '';
1349
-        }
1350
-        return $this->get($field_name, $date_or_time);
1351
-    }
1352
-
1353
-
1354
-    /**
1355
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1356
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1357
-     * other echoes the pretty value for dtt)
1358
-     *
1359
-     * @param  string $field_name name of model object datetime field holding the value
1360
-     * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1361
-     * @return string            datetime value formatted
1362
-     * @throws ReflectionException
1363
-     * @throws InvalidArgumentException
1364
-     * @throws InvalidInterfaceException
1365
-     * @throws InvalidDataTypeException
1366
-     * @throws EE_Error
1367
-     */
1368
-    public function get_date($field_name, $format = '')
1369
-    {
1370
-        return $this->_get_datetime($field_name, $format, null, 'D');
1371
-    }
1372
-
1373
-
1374
-    /**
1375
-     * @param        $field_name
1376
-     * @param string $format
1377
-     * @throws ReflectionException
1378
-     * @throws InvalidArgumentException
1379
-     * @throws InvalidInterfaceException
1380
-     * @throws InvalidDataTypeException
1381
-     * @throws EE_Error
1382
-     */
1383
-    public function e_date($field_name, $format = '')
1384
-    {
1385
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1386
-    }
1387
-
1388
-
1389
-    /**
1390
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1391
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1392
-     * other echoes the pretty value for dtt)
1393
-     *
1394
-     * @param  string $field_name name of model object datetime field holding the value
1395
-     * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1396
-     * @return string             datetime value formatted
1397
-     * @throws ReflectionException
1398
-     * @throws InvalidArgumentException
1399
-     * @throws InvalidInterfaceException
1400
-     * @throws InvalidDataTypeException
1401
-     * @throws EE_Error
1402
-     */
1403
-    public function get_time($field_name, $format = '')
1404
-    {
1405
-        return $this->_get_datetime($field_name, null, $format, 'T');
1406
-    }
1407
-
1408
-
1409
-    /**
1410
-     * @param        $field_name
1411
-     * @param string $format
1412
-     * @throws ReflectionException
1413
-     * @throws InvalidArgumentException
1414
-     * @throws InvalidInterfaceException
1415
-     * @throws InvalidDataTypeException
1416
-     * @throws EE_Error
1417
-     */
1418
-    public function e_time($field_name, $format = '')
1419
-    {
1420
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1421
-    }
1422
-
1423
-
1424
-    /**
1425
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1426
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1427
-     * other echoes the pretty value for dtt)
1428
-     *
1429
-     * @param  string $field_name name of model object datetime field holding the value
1430
-     * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1431
-     * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1432
-     * @return string             datetime value formatted
1433
-     * @throws ReflectionException
1434
-     * @throws InvalidArgumentException
1435
-     * @throws InvalidInterfaceException
1436
-     * @throws InvalidDataTypeException
1437
-     * @throws EE_Error
1438
-     */
1439
-    public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1440
-    {
1441
-        return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1442
-    }
1443
-
1444
-
1445
-    /**
1446
-     * @param string $field_name
1447
-     * @param string $dt_frmt
1448
-     * @param string $tm_frmt
1449
-     * @throws ReflectionException
1450
-     * @throws InvalidArgumentException
1451
-     * @throws InvalidInterfaceException
1452
-     * @throws InvalidDataTypeException
1453
-     * @throws EE_Error
1454
-     */
1455
-    public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1456
-    {
1457
-        $this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1458
-    }
1459
-
1460
-
1461
-    /**
1462
-     * Get the i8ln value for a date using the WordPress @see date_i18n function.
1463
-     *
1464
-     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1465
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1466
-     *                           on the object will be used.
1467
-     * @return string Date and time string in set locale or false if no field exists for the given
1468
-     * @throws ReflectionException
1469
-     * @throws InvalidArgumentException
1470
-     * @throws InvalidInterfaceException
1471
-     * @throws InvalidDataTypeException
1472
-     * @throws EE_Error
1473
-     *                           field name.
1474
-     */
1475
-    public function get_i18n_datetime($field_name, $format = '')
1476
-    {
1477
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1478
-        return date_i18n(
1479
-            $format,
1480
-            EEH_DTT_Helper::get_timestamp_with_offset(
1481
-                $this->get_raw($field_name),
1482
-                $this->_timezone
1483
-            )
1484
-        );
1485
-    }
1486
-
1487
-
1488
-    /**
1489
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1490
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1491
-     * thrown.
1492
-     *
1493
-     * @param  string $field_name The field name being checked
1494
-     * @throws ReflectionException
1495
-     * @throws InvalidArgumentException
1496
-     * @throws InvalidInterfaceException
1497
-     * @throws InvalidDataTypeException
1498
-     * @throws EE_Error
1499
-     * @return EE_Datetime_Field
1500
-     */
1501
-    protected function _get_dtt_field_settings($field_name)
1502
-    {
1503
-        $field = $this->get_model()->field_settings_for($field_name);
1504
-        // check if field is dtt
1505
-        if ($field instanceof EE_Datetime_Field) {
1506
-            return $field;
1507
-        }
1508
-        throw new EE_Error(
1509
-            sprintf(
1510
-                esc_html__(
1511
-                    'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1512
-                    'event_espresso'
1513
-                ),
1514
-                $field_name,
1515
-                self::_get_model_classname(get_class($this))
1516
-            )
1517
-        );
1518
-    }
1519
-
1520
-
1521
-
1522
-
1523
-    /**
1524
-     * NOTE ABOUT BELOW:
1525
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1526
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1527
-     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1528
-     * method and make sure you send the entire datetime value for setting.
1529
-     */
1530
-    /**
1531
-     * sets the time on a datetime property
1532
-     *
1533
-     * @access protected
1534
-     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1535
-     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1536
-     * @throws ReflectionException
1537
-     * @throws InvalidArgumentException
1538
-     * @throws InvalidInterfaceException
1539
-     * @throws InvalidDataTypeException
1540
-     * @throws EE_Error
1541
-     */
1542
-    protected function _set_time_for($time, $fieldname)
1543
-    {
1544
-        $this->_set_date_time('T', $time, $fieldname);
1545
-    }
1546
-
1547
-
1548
-    /**
1549
-     * sets the date on a datetime property
1550
-     *
1551
-     * @access protected
1552
-     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1553
-     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1554
-     * @throws ReflectionException
1555
-     * @throws InvalidArgumentException
1556
-     * @throws InvalidInterfaceException
1557
-     * @throws InvalidDataTypeException
1558
-     * @throws EE_Error
1559
-     */
1560
-    protected function _set_date_for($date, $fieldname)
1561
-    {
1562
-        $this->_set_date_time('D', $date, $fieldname);
1563
-    }
1564
-
1565
-
1566
-    /**
1567
-     * This takes care of setting a date or time independently on a given model object property. This method also
1568
-     * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1569
-     *
1570
-     * @access protected
1571
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1572
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1573
-     * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1574
-     *                                        EE_Datetime_Field property)
1575
-     * @throws ReflectionException
1576
-     * @throws InvalidArgumentException
1577
-     * @throws InvalidInterfaceException
1578
-     * @throws InvalidDataTypeException
1579
-     * @throws EE_Error
1580
-     */
1581
-    protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1582
-    {
1583
-        $field = $this->_get_dtt_field_settings($fieldname);
1584
-        $field->set_timezone($this->_timezone);
1585
-        $field->set_date_format($this->_dt_frmt);
1586
-        $field->set_time_format($this->_tm_frmt);
1587
-        switch ($what) {
1588
-            case 'T':
1589
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1590
-                    $datetime_value,
1591
-                    $this->_fields[ $fieldname ]
1592
-                );
1593
-                $this->_has_changes = true;
1594
-                break;
1595
-            case 'D':
1596
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1597
-                    $datetime_value,
1598
-                    $this->_fields[ $fieldname ]
1599
-                );
1600
-                $this->_has_changes = true;
1601
-                break;
1602
-            case 'B':
1603
-                $this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1604
-                $this->_has_changes = true;
1605
-                break;
1606
-        }
1607
-        $this->_clear_cached_property($fieldname);
1608
-    }
1609
-
1610
-
1611
-    /**
1612
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1613
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1614
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1615
-     * that could lead to some unexpected results!
1616
-     *
1617
-     * @access public
1618
-     * @param string $field_name               This is the name of the field on the object that contains the date/time
1619
-     *                                         value being returned.
1620
-     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1621
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1622
-     * @param string $prepend                  You can include something to prepend on the timestamp
1623
-     * @param string $append                   You can include something to append on the timestamp
1624
-     * @throws ReflectionException
1625
-     * @throws InvalidArgumentException
1626
-     * @throws InvalidInterfaceException
1627
-     * @throws InvalidDataTypeException
1628
-     * @throws EE_Error
1629
-     * @return string timestamp
1630
-     */
1631
-    public function display_in_my_timezone(
1632
-        $field_name,
1633
-        $callback = 'get_datetime',
1634
-        $args = null,
1635
-        $prepend = '',
1636
-        $append = ''
1637
-    ) {
1638
-        $timezone = EEH_DTT_Helper::get_timezone();
1639
-        if ($timezone === $this->_timezone) {
1640
-            return '';
1641
-        }
1642
-        $original_timezone = $this->_timezone;
1643
-        $this->set_timezone($timezone);
1644
-        $fn = (array) $field_name;
1645
-        $args = array_merge($fn, (array) $args);
1646
-        if (! method_exists($this, $callback)) {
1647
-            throw new EE_Error(
1648
-                sprintf(
1649
-                    esc_html__(
1650
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1651
-                        'event_espresso'
1652
-                    ),
1653
-                    $callback
1654
-                )
1655
-            );
1656
-        }
1657
-        $args = (array) $args;
1658
-        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1659
-        $this->set_timezone($original_timezone);
1660
-        return $return;
1661
-    }
1662
-
1663
-
1664
-    /**
1665
-     * Deletes this model object.
1666
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1667
-     * override
1668
-     * `EE_Base_Class::_delete` NOT this class.
1669
-     *
1670
-     * @return boolean | int
1671
-     * @throws ReflectionException
1672
-     * @throws InvalidArgumentException
1673
-     * @throws InvalidInterfaceException
1674
-     * @throws InvalidDataTypeException
1675
-     * @throws EE_Error
1676
-     */
1677
-    public function delete()
1678
-    {
1679
-        /**
1680
-         * Called just before the `EE_Base_Class::_delete` method call.
1681
-         * Note:
1682
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1683
-         * should be aware that `_delete` may not always result in a permanent delete.
1684
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
1685
-         * soft deletes (trash) the object and does not permanently delete it.
1686
-         *
1687
-         * @param EE_Base_Class $model_object about to be 'deleted'
1688
-         */
1689
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
1690
-        $result = $this->_delete();
1691
-        /**
1692
-         * Called just after the `EE_Base_Class::_delete` method call.
1693
-         * Note:
1694
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1695
-         * should be aware that `_delete` may not always result in a permanent delete.
1696
-         * For example `EE_Soft_Base_Class::_delete`
1697
-         * soft deletes (trash) the object and does not permanently delete it.
1698
-         *
1699
-         * @param EE_Base_Class $model_object that was just 'deleted'
1700
-         * @param boolean       $result
1701
-         */
1702
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1703
-        return $result;
1704
-    }
1705
-
1706
-
1707
-    /**
1708
-     * Calls the specific delete method for the instantiated class.
1709
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1710
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1711
-     * `EE_Base_Class::delete`
1712
-     *
1713
-     * @return bool|int
1714
-     * @throws ReflectionException
1715
-     * @throws InvalidArgumentException
1716
-     * @throws InvalidInterfaceException
1717
-     * @throws InvalidDataTypeException
1718
-     * @throws EE_Error
1719
-     */
1720
-    protected function _delete()
1721
-    {
1722
-        return $this->delete_permanently();
1723
-    }
1724
-
1725
-
1726
-    /**
1727
-     * Deletes this model object permanently from db
1728
-     * (but keep in mind related models may block the delete and return an error)
1729
-     *
1730
-     * @return bool | int
1731
-     * @throws ReflectionException
1732
-     * @throws InvalidArgumentException
1733
-     * @throws InvalidInterfaceException
1734
-     * @throws InvalidDataTypeException
1735
-     * @throws EE_Error
1736
-     */
1737
-    public function delete_permanently()
1738
-    {
1739
-        /**
1740
-         * Called just before HARD deleting a model object
1741
-         *
1742
-         * @param EE_Base_Class $model_object about to be 'deleted'
1743
-         */
1744
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1745
-        $model = $this->get_model();
1746
-        $result = $model->delete_permanently_by_ID($this->ID());
1747
-        $this->refresh_cache_of_related_objects();
1748
-        /**
1749
-         * Called just after HARD deleting a model object
1750
-         *
1751
-         * @param EE_Base_Class $model_object that was just 'deleted'
1752
-         * @param boolean       $result
1753
-         */
1754
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1755
-        return $result;
1756
-    }
1757
-
1758
-
1759
-    /**
1760
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1761
-     * related model objects
1762
-     *
1763
-     * @throws ReflectionException
1764
-     * @throws InvalidArgumentException
1765
-     * @throws InvalidInterfaceException
1766
-     * @throws InvalidDataTypeException
1767
-     * @throws EE_Error
1768
-     */
1769
-    public function refresh_cache_of_related_objects()
1770
-    {
1771
-        $model = $this->get_model();
1772
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1773
-            if (! empty($this->_model_relations[ $relation_name ])) {
1774
-                $related_objects = $this->_model_relations[ $relation_name ];
1775
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1776
-                    // this relation only stores a single model object, not an array
1777
-                    // but let's make it consistent
1778
-                    $related_objects = array($related_objects);
1779
-                }
1780
-                foreach ($related_objects as $related_object) {
1781
-                    // only refresh their cache if they're in memory
1782
-                    if ($related_object instanceof EE_Base_Class) {
1783
-                        $related_object->clear_cache(
1784
-                            $model->get_this_model_name(),
1785
-                            $this
1786
-                        );
1787
-                    }
1788
-                }
1789
-            }
1790
-        }
1791
-    }
1792
-
1793
-
1794
-    /**
1795
-     *        Saves this object to the database. An array may be supplied to set some values on this
1796
-     * object just before saving.
1797
-     *
1798
-     * @access public
1799
-     * @param array $set_cols_n_values keys are field names, values are their new values,
1800
-     *                                 if provided during the save() method (often client code will change the fields'
1801
-     *                                 values before calling save)
1802
-     * @return bool|int|string         1 on a successful update
1803
-     *                                 the ID of the new entry on insert
1804
-     *                                 0 on failure or if the model object isn't allowed to persist
1805
-     *                                 (as determined by EE_Base_Class::allow_persist())
1806
-     * @throws InvalidInterfaceException
1807
-     * @throws InvalidDataTypeException
1808
-     * @throws EE_Error
1809
-     * @throws InvalidArgumentException
1810
-     * @throws ReflectionException
1811
-     * @throws ReflectionException
1812
-     * @throws ReflectionException
1813
-     */
1814
-    public function save($set_cols_n_values = array())
1815
-    {
1816
-        $model = $this->get_model();
1817
-        /**
1818
-         * Filters the fields we're about to save on the model object
1819
-         *
1820
-         * @param array         $set_cols_n_values
1821
-         * @param EE_Base_Class $model_object
1822
-         */
1823
-        $set_cols_n_values = (array) apply_filters(
1824
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
1825
-            $set_cols_n_values,
1826
-            $this
1827
-        );
1828
-        // set attributes as provided in $set_cols_n_values
1829
-        foreach ($set_cols_n_values as $column => $value) {
1830
-            $this->set($column, $value);
1831
-        }
1832
-        // no changes ? then don't do anything
1833
-        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1834
-            return 0;
1835
-        }
1836
-        /**
1837
-         * Saving a model object.
1838
-         * Before we perform a save, this action is fired.
1839
-         *
1840
-         * @param EE_Base_Class $model_object the model object about to be saved.
1841
-         */
1842
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
1843
-        if (! $this->allow_persist()) {
1844
-            return 0;
1845
-        }
1846
-        // now get current attribute values
1847
-        $save_cols_n_values = $this->_fields;
1848
-        // if the object already has an ID, update it. Otherwise, insert it
1849
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
1850
-        // They have been
1851
-        $old_assumption_concerning_value_preparation = $model
1852
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
1853
-        $model->assume_values_already_prepared_by_model_object(true);
1854
-        // does this model have an autoincrement PK?
1855
-        if ($model->has_primary_key_field()) {
1856
-            if ($model->get_primary_key_field()->is_auto_increment()) {
1857
-                // ok check if it's set, if so: update; if not, insert
1858
-                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1859
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1860
-                } else {
1861
-                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1862
-                    $results = $model->insert($save_cols_n_values);
1863
-                    if ($results) {
1864
-                        // if successful, set the primary key
1865
-                        // but don't use the normal SET method, because it will check if
1866
-                        // an item with the same ID exists in the mapper & db, then
1867
-                        // will find it in the db (because we just added it) and THAT object
1868
-                        // will get added to the mapper before we can add this one!
1869
-                        // but if we just avoid using the SET method, all that headache can be avoided
1870
-                        $pk_field_name = $model->primary_key_name();
1871
-                        $this->_fields[ $pk_field_name ] = $results;
1872
-                        $this->_clear_cached_property($pk_field_name);
1873
-                        $model->add_to_entity_map($this);
1874
-                        $this->_update_cached_related_model_objs_fks();
1875
-                    }
1876
-                }
1877
-            } else {// PK is NOT auto-increment
1878
-                // so check if one like it already exists in the db
1879
-                if ($model->exists_by_ID($this->ID())) {
1880
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
1881
-                        throw new EE_Error(
1882
-                            sprintf(
1883
-                                esc_html__(
1884
-                                    'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1885
-                                    'event_espresso'
1886
-                                ),
1887
-                                get_class($this),
1888
-                                get_class($model) . '::instance()->add_to_entity_map()',
1889
-                                get_class($model) . '::instance()->get_one_by_ID()',
1890
-                                '<br />'
1891
-                            )
1892
-                        );
1893
-                    }
1894
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1895
-                } else {
1896
-                    $results = $model->insert($save_cols_n_values);
1897
-                    $this->_update_cached_related_model_objs_fks();
1898
-                }
1899
-            }
1900
-        } else {// there is NO primary key
1901
-            $already_in_db = false;
1902
-            foreach ($model->unique_indexes() as $index) {
1903
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1904
-                if ($model->exists(array($uniqueness_where_params))) {
1905
-                    $already_in_db = true;
1906
-                }
1907
-            }
1908
-            if ($already_in_db) {
1909
-                $combined_pk_fields_n_values = array_intersect_key(
1910
-                    $save_cols_n_values,
1911
-                    $model->get_combined_primary_key_fields()
1912
-                );
1913
-                $results = $model->update(
1914
-                    $save_cols_n_values,
1915
-                    $combined_pk_fields_n_values
1916
-                );
1917
-            } else {
1918
-                $results = $model->insert($save_cols_n_values);
1919
-            }
1920
-        }
1921
-        // restore the old assumption about values being prepared by the model object
1922
-        $model->assume_values_already_prepared_by_model_object(
1923
-            $old_assumption_concerning_value_preparation
1924
-        );
1925
-        /**
1926
-         * After saving the model object this action is called
1927
-         *
1928
-         * @param EE_Base_Class $model_object which was just saved
1929
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1930
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1931
-         */
1932
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1933
-        $this->_has_changes = false;
1934
-        return $results;
1935
-    }
1936
-
1937
-
1938
-    /**
1939
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1940
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1941
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1942
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1943
-     * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1944
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1945
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1946
-     *
1947
-     * @return void
1948
-     * @throws ReflectionException
1949
-     * @throws InvalidArgumentException
1950
-     * @throws InvalidInterfaceException
1951
-     * @throws InvalidDataTypeException
1952
-     * @throws EE_Error
1953
-     */
1954
-    protected function _update_cached_related_model_objs_fks()
1955
-    {
1956
-        $model = $this->get_model();
1957
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1958
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1959
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1960
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1961
-                        $model->get_this_model_name()
1962
-                    );
1963
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1964
-                    if ($related_model_obj_in_cache->ID()) {
1965
-                        $related_model_obj_in_cache->save();
1966
-                    }
1967
-                }
1968
-            }
1969
-        }
1970
-    }
1971
-
1972
-
1973
-    /**
1974
-     * Saves this model object and its NEW cached relations to the database.
1975
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1976
-     * In order for that to work, we would need to mark model objects as dirty/clean...
1977
-     * because otherwise, there's a potential for infinite looping of saving
1978
-     * Saves the cached related model objects, and ensures the relation between them
1979
-     * and this object and properly setup
1980
-     *
1981
-     * @return int ID of new model object on save; 0 on failure+
1982
-     * @throws ReflectionException
1983
-     * @throws InvalidArgumentException
1984
-     * @throws InvalidInterfaceException
1985
-     * @throws InvalidDataTypeException
1986
-     * @throws EE_Error
1987
-     */
1988
-    public function save_new_cached_related_model_objs()
1989
-    {
1990
-        // make sure this has been saved
1991
-        if (! $this->ID()) {
1992
-            $id = $this->save();
1993
-        } else {
1994
-            $id = $this->ID();
1995
-        }
1996
-        // now save all the NEW cached model objects  (ie they don't exist in the DB)
1997
-        foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1998
-            if ($this->_model_relations[ $relationName ]) {
1999
-                // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2000
-                // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2001
-                /* @var $related_model_obj EE_Base_Class */
2002
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
2003
-                    // add a relation to that relation type (which saves the appropriate thing in the process)
2004
-                    // but ONLY if it DOES NOT exist in the DB
2005
-                    $related_model_obj = $this->_model_relations[ $relationName ];
2006
-                    // if( ! $related_model_obj->ID()){
2007
-                    $this->_add_relation_to($related_model_obj, $relationName);
2008
-                    $related_model_obj->save_new_cached_related_model_objs();
2009
-                    // }
2010
-                } else {
2011
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2012
-                        // add a relation to that relation type (which saves the appropriate thing in the process)
2013
-                        // but ONLY if it DOES NOT exist in the DB
2014
-                        // if( ! $related_model_obj->ID()){
2015
-                        $this->_add_relation_to($related_model_obj, $relationName);
2016
-                        $related_model_obj->save_new_cached_related_model_objs();
2017
-                        // }
2018
-                    }
2019
-                }
2020
-            }
2021
-        }
2022
-        return $id;
2023
-    }
2024
-
2025
-
2026
-    /**
2027
-     * for getting a model while instantiated.
2028
-     *
2029
-     * @return EEM_Base | EEM_CPT_Base
2030
-     * @throws ReflectionException
2031
-     * @throws InvalidArgumentException
2032
-     * @throws InvalidInterfaceException
2033
-     * @throws InvalidDataTypeException
2034
-     * @throws EE_Error
2035
-     */
2036
-    public function get_model()
2037
-    {
2038
-        if (! $this->_model) {
2039
-            $modelName = self::_get_model_classname(get_class($this));
2040
-            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2041
-        } else {
2042
-            $this->_model->set_timezone($this->_timezone);
2043
-        }
2044
-        return $this->_model;
2045
-    }
2046
-
2047
-
2048
-    /**
2049
-     * @param $props_n_values
2050
-     * @param $classname
2051
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2052
-     * @throws ReflectionException
2053
-     * @throws InvalidArgumentException
2054
-     * @throws InvalidInterfaceException
2055
-     * @throws InvalidDataTypeException
2056
-     * @throws EE_Error
2057
-     */
2058
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2059
-    {
2060
-        // TODO: will not work for Term_Relationships because they have no PK!
2061
-        $primary_id_ref = self::_get_primary_key_name($classname);
2062
-        if (
2063
-            array_key_exists($primary_id_ref, $props_n_values)
2064
-            && ! empty($props_n_values[ $primary_id_ref ])
2065
-        ) {
2066
-            $id = $props_n_values[ $primary_id_ref ];
2067
-            return self::_get_model($classname)->get_from_entity_map($id);
2068
-        }
2069
-        return false;
2070
-    }
2071
-
2072
-
2073
-    /**
2074
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2075
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2076
-     * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2077
-     * we return false.
2078
-     *
2079
-     * @param  array  $props_n_values   incoming array of properties and their values
2080
-     * @param  string $classname        the classname of the child class
2081
-     * @param null    $timezone
2082
-     * @param array   $date_formats     incoming date_formats in an array where the first value is the
2083
-     *                                  date_format and the second value is the time format
2084
-     * @return mixed (EE_Base_Class|bool)
2085
-     * @throws InvalidArgumentException
2086
-     * @throws InvalidInterfaceException
2087
-     * @throws InvalidDataTypeException
2088
-     * @throws EE_Error
2089
-     * @throws ReflectionException
2090
-     * @throws ReflectionException
2091
-     * @throws ReflectionException
2092
-     */
2093
-    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2094
-    {
2095
-        $existing = null;
2096
-        $model = self::_get_model($classname, $timezone);
2097
-        if ($model->has_primary_key_field()) {
2098
-            $primary_id_ref = self::_get_primary_key_name($classname);
2099
-            if (
2100
-                array_key_exists($primary_id_ref, $props_n_values)
2101
-                && ! empty($props_n_values[ $primary_id_ref ])
2102
-            ) {
2103
-                $existing = $model->get_one_by_ID(
2104
-                    $props_n_values[ $primary_id_ref ]
2105
-                );
2106
-            }
2107
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2108
-            // no primary key on this model, but there's still a matching item in the DB
2109
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2110
-                self::_get_model($classname, $timezone)
2111
-                    ->get_index_primary_key_string($props_n_values)
2112
-            );
2113
-        }
2114
-        if ($existing) {
2115
-            // set date formats if present before setting values
2116
-            if (! empty($date_formats) && is_array($date_formats)) {
2117
-                $existing->set_date_format($date_formats[0]);
2118
-                $existing->set_time_format($date_formats[1]);
2119
-            } else {
2120
-                // set default formats for date and time
2121
-                $existing->set_date_format(get_option('date_format'));
2122
-                $existing->set_time_format(get_option('time_format'));
2123
-            }
2124
-            foreach ($props_n_values as $property => $field_value) {
2125
-                $existing->set($property, $field_value);
2126
-            }
2127
-            return $existing;
2128
-        }
2129
-        return false;
2130
-    }
2131
-
2132
-
2133
-    /**
2134
-     * Gets the EEM_*_Model for this class
2135
-     *
2136
-     * @access public now, as this is more convenient
2137
-     * @param      $classname
2138
-     * @param null $timezone
2139
-     * @throws ReflectionException
2140
-     * @throws InvalidArgumentException
2141
-     * @throws InvalidInterfaceException
2142
-     * @throws InvalidDataTypeException
2143
-     * @throws EE_Error
2144
-     * @return EEM_Base
2145
-     */
2146
-    protected static function _get_model($classname, $timezone = null)
2147
-    {
2148
-        // find model for this class
2149
-        if (! $classname) {
2150
-            throw new EE_Error(
2151
-                sprintf(
2152
-                    esc_html__(
2153
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2154
-                        'event_espresso'
2155
-                    ),
2156
-                    $classname
2157
-                )
2158
-            );
2159
-        }
2160
-        $modelName = self::_get_model_classname($classname);
2161
-        return self::_get_model_instance_with_name($modelName, $timezone);
2162
-    }
2163
-
2164
-
2165
-    /**
2166
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2167
-     *
2168
-     * @param string $model_classname
2169
-     * @param null   $timezone
2170
-     * @return EEM_Base
2171
-     * @throws ReflectionException
2172
-     * @throws InvalidArgumentException
2173
-     * @throws InvalidInterfaceException
2174
-     * @throws InvalidDataTypeException
2175
-     * @throws EE_Error
2176
-     */
2177
-    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2178
-    {
2179
-        $model_classname = str_replace('EEM_', '', $model_classname);
2180
-        $model = EE_Registry::instance()->load_model($model_classname);
2181
-        $model->set_timezone($timezone);
2182
-        return $model;
2183
-    }
2184
-
2185
-
2186
-    /**
2187
-     * If a model name is provided (eg Registration), gets the model classname for that model.
2188
-     * Also works if a model class's classname is provided (eg EE_Registration).
2189
-     *
2190
-     * @param null $model_name
2191
-     * @return string like EEM_Attendee
2192
-     */
2193
-    private static function _get_model_classname($model_name = null)
2194
-    {
2195
-        if (strpos($model_name, 'EE_') === 0) {
2196
-            $model_classname = str_replace('EE_', 'EEM_', $model_name);
2197
-        } else {
2198
-            $model_classname = 'EEM_' . $model_name;
2199
-        }
2200
-        return $model_classname;
2201
-    }
2202
-
2203
-
2204
-    /**
2205
-     * returns the name of the primary key attribute
2206
-     *
2207
-     * @param null $classname
2208
-     * @throws ReflectionException
2209
-     * @throws InvalidArgumentException
2210
-     * @throws InvalidInterfaceException
2211
-     * @throws InvalidDataTypeException
2212
-     * @throws EE_Error
2213
-     * @return string
2214
-     */
2215
-    protected static function _get_primary_key_name($classname = null)
2216
-    {
2217
-        if (! $classname) {
2218
-            throw new EE_Error(
2219
-                sprintf(
2220
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2221
-                    $classname
2222
-                )
2223
-            );
2224
-        }
2225
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
2226
-    }
2227
-
2228
-
2229
-    /**
2230
-     * Gets the value of the primary key.
2231
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
2232
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2233
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2234
-     *
2235
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2236
-     * @throws ReflectionException
2237
-     * @throws InvalidArgumentException
2238
-     * @throws InvalidInterfaceException
2239
-     * @throws InvalidDataTypeException
2240
-     * @throws EE_Error
2241
-     */
2242
-    public function ID()
2243
-    {
2244
-        $model = $this->get_model();
2245
-        // now that we know the name of the variable, use a variable variable to get its value and return its
2246
-        if ($model->has_primary_key_field()) {
2247
-            return $this->_fields[ $model->primary_key_name() ];
2248
-        }
2249
-        return $model->get_index_primary_key_string($this->_fields);
2250
-    }
2251
-
2252
-
2253
-    /**
2254
-     * @param EE_Base_Class|int|string $otherModelObjectOrID
2255
-     * @param string                   $relationName
2256
-     * @return bool
2257
-     * @throws EE_Error
2258
-     * @throws ReflectionException
2259
-     * @since   $VID:$
2260
-     */
2261
-    public function hasRelation($otherModelObjectOrID, string $relationName): bool
2262
-    {
2263
-        $other_model = self::_get_model_instance_with_name(
2264
-            self::_get_model_classname($relationName),
2265
-            $this->_timezone
2266
-        );
2267
-        $primary_key = $other_model->primary_key_name();
2268
-        /** @var EE_Base_Class $otherModelObject */
2269
-        $otherModelObject = $other_model->ensure_is_obj($otherModelObjectOrID, $relationName);
2270
-        return $this->count_related($relationName, [[$primary_key => $otherModelObject->ID()]]) > 0;
2271
-    }
2272
-
2273
-
2274
-    /**
2275
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2276
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2277
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2278
-     *
2279
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2280
-     * @param string $relationName                     eg 'Events','Question',etc.
2281
-     *                                                 an attendee to a group, you also want to specify which role they
2282
-     *                                                 will have in that group. So you would use this parameter to
2283
-     *                                                 specify array('role-column-name'=>'role-id')
2284
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2285
-     *                                                 allow you to further constrict the relation to being added.
2286
-     *                                                 However, keep in mind that the columns (keys) given must match a
2287
-     *                                                 column on the JOIN table and currently only the HABTM models
2288
-     *                                                 accept these additional conditions.  Also remember that if an
2289
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2290
-     *                                                 NEW row is created in the join table.
2291
-     * @param null   $cache_id
2292
-     * @throws ReflectionException
2293
-     * @throws InvalidArgumentException
2294
-     * @throws InvalidInterfaceException
2295
-     * @throws InvalidDataTypeException
2296
-     * @throws EE_Error
2297
-     * @return EE_Base_Class the object the relation was added to
2298
-     */
2299
-    public function _add_relation_to(
2300
-        $otherObjectModelObjectOrID,
2301
-        $relationName,
2302
-        $extra_join_model_fields_n_values = array(),
2303
-        $cache_id = null
2304
-    ) {
2305
-        $model = $this->get_model();
2306
-        // if this thing exists in the DB, save the relation to the DB
2307
-        if ($this->ID()) {
2308
-            $otherObject = $model->add_relationship_to(
2309
-                $this,
2310
-                $otherObjectModelObjectOrID,
2311
-                $relationName,
2312
-                $extra_join_model_fields_n_values
2313
-            );
2314
-            // clear cache so future get_many_related and get_first_related() return new results.
2315
-            $this->clear_cache($relationName, $otherObject, true);
2316
-            if ($otherObject instanceof EE_Base_Class) {
2317
-                $otherObject->clear_cache($model->get_this_model_name(), $this);
2318
-            }
2319
-        } else {
2320
-            // this thing doesn't exist in the DB,  so just cache it
2321
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2322
-                throw new EE_Error(
2323
-                    sprintf(
2324
-                        esc_html__(
2325
-                            'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2326
-                            'event_espresso'
2327
-                        ),
2328
-                        $otherObjectModelObjectOrID,
2329
-                        get_class($this)
2330
-                    )
2331
-                );
2332
-            }
2333
-            $otherObject = $otherObjectModelObjectOrID;
2334
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2335
-        }
2336
-        if ($otherObject instanceof EE_Base_Class) {
2337
-            // fix the reciprocal relation too
2338
-            if ($otherObject->ID()) {
2339
-                // its saved so assumed relations exist in the DB, so we can just
2340
-                // clear the cache so future queries use the updated info in the DB
2341
-                $otherObject->clear_cache(
2342
-                    $model->get_this_model_name(),
2343
-                    null,
2344
-                    true
2345
-                );
2346
-            } else {
2347
-                // it's not saved, so it caches relations like this
2348
-                $otherObject->cache($model->get_this_model_name(), $this);
2349
-            }
2350
-        }
2351
-        return $otherObject;
2352
-    }
2353
-
2354
-
2355
-    /**
2356
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2357
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2358
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2359
-     * from the cache
2360
-     *
2361
-     * @param mixed  $otherObjectModelObjectOrID
2362
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2363
-     *                to the DB yet
2364
-     * @param string $relationName
2365
-     * @param array  $where_query
2366
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2367
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2368
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2369
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2370
-     *                deleted.
2371
-     * @return EE_Base_Class the relation was removed from
2372
-     * @throws ReflectionException
2373
-     * @throws InvalidArgumentException
2374
-     * @throws InvalidInterfaceException
2375
-     * @throws InvalidDataTypeException
2376
-     * @throws EE_Error
2377
-     */
2378
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2379
-    {
2380
-        if ($this->ID()) {
2381
-            // if this exists in the DB, save the relation change to the DB too
2382
-            $otherObject = $this->get_model()->remove_relationship_to(
2383
-                $this,
2384
-                $otherObjectModelObjectOrID,
2385
-                $relationName,
2386
-                $where_query
2387
-            );
2388
-            $this->clear_cache(
2389
-                $relationName,
2390
-                $otherObject
2391
-            );
2392
-        } else {
2393
-            // this doesn't exist in the DB, just remove it from the cache
2394
-            $otherObject = $this->clear_cache(
2395
-                $relationName,
2396
-                $otherObjectModelObjectOrID
2397
-            );
2398
-        }
2399
-        if ($otherObject instanceof EE_Base_Class) {
2400
-            $otherObject->clear_cache(
2401
-                $this->get_model()->get_this_model_name(),
2402
-                $this
2403
-            );
2404
-        }
2405
-        return $otherObject;
2406
-    }
2407
-
2408
-
2409
-    /**
2410
-     * Removes ALL the related things for the $relationName.
2411
-     *
2412
-     * @param string $relationName
2413
-     * @param array  $where_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2414
-     * @return EE_Base_Class
2415
-     * @throws ReflectionException
2416
-     * @throws InvalidArgumentException
2417
-     * @throws InvalidInterfaceException
2418
-     * @throws InvalidDataTypeException
2419
-     * @throws EE_Error
2420
-     */
2421
-    public function _remove_relations($relationName, $where_query_params = array())
2422
-    {
2423
-        if ($this->ID()) {
2424
-            // if this exists in the DB, save the relation change to the DB too
2425
-            $otherObjects = $this->get_model()->remove_relations(
2426
-                $this,
2427
-                $relationName,
2428
-                $where_query_params
2429
-            );
2430
-            $this->clear_cache(
2431
-                $relationName,
2432
-                null,
2433
-                true
2434
-            );
2435
-        } else {
2436
-            // this doesn't exist in the DB, just remove it from the cache
2437
-            $otherObjects = $this->clear_cache(
2438
-                $relationName,
2439
-                null,
2440
-                true
2441
-            );
2442
-        }
2443
-        if (is_array($otherObjects)) {
2444
-            foreach ($otherObjects as $otherObject) {
2445
-                $otherObject->clear_cache(
2446
-                    $this->get_model()->get_this_model_name(),
2447
-                    $this
2448
-                );
2449
-            }
2450
-        }
2451
-        return $otherObjects;
2452
-    }
2453
-
2454
-
2455
-    /**
2456
-     * Gets all the related model objects of the specified type. Eg, if the current class if
2457
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2458
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2459
-     * because we want to get even deleted items etc.
2460
-     *
2461
-     * @param string $relationName key in the model's _model_relations array
2462
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2463
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2464
-     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2465
-     *                             results if you want IDs
2466
-     * @throws ReflectionException
2467
-     * @throws InvalidArgumentException
2468
-     * @throws InvalidInterfaceException
2469
-     * @throws InvalidDataTypeException
2470
-     * @throws EE_Error
2471
-     */
2472
-    public function get_many_related($relationName, $query_params = array())
2473
-    {
2474
-        if ($this->ID()) {
2475
-            // this exists in the DB, so get the related things from either the cache or the DB
2476
-            // if there are query parameters, forget about caching the related model objects.
2477
-            if ($query_params) {
2478
-                $related_model_objects = $this->get_model()->get_all_related(
2479
-                    $this,
2480
-                    $relationName,
2481
-                    $query_params
2482
-                );
2483
-            } else {
2484
-                // did we already cache the result of this query?
2485
-                $cached_results = $this->get_all_from_cache($relationName);
2486
-                if (! $cached_results) {
2487
-                    $related_model_objects = $this->get_model()->get_all_related(
2488
-                        $this,
2489
-                        $relationName,
2490
-                        $query_params
2491
-                    );
2492
-                    // if no query parameters were passed, then we got all the related model objects
2493
-                    // for that relation. We can cache them then.
2494
-                    foreach ($related_model_objects as $related_model_object) {
2495
-                        $this->cache($relationName, $related_model_object);
2496
-                    }
2497
-                } else {
2498
-                    $related_model_objects = $cached_results;
2499
-                }
2500
-            }
2501
-        } else {
2502
-            // this doesn't exist in the DB, so just get the related things from the cache
2503
-            $related_model_objects = $this->get_all_from_cache($relationName);
2504
-        }
2505
-        return $related_model_objects;
2506
-    }
2507
-
2508
-
2509
-    /**
2510
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2511
-     * unless otherwise specified in the $query_params
2512
-     *
2513
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2514
-     * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2515
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2516
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2517
-     *                               that by the setting $distinct to TRUE;
2518
-     * @return int
2519
-     * @throws ReflectionException
2520
-     * @throws InvalidArgumentException
2521
-     * @throws InvalidInterfaceException
2522
-     * @throws InvalidDataTypeException
2523
-     * @throws EE_Error
2524
-     */
2525
-    public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2526
-    {
2527
-        return $this->get_model()->count_related(
2528
-            $this,
2529
-            $relation_name,
2530
-            $query_params,
2531
-            $field_to_count,
2532
-            $distinct
2533
-        );
2534
-    }
2535
-
2536
-
2537
-    /**
2538
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2539
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2540
-     *
2541
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2542
-     * @param array  $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2543
-     * @param string $field_to_sum  name of field to count by.
2544
-     *                              By default, uses primary key
2545
-     *                              (which doesn't make much sense, so you should probably change it)
2546
-     * @return int
2547
-     * @throws ReflectionException
2548
-     * @throws InvalidArgumentException
2549
-     * @throws InvalidInterfaceException
2550
-     * @throws InvalidDataTypeException
2551
-     * @throws EE_Error
2552
-     */
2553
-    public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2554
-    {
2555
-        return $this->get_model()->sum_related(
2556
-            $this,
2557
-            $relation_name,
2558
-            $query_params,
2559
-            $field_to_sum
2560
-        );
2561
-    }
2562
-
2563
-
2564
-    /**
2565
-     * Gets the first (ie, one) related model object of the specified type.
2566
-     *
2567
-     * @param string $relationName key in the model's _model_relations array
2568
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2569
-     * @return EE_Base_Class (not an array, a single object)
2570
-     * @throws ReflectionException
2571
-     * @throws InvalidArgumentException
2572
-     * @throws InvalidInterfaceException
2573
-     * @throws InvalidDataTypeException
2574
-     * @throws EE_Error
2575
-     */
2576
-    public function get_first_related($relationName, $query_params = array())
2577
-    {
2578
-        $model = $this->get_model();
2579
-        if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2580
-            // if they've provided some query parameters, don't bother trying to cache the result
2581
-            // also make sure we're not caching the result of get_first_related
2582
-            // on a relation which should have an array of objects (because the cache might have an array of objects)
2583
-            if (
2584
-                $query_params
2585
-                || ! $model->related_settings_for($relationName)
2586
-                     instanceof
2587
-                     EE_Belongs_To_Relation
2588
-            ) {
2589
-                $related_model_object = $model->get_first_related(
2590
-                    $this,
2591
-                    $relationName,
2592
-                    $query_params
2593
-                );
2594
-            } else {
2595
-                // first, check if we've already cached the result of this query
2596
-                $cached_result = $this->get_one_from_cache($relationName);
2597
-                if (! $cached_result) {
2598
-                    $related_model_object = $model->get_first_related(
2599
-                        $this,
2600
-                        $relationName,
2601
-                        $query_params
2602
-                    );
2603
-                    $this->cache($relationName, $related_model_object);
2604
-                } else {
2605
-                    $related_model_object = $cached_result;
2606
-                }
2607
-            }
2608
-        } else {
2609
-            $related_model_object = null;
2610
-            // this doesn't exist in the Db,
2611
-            // but maybe the relation is of type belongs to, and so the related thing might
2612
-            if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2613
-                $related_model_object = $model->get_first_related(
2614
-                    $this,
2615
-                    $relationName,
2616
-                    $query_params
2617
-                );
2618
-            }
2619
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2620
-            // just get what's cached on this object
2621
-            if (! $related_model_object) {
2622
-                $related_model_object = $this->get_one_from_cache($relationName);
2623
-            }
2624
-        }
2625
-        return $related_model_object;
2626
-    }
2627
-
2628
-
2629
-    /**
2630
-     * Does a delete on all related objects of type $relationName and removes
2631
-     * the current model object's relation to them. If they can't be deleted (because
2632
-     * of blocking related model objects) does nothing. If the related model objects are
2633
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2634
-     * If this model object doesn't exist yet in the DB, just removes its related things
2635
-     *
2636
-     * @param string $relationName
2637
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2638
-     * @return int how many deleted
2639
-     * @throws ReflectionException
2640
-     * @throws InvalidArgumentException
2641
-     * @throws InvalidInterfaceException
2642
-     * @throws InvalidDataTypeException
2643
-     * @throws EE_Error
2644
-     */
2645
-    public function delete_related($relationName, $query_params = array())
2646
-    {
2647
-        if ($this->ID()) {
2648
-            $count = $this->get_model()->delete_related(
2649
-                $this,
2650
-                $relationName,
2651
-                $query_params
2652
-            );
2653
-        } else {
2654
-            $count = count($this->get_all_from_cache($relationName));
2655
-            $this->clear_cache($relationName, null, true);
2656
-        }
2657
-        return $count;
2658
-    }
2659
-
2660
-
2661
-    /**
2662
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2663
-     * the current model object's relation to them. If they can't be deleted (because
2664
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2665
-     * If the related thing isn't a soft-deletable model object, this function is identical
2666
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2667
-     *
2668
-     * @param string $relationName
2669
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2670
-     * @return int how many deleted (including those soft deleted)
2671
-     * @throws ReflectionException
2672
-     * @throws InvalidArgumentException
2673
-     * @throws InvalidInterfaceException
2674
-     * @throws InvalidDataTypeException
2675
-     * @throws EE_Error
2676
-     */
2677
-    public function delete_related_permanently($relationName, $query_params = array())
2678
-    {
2679
-        if ($this->ID()) {
2680
-            $count = $this->get_model()->delete_related_permanently(
2681
-                $this,
2682
-                $relationName,
2683
-                $query_params
2684
-            );
2685
-        } else {
2686
-            $count = count($this->get_all_from_cache($relationName));
2687
-        }
2688
-        $this->clear_cache($relationName, null, true);
2689
-        return $count;
2690
-    }
2691
-
2692
-
2693
-    /**
2694
-     * is_set
2695
-     * Just a simple utility function children can use for checking if property exists
2696
-     *
2697
-     * @access  public
2698
-     * @param  string $field_name property to check
2699
-     * @return bool                              TRUE if existing,FALSE if not.
2700
-     */
2701
-    public function is_set($field_name)
2702
-    {
2703
-        return isset($this->_fields[ $field_name ]);
2704
-    }
2705
-
2706
-
2707
-    /**
2708
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2709
-     * EE_Error exception if they don't
2710
-     *
2711
-     * @param  mixed (string|array) $properties properties to check
2712
-     * @throws EE_Error
2713
-     * @return bool                              TRUE if existing, throw EE_Error if not.
2714
-     */
2715
-    protected function _property_exists($properties)
2716
-    {
2717
-        foreach ((array) $properties as $property_name) {
2718
-            // first make sure this property exists
2719
-            if (! $this->_fields[ $property_name ]) {
2720
-                throw new EE_Error(
2721
-                    sprintf(
2722
-                        esc_html__(
2723
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2724
-                            'event_espresso'
2725
-                        ),
2726
-                        $property_name
2727
-                    )
2728
-                );
2729
-            }
2730
-        }
2731
-        return true;
2732
-    }
2733
-
2734
-
2735
-    /**
2736
-     * This simply returns an array of model fields for this object
2737
-     *
2738
-     * @return array
2739
-     * @throws ReflectionException
2740
-     * @throws InvalidArgumentException
2741
-     * @throws InvalidInterfaceException
2742
-     * @throws InvalidDataTypeException
2743
-     * @throws EE_Error
2744
-     */
2745
-    public function model_field_array()
2746
-    {
2747
-        $fields = $this->get_model()->field_settings(false);
2748
-        $properties = array();
2749
-        // remove prepended underscore
2750
-        foreach ($fields as $field_name => $settings) {
2751
-            $properties[ $field_name ] = $this->get($field_name);
2752
-        }
2753
-        return $properties;
2754
-    }
2755
-
2756
-
2757
-    /**
2758
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2759
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2760
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2761
-     * Instead of requiring a plugin to extend the EE_Base_Class
2762
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2763
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2764
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2765
-     * and accepts 2 arguments: the object on which the function was called,
2766
-     * and an array of the original arguments passed to the function.
2767
-     * Whatever their callback function returns will be returned by this function.
2768
-     * Example: in functions.php (or in a plugin):
2769
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2770
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2771
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2772
-     *          return $previousReturnValue.$returnString;
2773
-     *      }
2774
-     * require('EE_Answer.class.php');
2775
-     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2776
-     * echo $answer->my_callback('monkeys',100);
2777
-     * //will output "you called my_callback! and passed args:monkeys,100"
2778
-     *
2779
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2780
-     * @param array  $args       array of original arguments passed to the function
2781
-     * @throws EE_Error
2782
-     * @return mixed whatever the plugin which calls add_filter decides
2783
-     */
2784
-    public function __call($methodName, $args)
2785
-    {
2786
-        $className = get_class($this);
2787
-        $tagName = "FHEE__{$className}__{$methodName}";
2788
-        if (! has_filter($tagName)) {
2789
-            throw new EE_Error(
2790
-                sprintf(
2791
-                    esc_html__(
2792
-                        "Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2793
-                        'event_espresso'
2794
-                    ),
2795
-                    $methodName,
2796
-                    $className,
2797
-                    $tagName
2798
-                )
2799
-            );
2800
-        }
2801
-        return apply_filters($tagName, null, $this, $args);
2802
-    }
2803
-
2804
-
2805
-    /**
2806
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2807
-     * A $previous_value can be specified in case there are many meta rows with the same key
2808
-     *
2809
-     * @param string $meta_key
2810
-     * @param mixed  $meta_value
2811
-     * @param mixed  $previous_value
2812
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2813
-     *                  NOTE: if the values haven't changed, returns 0
2814
-     * @throws InvalidArgumentException
2815
-     * @throws InvalidInterfaceException
2816
-     * @throws InvalidDataTypeException
2817
-     * @throws EE_Error
2818
-     * @throws ReflectionException
2819
-     */
2820
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2821
-    {
2822
-        $query_params = array(
2823
-            array(
2824
-                'EXM_key'  => $meta_key,
2825
-                'OBJ_ID'   => $this->ID(),
2826
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2827
-            ),
2828
-        );
2829
-        if ($previous_value !== null) {
2830
-            $query_params[0]['EXM_value'] = $meta_value;
2831
-        }
2832
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2833
-        if (! $existing_rows_like_that) {
2834
-            return $this->add_extra_meta($meta_key, $meta_value);
2835
-        }
2836
-        foreach ($existing_rows_like_that as $existing_row) {
2837
-            $existing_row->save(array('EXM_value' => $meta_value));
2838
-        }
2839
-        return count($existing_rows_like_that);
2840
-    }
2841
-
2842
-
2843
-    /**
2844
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2845
-     * no other extra meta for this model object have the same key. Returns TRUE if the
2846
-     * extra meta row was entered, false if not
2847
-     *
2848
-     * @param string  $meta_key
2849
-     * @param mixed   $meta_value
2850
-     * @param boolean $unique
2851
-     * @return boolean
2852
-     * @throws InvalidArgumentException
2853
-     * @throws InvalidInterfaceException
2854
-     * @throws InvalidDataTypeException
2855
-     * @throws EE_Error
2856
-     * @throws ReflectionException
2857
-     * @throws ReflectionException
2858
-     */
2859
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2860
-    {
2861
-        if ($unique) {
2862
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2863
-                array(
2864
-                    array(
2865
-                        'EXM_key'  => $meta_key,
2866
-                        'OBJ_ID'   => $this->ID(),
2867
-                        'EXM_type' => $this->get_model()->get_this_model_name(),
2868
-                    ),
2869
-                )
2870
-            );
2871
-            if ($existing_extra_meta) {
2872
-                return false;
2873
-            }
2874
-        }
2875
-        $new_extra_meta = EE_Extra_Meta::new_instance(
2876
-            array(
2877
-                'EXM_key'   => $meta_key,
2878
-                'EXM_value' => $meta_value,
2879
-                'OBJ_ID'    => $this->ID(),
2880
-                'EXM_type'  => $this->get_model()->get_this_model_name(),
2881
-            )
2882
-        );
2883
-        $new_extra_meta->save();
2884
-        return true;
2885
-    }
2886
-
2887
-
2888
-    /**
2889
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2890
-     * is specified, only deletes extra meta records with that value.
2891
-     *
2892
-     * @param string $meta_key
2893
-     * @param mixed  $meta_value
2894
-     * @return int number of extra meta rows deleted
2895
-     * @throws InvalidArgumentException
2896
-     * @throws InvalidInterfaceException
2897
-     * @throws InvalidDataTypeException
2898
-     * @throws EE_Error
2899
-     * @throws ReflectionException
2900
-     */
2901
-    public function delete_extra_meta($meta_key, $meta_value = null)
2902
-    {
2903
-        $query_params = array(
2904
-            array(
2905
-                'EXM_key'  => $meta_key,
2906
-                'OBJ_ID'   => $this->ID(),
2907
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2908
-            ),
2909
-        );
2910
-        if ($meta_value !== null) {
2911
-            $query_params[0]['EXM_value'] = $meta_value;
2912
-        }
2913
-        return EEM_Extra_Meta::instance()->delete($query_params);
2914
-    }
2915
-
2916
-
2917
-    /**
2918
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2919
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2920
-     * You can specify $default is case you haven't found the extra meta
2921
-     *
2922
-     * @param string  $meta_key
2923
-     * @param boolean $single
2924
-     * @param mixed   $default if we don't find anything, what should we return?
2925
-     * @return mixed single value if $single; array if ! $single
2926
-     * @throws ReflectionException
2927
-     * @throws InvalidArgumentException
2928
-     * @throws InvalidInterfaceException
2929
-     * @throws InvalidDataTypeException
2930
-     * @throws EE_Error
2931
-     */
2932
-    public function get_extra_meta($meta_key, $single = false, $default = null)
2933
-    {
2934
-        if ($single) {
2935
-            $result = $this->get_first_related(
2936
-                'Extra_Meta',
2937
-                array(array('EXM_key' => $meta_key))
2938
-            );
2939
-            if ($result instanceof EE_Extra_Meta) {
2940
-                return $result->value();
2941
-            }
2942
-        } else {
2943
-            $results = $this->get_many_related(
2944
-                'Extra_Meta',
2945
-                array(array('EXM_key' => $meta_key))
2946
-            );
2947
-            if ($results) {
2948
-                $values = array();
2949
-                foreach ($results as $result) {
2950
-                    if ($result instanceof EE_Extra_Meta) {
2951
-                        $values[ $result->ID() ] = $result->value();
2952
-                    }
2953
-                }
2954
-                return $values;
2955
-            }
2956
-        }
2957
-        // if nothing discovered yet return default.
2958
-        return apply_filters(
2959
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2960
-            $default,
2961
-            $meta_key,
2962
-            $single,
2963
-            $this
2964
-        );
2965
-    }
2966
-
2967
-
2968
-    /**
2969
-     * Returns a simple array of all the extra meta associated with this model object.
2970
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2971
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2972
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2973
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2974
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2975
-     * finally the extra meta's value as each sub-value. (eg
2976
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2977
-     *
2978
-     * @param boolean $one_of_each_key
2979
-     * @return array
2980
-     * @throws ReflectionException
2981
-     * @throws InvalidArgumentException
2982
-     * @throws InvalidInterfaceException
2983
-     * @throws InvalidDataTypeException
2984
-     * @throws EE_Error
2985
-     */
2986
-    public function all_extra_meta_array($one_of_each_key = true)
2987
-    {
2988
-        $return_array = array();
2989
-        if ($one_of_each_key) {
2990
-            $extra_meta_objs = $this->get_many_related(
2991
-                'Extra_Meta',
2992
-                array('group_by' => 'EXM_key')
2993
-            );
2994
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2995
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2996
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2997
-                }
2998
-            }
2999
-        } else {
3000
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
3001
-            foreach ($extra_meta_objs as $extra_meta_obj) {
3002
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
3003
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
3004
-                        $return_array[ $extra_meta_obj->key() ] = array();
3005
-                    }
3006
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
3007
-                }
3008
-            }
3009
-        }
3010
-        return $return_array;
3011
-    }
3012
-
3013
-
3014
-    /**
3015
-     * Gets a pretty nice displayable nice for this model object. Often overridden
3016
-     *
3017
-     * @return string
3018
-     * @throws ReflectionException
3019
-     * @throws InvalidArgumentException
3020
-     * @throws InvalidInterfaceException
3021
-     * @throws InvalidDataTypeException
3022
-     * @throws EE_Error
3023
-     */
3024
-    public function name()
3025
-    {
3026
-        // find a field that's not a text field
3027
-        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
3028
-        if ($field_we_can_use) {
3029
-            return $this->get($field_we_can_use->get_name());
3030
-        }
3031
-        $first_few_properties = $this->model_field_array();
3032
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
3033
-        $name_parts = array();
3034
-        foreach ($first_few_properties as $name => $value) {
3035
-            $name_parts[] = "$name:$value";
3036
-        }
3037
-        return implode(',', $name_parts);
3038
-    }
3039
-
3040
-
3041
-    /**
3042
-     * in_entity_map
3043
-     * Checks if this model object has been proven to already be in the entity map
3044
-     *
3045
-     * @return boolean
3046
-     * @throws ReflectionException
3047
-     * @throws InvalidArgumentException
3048
-     * @throws InvalidInterfaceException
3049
-     * @throws InvalidDataTypeException
3050
-     * @throws EE_Error
3051
-     */
3052
-    public function in_entity_map()
3053
-    {
3054
-        // well, if we looked, did we find it in the entity map?
3055
-        return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3056
-    }
3057
-
3058
-
3059
-    /**
3060
-     * refresh_from_db
3061
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
3062
-     *
3063
-     * @throws ReflectionException
3064
-     * @throws InvalidArgumentException
3065
-     * @throws InvalidInterfaceException
3066
-     * @throws InvalidDataTypeException
3067
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3068
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3069
-     */
3070
-    public function refresh_from_db()
3071
-    {
3072
-        if ($this->ID() && $this->in_entity_map()) {
3073
-            $this->get_model()->refresh_entity_map_from_db($this->ID());
3074
-        } else {
3075
-            // if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3076
-            // if it has an ID but it's not in the map, and you're asking me to refresh it
3077
-            // that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3078
-            // absolutely nothing in it for this ID
3079
-            if (WP_DEBUG) {
3080
-                throw new EE_Error(
3081
-                    sprintf(
3082
-                        esc_html__(
3083
-                            'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3084
-                            'event_espresso'
3085
-                        ),
3086
-                        $this->ID(),
3087
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3088
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3089
-                    )
3090
-                );
3091
-            }
3092
-        }
3093
-    }
3094
-
3095
-
3096
-    /**
3097
-     * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3098
-     *
3099
-     * @since 4.9.80.p
3100
-     * @param EE_Model_Field_Base[] $fields
3101
-     * @param string $new_value_sql
3102
-     *      example: 'column_name=123',
3103
-     *      or 'column_name=column_name+1',
3104
-     *      or 'column_name= CASE
3105
-     *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3106
-     *          THEN `column_name` + 5
3107
-     *          ELSE `column_name`
3108
-     *      END'
3109
-     *      Also updates $field on this model object with the latest value from the database.
3110
-     * @return bool
3111
-     * @throws EE_Error
3112
-     * @throws InvalidArgumentException
3113
-     * @throws InvalidDataTypeException
3114
-     * @throws InvalidInterfaceException
3115
-     * @throws ReflectionException
3116
-     */
3117
-    protected function updateFieldsInDB($fields, $new_value_sql)
3118
-    {
3119
-        // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3120
-        // if it wasn't even there to start off.
3121
-        if (! $this->ID()) {
3122
-            $this->save();
3123
-        }
3124
-        global $wpdb;
3125
-        if (empty($fields)) {
3126
-            throw new InvalidArgumentException(
3127
-                esc_html__(
3128
-                    'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3129
-                    'event_espresso'
3130
-                )
3131
-            );
3132
-        }
3133
-        $first_field = reset($fields);
3134
-        $table_alias = $first_field->get_table_alias();
3135
-        foreach ($fields as $field) {
3136
-            if ($table_alias !== $field->get_table_alias()) {
3137
-                throw new InvalidArgumentException(
3138
-                    sprintf(
3139
-                        esc_html__(
3140
-                            // @codingStandardsIgnoreStart
3141
-                            'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
3142
-                            // @codingStandardsIgnoreEnd
3143
-                            'event_espresso'
3144
-                        ),
3145
-                        $table_alias,
3146
-                        $field->get_table_alias()
3147
-                    )
3148
-                );
3149
-            }
3150
-        }
3151
-        // Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3152
-        $table_obj = $this->get_model()->get_table_obj_by_alias($table_alias);
3153
-        $table_pk_value = $this->ID();
3154
-        $table_name = $table_obj->get_table_name();
3155
-        if ($table_obj instanceof EE_Secondary_Table) {
3156
-            $table_pk_field_name = $table_obj->get_fk_on_table();
3157
-        } else {
3158
-            $table_pk_field_name = $table_obj->get_pk_column();
3159
-        }
3160
-
3161
-        $query =
3162
-            "UPDATE `{$table_name}`
337
+				$this->_props_n_values_provided_in_constructor
338
+				&& $field_value
339
+				&& $field_name === $model->primary_key_name()
340
+			) {
341
+				// if so, we want all this object's fields to be filled either with
342
+				// what we've explicitly set on this model
343
+				// or what we have in the db
344
+				// echo "setting primary key!";
345
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
346
+				$obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
347
+				foreach ($fields_on_model as $field_obj) {
348
+					if (
349
+						! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
350
+						&& $field_obj->get_name() !== $field_name
351
+					) {
352
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
353
+					}
354
+				}
355
+				// oh this model object has an ID? well make sure its in the entity mapper
356
+				$model->add_to_entity_map($this);
357
+			}
358
+			// let's unset any cache for this field_name from the $_cached_properties property.
359
+			$this->_clear_cached_property($field_name);
360
+		} else {
361
+			throw new EE_Error(
362
+				sprintf(
363
+					esc_html__(
364
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
365
+						'event_espresso'
366
+					),
367
+					$field_name
368
+				)
369
+			);
370
+		}
371
+	}
372
+
373
+
374
+	/**
375
+	 * Set custom select values for model.
376
+	 *
377
+	 * @param array $custom_select_values
378
+	 */
379
+	public function setCustomSelectsValues(array $custom_select_values)
380
+	{
381
+		$this->custom_selection_results = $custom_select_values;
382
+	}
383
+
384
+
385
+	/**
386
+	 * Returns the custom select value for the provided alias if its set.
387
+	 * If not set, returns null.
388
+	 *
389
+	 * @param string $alias
390
+	 * @return string|int|float|null
391
+	 */
392
+	public function getCustomSelect($alias)
393
+	{
394
+		return isset($this->custom_selection_results[ $alias ])
395
+			? $this->custom_selection_results[ $alias ]
396
+			: null;
397
+	}
398
+
399
+
400
+	/**
401
+	 * This sets the field value on the db column if it exists for the given $column_name or
402
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
403
+	 *
404
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
405
+	 * @param string $field_name  Must be the exact column name.
406
+	 * @param mixed  $field_value The value to set.
407
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
408
+	 * @throws InvalidArgumentException
409
+	 * @throws InvalidInterfaceException
410
+	 * @throws InvalidDataTypeException
411
+	 * @throws EE_Error
412
+	 * @throws ReflectionException
413
+	 */
414
+	public function set_field_or_extra_meta($field_name, $field_value)
415
+	{
416
+		if ($this->get_model()->has_field($field_name)) {
417
+			$this->set($field_name, $field_value);
418
+			return true;
419
+		}
420
+		// ensure this object is saved first so that extra meta can be properly related.
421
+		$this->save();
422
+		return $this->update_extra_meta($field_name, $field_value);
423
+	}
424
+
425
+
426
+	/**
427
+	 * This retrieves the value of the db column set on this class or if that's not present
428
+	 * it will attempt to retrieve from extra_meta if found.
429
+	 * Example Usage:
430
+	 * Via EE_Message child class:
431
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
432
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
433
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
434
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
435
+	 * value for those extra fields dynamically via the EE_message object.
436
+	 *
437
+	 * @param  string $field_name expecting the fully qualified field name.
438
+	 * @return mixed|null  value for the field if found.  null if not found.
439
+	 * @throws ReflectionException
440
+	 * @throws InvalidArgumentException
441
+	 * @throws InvalidInterfaceException
442
+	 * @throws InvalidDataTypeException
443
+	 * @throws EE_Error
444
+	 */
445
+	public function get_field_or_extra_meta($field_name)
446
+	{
447
+		if ($this->get_model()->has_field($field_name)) {
448
+			$column_value = $this->get($field_name);
449
+		} else {
450
+			// This isn't a column in the main table, let's see if it is in the extra meta.
451
+			$column_value = $this->get_extra_meta($field_name, true, null);
452
+		}
453
+		return $column_value;
454
+	}
455
+
456
+
457
+	/**
458
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
459
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
460
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
461
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
462
+	 *
463
+	 * @access public
464
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
465
+	 * @return void
466
+	 * @throws InvalidArgumentException
467
+	 * @throws InvalidInterfaceException
468
+	 * @throws InvalidDataTypeException
469
+	 * @throws EE_Error
470
+	 * @throws ReflectionException
471
+	 */
472
+	public function set_timezone($timezone = '')
473
+	{
474
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
475
+		// make sure we clear all cached properties because they won't be relevant now
476
+		$this->_clear_cached_properties();
477
+		// make sure we update field settings and the date for all EE_Datetime_Fields
478
+		$model_fields = $this->get_model()->field_settings(false);
479
+		foreach ($model_fields as $field_name => $field_obj) {
480
+			if ($field_obj instanceof EE_Datetime_Field) {
481
+				$field_obj->set_timezone($this->_timezone);
482
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
483
+					EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
484
+				}
485
+			}
486
+		}
487
+	}
488
+
489
+
490
+	/**
491
+	 * This just returns whatever is set for the current timezone.
492
+	 *
493
+	 * @access public
494
+	 * @return string timezone string
495
+	 */
496
+	public function get_timezone()
497
+	{
498
+		return $this->_timezone;
499
+	}
500
+
501
+
502
+	/**
503
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
504
+	 * internally instead of wp set date format options
505
+	 *
506
+	 * @since 4.6
507
+	 * @param string $format should be a format recognizable by PHP date() functions.
508
+	 */
509
+	public function set_date_format($format)
510
+	{
511
+		$this->_dt_frmt = $format;
512
+		// clear cached_properties because they won't be relevant now.
513
+		$this->_clear_cached_properties();
514
+	}
515
+
516
+
517
+	/**
518
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
519
+	 * class internally instead of wp set time format options.
520
+	 *
521
+	 * @since 4.6
522
+	 * @param string $format should be a format recognizable by PHP date() functions.
523
+	 */
524
+	public function set_time_format($format)
525
+	{
526
+		$this->_tm_frmt = $format;
527
+		// clear cached_properties because they won't be relevant now.
528
+		$this->_clear_cached_properties();
529
+	}
530
+
531
+
532
+	/**
533
+	 * This returns the current internal set format for the date and time formats.
534
+	 *
535
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
536
+	 *                             where the first value is the date format and the second value is the time format.
537
+	 * @return mixed string|array
538
+	 */
539
+	public function get_format($full = true)
540
+	{
541
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
542
+	}
543
+
544
+
545
+	/**
546
+	 * cache
547
+	 * stores the passed model object on the current model object.
548
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
549
+	 *
550
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
551
+	 *                                       'Registration' associated with this model object
552
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
553
+	 *                                       that could be a payment or a registration)
554
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
555
+	 *                                       items which will be stored in an array on this object
556
+	 * @throws ReflectionException
557
+	 * @throws InvalidArgumentException
558
+	 * @throws InvalidInterfaceException
559
+	 * @throws InvalidDataTypeException
560
+	 * @throws EE_Error
561
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
562
+	 *                                       related thing, no array)
563
+	 */
564
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
565
+	{
566
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
567
+		if (! $object_to_cache instanceof EE_Base_Class) {
568
+			return false;
569
+		}
570
+		// also get "how" the object is related, or throw an error
571
+		if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
572
+			throw new EE_Error(
573
+				sprintf(
574
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
575
+					$relationName,
576
+					get_class($this)
577
+				)
578
+			);
579
+		}
580
+		// how many things are related ?
581
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
582
+			// if it's a "belongs to" relationship, then there's only one related model object
583
+			// eg, if this is a registration, there's only 1 attendee for it
584
+			// so for these model objects just set it to be cached
585
+			$this->_model_relations[ $relationName ] = $object_to_cache;
586
+			$return = true;
587
+		} else {
588
+			// otherwise, this is the "many" side of a one to many relationship,
589
+			// so we'll add the object to the array of related objects for that type.
590
+			// eg: if this is an event, there are many registrations for that event,
591
+			// so we cache the registrations in an array
592
+			if (! is_array($this->_model_relations[ $relationName ])) {
593
+				// if for some reason, the cached item is a model object,
594
+				// then stick that in the array, otherwise start with an empty array
595
+				$this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
596
+														   instanceof
597
+														   EE_Base_Class
598
+					? array($this->_model_relations[ $relationName ]) : array();
599
+			}
600
+			// first check for a cache_id which is normally empty
601
+			if (! empty($cache_id)) {
602
+				// if the cache_id exists, then it means we are purposely trying to cache this
603
+				// with a known key that can then be used to retrieve the object later on
604
+				$this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
605
+				$return = $cache_id;
606
+			} elseif ($object_to_cache->ID()) {
607
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
608
+				$this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
609
+				$return = $object_to_cache->ID();
610
+			} else {
611
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
612
+				$this->_model_relations[ $relationName ][] = $object_to_cache;
613
+				// move the internal pointer to the end of the array
614
+				end($this->_model_relations[ $relationName ]);
615
+				// and grab the key so that we can return it
616
+				$return = key($this->_model_relations[ $relationName ]);
617
+			}
618
+		}
619
+		return $return;
620
+	}
621
+
622
+
623
+	/**
624
+	 * For adding an item to the cached_properties property.
625
+	 *
626
+	 * @access protected
627
+	 * @param string      $fieldname the property item the corresponding value is for.
628
+	 * @param mixed       $value     The value we are caching.
629
+	 * @param string|null $cache_type
630
+	 * @return void
631
+	 * @throws ReflectionException
632
+	 * @throws InvalidArgumentException
633
+	 * @throws InvalidInterfaceException
634
+	 * @throws InvalidDataTypeException
635
+	 * @throws EE_Error
636
+	 */
637
+	protected function _set_cached_property($fieldname, $value, $cache_type = null)
638
+	{
639
+		// first make sure this property exists
640
+		$this->get_model()->field_settings_for($fieldname);
641
+		$cache_type = empty($cache_type) ? 'standard' : $cache_type;
642
+		$this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
643
+	}
644
+
645
+
646
+	/**
647
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
648
+	 * This also SETS the cache if we return the actual property!
649
+	 *
650
+	 * @param string $fieldname        the name of the property we're trying to retrieve
651
+	 * @param bool   $pretty
652
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
653
+	 *                                 (in cases where the same property may be used for different outputs
654
+	 *                                 - i.e. datetime, money etc.)
655
+	 *                                 It can also accept certain pre-defined "schema" strings
656
+	 *                                 to define how to output the property.
657
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
658
+	 * @return mixed                   whatever the value for the property is we're retrieving
659
+	 * @throws ReflectionException
660
+	 * @throws InvalidArgumentException
661
+	 * @throws InvalidInterfaceException
662
+	 * @throws InvalidDataTypeException
663
+	 * @throws EE_Error
664
+	 */
665
+	protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
666
+	{
667
+		// verify the field exists
668
+		$model = $this->get_model();
669
+		$model->field_settings_for($fieldname);
670
+		$cache_type = $pretty ? 'pretty' : 'standard';
671
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
672
+		if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
673
+			return $this->_cached_properties[ $fieldname ][ $cache_type ];
674
+		}
675
+		$value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
676
+		$this->_set_cached_property($fieldname, $value, $cache_type);
677
+		return $value;
678
+	}
679
+
680
+
681
+	/**
682
+	 * If the cache didn't fetch the needed item, this fetches it.
683
+	 *
684
+	 * @param string $fieldname
685
+	 * @param bool   $pretty
686
+	 * @param string $extra_cache_ref
687
+	 * @return mixed
688
+	 * @throws InvalidArgumentException
689
+	 * @throws InvalidInterfaceException
690
+	 * @throws InvalidDataTypeException
691
+	 * @throws EE_Error
692
+	 * @throws ReflectionException
693
+	 */
694
+	protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
695
+	{
696
+		$field_obj = $this->get_model()->field_settings_for($fieldname);
697
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
698
+		if ($field_obj instanceof EE_Datetime_Field) {
699
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
700
+		}
701
+		if (! isset($this->_fields[ $fieldname ])) {
702
+			$this->_fields[ $fieldname ] = null;
703
+		}
704
+		$value = $pretty
705
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
706
+			: $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
707
+		return $value;
708
+	}
709
+
710
+
711
+	/**
712
+	 * set timezone, formats, and output for EE_Datetime_Field objects
713
+	 *
714
+	 * @param \EE_Datetime_Field $datetime_field
715
+	 * @param bool               $pretty
716
+	 * @param null               $date_or_time
717
+	 * @return void
718
+	 * @throws InvalidArgumentException
719
+	 * @throws InvalidInterfaceException
720
+	 * @throws InvalidDataTypeException
721
+	 * @throws EE_Error
722
+	 */
723
+	protected function _prepare_datetime_field(
724
+		EE_Datetime_Field $datetime_field,
725
+		$pretty = false,
726
+		$date_or_time = null
727
+	) {
728
+		$datetime_field->set_timezone($this->_timezone);
729
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
730
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
731
+		// set the output returned
732
+		switch ($date_or_time) {
733
+			case 'D':
734
+				$datetime_field->set_date_time_output('date');
735
+				break;
736
+			case 'T':
737
+				$datetime_field->set_date_time_output('time');
738
+				break;
739
+			default:
740
+				$datetime_field->set_date_time_output();
741
+		}
742
+	}
743
+
744
+
745
+	/**
746
+	 * This just takes care of clearing out the cached_properties
747
+	 *
748
+	 * @return void
749
+	 */
750
+	protected function _clear_cached_properties()
751
+	{
752
+		$this->_cached_properties = array();
753
+	}
754
+
755
+
756
+	/**
757
+	 * This just clears out ONE property if it exists in the cache
758
+	 *
759
+	 * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
760
+	 * @return void
761
+	 */
762
+	protected function _clear_cached_property($property_name)
763
+	{
764
+		if (isset($this->_cached_properties[ $property_name ])) {
765
+			unset($this->_cached_properties[ $property_name ]);
766
+		}
767
+	}
768
+
769
+
770
+	/**
771
+	 * Ensures that this related thing is a model object.
772
+	 *
773
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
774
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
775
+	 * @return EE_Base_Class
776
+	 * @throws ReflectionException
777
+	 * @throws InvalidArgumentException
778
+	 * @throws InvalidInterfaceException
779
+	 * @throws InvalidDataTypeException
780
+	 * @throws EE_Error
781
+	 */
782
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
783
+	{
784
+		$other_model_instance = self::_get_model_instance_with_name(
785
+			self::_get_model_classname($model_name),
786
+			$this->_timezone
787
+		);
788
+		return $other_model_instance->ensure_is_obj($object_or_id);
789
+	}
790
+
791
+
792
+	/**
793
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
794
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
795
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
796
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
797
+	 *
798
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
799
+	 *                                                     Eg 'Registration'
800
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
801
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
802
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
803
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
804
+	 *                                                     this is HasMany or HABTM.
805
+	 * @throws ReflectionException
806
+	 * @throws InvalidArgumentException
807
+	 * @throws InvalidInterfaceException
808
+	 * @throws InvalidDataTypeException
809
+	 * @throws EE_Error
810
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
811
+	 *                                                     relation from all
812
+	 */
813
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
814
+	{
815
+		$relationship_to_model = $this->get_model()->related_settings_for($relationName);
816
+		$index_in_cache = '';
817
+		if (! $relationship_to_model) {
818
+			throw new EE_Error(
819
+				sprintf(
820
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
821
+					$relationName,
822
+					get_class($this)
823
+				)
824
+			);
825
+		}
826
+		if ($clear_all) {
827
+			$obj_removed = true;
828
+			$this->_model_relations[ $relationName ] = null;
829
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
830
+			$obj_removed = $this->_model_relations[ $relationName ];
831
+			$this->_model_relations[ $relationName ] = null;
832
+		} else {
833
+			if (
834
+				$object_to_remove_or_index_into_array instanceof EE_Base_Class
835
+				&& $object_to_remove_or_index_into_array->ID()
836
+			) {
837
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
838
+				if (
839
+					is_array($this->_model_relations[ $relationName ])
840
+					&& ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
841
+				) {
842
+					$index_found_at = null;
843
+					// find this object in the array even though it has a different key
844
+					foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
845
+						/** @noinspection TypeUnsafeComparisonInspection */
846
+						if (
847
+							$obj instanceof EE_Base_Class
848
+							&& (
849
+								$obj == $object_to_remove_or_index_into_array
850
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
851
+							)
852
+						) {
853
+							$index_found_at = $index;
854
+							break;
855
+						}
856
+					}
857
+					if ($index_found_at) {
858
+						$index_in_cache = $index_found_at;
859
+					} else {
860
+						// it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
861
+						// if it wasn't in it to begin with. So we're done
862
+						return $object_to_remove_or_index_into_array;
863
+					}
864
+				}
865
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
866
+				// so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
867
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
868
+					/** @noinspection TypeUnsafeComparisonInspection */
869
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
870
+						$index_in_cache = $index;
871
+					}
872
+				}
873
+			} else {
874
+				$index_in_cache = $object_to_remove_or_index_into_array;
875
+			}
876
+			// supposedly we've found it. But it could just be that the client code
877
+			// provided a bad index/object
878
+			if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
879
+				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
880
+				unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
881
+			} else {
882
+				// that thing was never cached anyways.
883
+				$obj_removed = null;
884
+			}
885
+		}
886
+		return $obj_removed;
887
+	}
888
+
889
+
890
+	/**
891
+	 * update_cache_after_object_save
892
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
893
+	 * obtained after being saved to the db
894
+	 *
895
+	 * @param string        $relationName       - the type of object that is cached
896
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
897
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
898
+	 * @return boolean TRUE on success, FALSE on fail
899
+	 * @throws ReflectionException
900
+	 * @throws InvalidArgumentException
901
+	 * @throws InvalidInterfaceException
902
+	 * @throws InvalidDataTypeException
903
+	 * @throws EE_Error
904
+	 */
905
+	public function update_cache_after_object_save(
906
+		$relationName,
907
+		EE_Base_Class $newly_saved_object,
908
+		$current_cache_id = ''
909
+	) {
910
+		// verify that incoming object is of the correct type
911
+		$obj_class = 'EE_' . $relationName;
912
+		if ($newly_saved_object instanceof $obj_class) {
913
+			/* @type EE_Base_Class $newly_saved_object */
914
+			// now get the type of relation
915
+			$relationship_to_model = $this->get_model()->related_settings_for($relationName);
916
+			// if this is a 1:1 relationship
917
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
918
+				// then just replace the cached object with the newly saved object
919
+				$this->_model_relations[ $relationName ] = $newly_saved_object;
920
+				return true;
921
+				// or if it's some kind of sordid feral polyamorous relationship...
922
+			}
923
+			if (
924
+				is_array($this->_model_relations[ $relationName ])
925
+				&& isset($this->_model_relations[ $relationName ][ $current_cache_id ])
926
+			) {
927
+				// then remove the current cached item
928
+				unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
929
+				// and cache the newly saved object using it's new ID
930
+				$this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
931
+				return true;
932
+			}
933
+		}
934
+		return false;
935
+	}
936
+
937
+
938
+	/**
939
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
940
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
941
+	 *
942
+	 * @param string $relationName
943
+	 * @return EE_Base_Class
944
+	 */
945
+	public function get_one_from_cache($relationName)
946
+	{
947
+		$cached_array_or_object = isset($this->_model_relations[ $relationName ])
948
+			? $this->_model_relations[ $relationName ]
949
+			: null;
950
+		if (is_array($cached_array_or_object)) {
951
+			return array_shift($cached_array_or_object);
952
+		}
953
+		return $cached_array_or_object;
954
+	}
955
+
956
+
957
+	/**
958
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
959
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
960
+	 *
961
+	 * @param string $relationName
962
+	 * @throws ReflectionException
963
+	 * @throws InvalidArgumentException
964
+	 * @throws InvalidInterfaceException
965
+	 * @throws InvalidDataTypeException
966
+	 * @throws EE_Error
967
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
968
+	 */
969
+	public function get_all_from_cache($relationName)
970
+	{
971
+		$objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
972
+		// if the result is not an array, but exists, make it an array
973
+		$objects = is_array($objects) ? $objects : array($objects);
974
+		// bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
975
+		// basically, if this model object was stored in the session, and these cached model objects
976
+		// already have IDs, let's make sure they're in their model's entity mapper
977
+		// otherwise we will have duplicates next time we call
978
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
979
+		$model = EE_Registry::instance()->load_model($relationName);
980
+		foreach ($objects as $model_object) {
981
+			if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
982
+				// ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
983
+				if ($model_object->ID()) {
984
+					$model->add_to_entity_map($model_object);
985
+				}
986
+			} else {
987
+				throw new EE_Error(
988
+					sprintf(
989
+						esc_html__(
990
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
991
+							'event_espresso'
992
+						),
993
+						$relationName,
994
+						gettype($model_object)
995
+					)
996
+				);
997
+			}
998
+		}
999
+		return $objects;
1000
+	}
1001
+
1002
+
1003
+	/**
1004
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1005
+	 * matching the given query conditions.
1006
+	 *
1007
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1008
+	 * @param int   $limit              How many objects to return.
1009
+	 * @param array $query_params       Any additional conditions on the query.
1010
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1011
+	 *                                  you can indicate just the columns you want returned
1012
+	 * @return array|EE_Base_Class[]
1013
+	 * @throws ReflectionException
1014
+	 * @throws InvalidArgumentException
1015
+	 * @throws InvalidInterfaceException
1016
+	 * @throws InvalidDataTypeException
1017
+	 * @throws EE_Error
1018
+	 */
1019
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1020
+	{
1021
+		$model = $this->get_model();
1022
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1023
+			? $model->get_primary_key_field()->get_name()
1024
+			: $field_to_order_by;
1025
+		$current_value = ! empty($field) ? $this->get($field) : null;
1026
+		if (empty($field) || empty($current_value)) {
1027
+			return array();
1028
+		}
1029
+		return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1030
+	}
1031
+
1032
+
1033
+	/**
1034
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1035
+	 * matching the given query conditions.
1036
+	 *
1037
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1038
+	 * @param int   $limit              How many objects to return.
1039
+	 * @param array $query_params       Any additional conditions on the query.
1040
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1041
+	 *                                  you can indicate just the columns you want returned
1042
+	 * @return array|EE_Base_Class[]
1043
+	 * @throws ReflectionException
1044
+	 * @throws InvalidArgumentException
1045
+	 * @throws InvalidInterfaceException
1046
+	 * @throws InvalidDataTypeException
1047
+	 * @throws EE_Error
1048
+	 */
1049
+	public function previous_x(
1050
+		$field_to_order_by = null,
1051
+		$limit = 1,
1052
+		$query_params = array(),
1053
+		$columns_to_select = null
1054
+	) {
1055
+		$model = $this->get_model();
1056
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1057
+			? $model->get_primary_key_field()->get_name()
1058
+			: $field_to_order_by;
1059
+		$current_value = ! empty($field) ? $this->get($field) : null;
1060
+		if (empty($field) || empty($current_value)) {
1061
+			return array();
1062
+		}
1063
+		return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1064
+	}
1065
+
1066
+
1067
+	/**
1068
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1069
+	 * matching the given query conditions.
1070
+	 *
1071
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1072
+	 * @param array $query_params       Any additional conditions on the query.
1073
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1074
+	 *                                  you can indicate just the columns you want returned
1075
+	 * @return array|EE_Base_Class
1076
+	 * @throws ReflectionException
1077
+	 * @throws InvalidArgumentException
1078
+	 * @throws InvalidInterfaceException
1079
+	 * @throws InvalidDataTypeException
1080
+	 * @throws EE_Error
1081
+	 */
1082
+	public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1083
+	{
1084
+		$model = $this->get_model();
1085
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1086
+			? $model->get_primary_key_field()->get_name()
1087
+			: $field_to_order_by;
1088
+		$current_value = ! empty($field) ? $this->get($field) : null;
1089
+		if (empty($field) || empty($current_value)) {
1090
+			return array();
1091
+		}
1092
+		return $model->next($current_value, $field, $query_params, $columns_to_select);
1093
+	}
1094
+
1095
+
1096
+	/**
1097
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1098
+	 * matching the given query conditions.
1099
+	 *
1100
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1101
+	 * @param array $query_params       Any additional conditions on the query.
1102
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1103
+	 *                                  you can indicate just the column you want returned
1104
+	 * @return array|EE_Base_Class
1105
+	 * @throws ReflectionException
1106
+	 * @throws InvalidArgumentException
1107
+	 * @throws InvalidInterfaceException
1108
+	 * @throws InvalidDataTypeException
1109
+	 * @throws EE_Error
1110
+	 */
1111
+	public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1112
+	{
1113
+		$model = $this->get_model();
1114
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1115
+			? $model->get_primary_key_field()->get_name()
1116
+			: $field_to_order_by;
1117
+		$current_value = ! empty($field) ? $this->get($field) : null;
1118
+		if (empty($field) || empty($current_value)) {
1119
+			return array();
1120
+		}
1121
+		return $model->previous($current_value, $field, $query_params, $columns_to_select);
1122
+	}
1123
+
1124
+
1125
+	/**
1126
+	 * Overrides parent because parent expects old models.
1127
+	 * This also doesn't do any validation, and won't work for serialized arrays
1128
+	 *
1129
+	 * @param string $field_name
1130
+	 * @param mixed  $field_value_from_db
1131
+	 * @throws ReflectionException
1132
+	 * @throws InvalidArgumentException
1133
+	 * @throws InvalidInterfaceException
1134
+	 * @throws InvalidDataTypeException
1135
+	 * @throws EE_Error
1136
+	 */
1137
+	public function set_from_db($field_name, $field_value_from_db)
1138
+	{
1139
+		$field_obj = $this->get_model()->field_settings_for($field_name);
1140
+		if ($field_obj instanceof EE_Model_Field_Base) {
1141
+			// you would think the DB has no NULLs for non-null label fields right? wrong!
1142
+			// eg, a CPT model object could have an entry in the posts table, but no
1143
+			// entry in the meta table. Meaning that all its columns in the meta table
1144
+			// are null! yikes! so when we find one like that, use defaults for its meta columns
1145
+			if ($field_value_from_db === null) {
1146
+				if ($field_obj->is_nullable()) {
1147
+					// if the field allows nulls, then let it be null
1148
+					$field_value = null;
1149
+				} else {
1150
+					$field_value = $field_obj->get_default_value();
1151
+				}
1152
+			} else {
1153
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1154
+			}
1155
+			$this->_fields[ $field_name ] = $field_value;
1156
+			$this->_clear_cached_property($field_name);
1157
+		}
1158
+	}
1159
+
1160
+
1161
+	/**
1162
+	 * verifies that the specified field is of the correct type
1163
+	 *
1164
+	 * @param string $field_name
1165
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1166
+	 *                                (in cases where the same property may be used for different outputs
1167
+	 *                                - i.e. datetime, money etc.)
1168
+	 * @return mixed
1169
+	 * @throws ReflectionException
1170
+	 * @throws InvalidArgumentException
1171
+	 * @throws InvalidInterfaceException
1172
+	 * @throws InvalidDataTypeException
1173
+	 * @throws EE_Error
1174
+	 */
1175
+	public function get($field_name, $extra_cache_ref = null)
1176
+	{
1177
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1178
+	}
1179
+
1180
+
1181
+	/**
1182
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1183
+	 *
1184
+	 * @param  string $field_name A valid fieldname
1185
+	 * @return mixed              Whatever the raw value stored on the property is.
1186
+	 * @throws ReflectionException
1187
+	 * @throws InvalidArgumentException
1188
+	 * @throws InvalidInterfaceException
1189
+	 * @throws InvalidDataTypeException
1190
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1191
+	 */
1192
+	public function get_raw($field_name)
1193
+	{
1194
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1195
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1196
+			? $this->_fields[ $field_name ]->format('U')
1197
+			: $this->_fields[ $field_name ];
1198
+	}
1199
+
1200
+
1201
+	/**
1202
+	 * This is used to return the internal DateTime object used for a field that is a
1203
+	 * EE_Datetime_Field.
1204
+	 *
1205
+	 * @param string $field_name               The field name retrieving the DateTime object.
1206
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1207
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1208
+	 *                                         EE_Datetime_Field and but the field value is null, then
1209
+	 *                                         just null is returned (because that indicates that likely
1210
+	 *                                         this field is nullable).
1211
+	 * @throws InvalidArgumentException
1212
+	 * @throws InvalidDataTypeException
1213
+	 * @throws InvalidInterfaceException
1214
+	 * @throws ReflectionException
1215
+	 */
1216
+	public function get_DateTime_object($field_name)
1217
+	{
1218
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1219
+		if (! $field_settings instanceof EE_Datetime_Field) {
1220
+			EE_Error::add_error(
1221
+				sprintf(
1222
+					esc_html__(
1223
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1224
+						'event_espresso'
1225
+					),
1226
+					$field_name
1227
+				),
1228
+				__FILE__,
1229
+				__FUNCTION__,
1230
+				__LINE__
1231
+			);
1232
+			return false;
1233
+		}
1234
+		return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1235
+			? clone $this->_fields[ $field_name ]
1236
+			: null;
1237
+	}
1238
+
1239
+
1240
+	/**
1241
+	 * To be used in template to immediately echo out the value, and format it for output.
1242
+	 * Eg, should call stripslashes and whatnot before echoing
1243
+	 *
1244
+	 * @param string $field_name      the name of the field as it appears in the DB
1245
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1246
+	 *                                (in cases where the same property may be used for different outputs
1247
+	 *                                - i.e. datetime, money etc.)
1248
+	 * @return void
1249
+	 * @throws ReflectionException
1250
+	 * @throws InvalidArgumentException
1251
+	 * @throws InvalidInterfaceException
1252
+	 * @throws InvalidDataTypeException
1253
+	 * @throws EE_Error
1254
+	 */
1255
+	public function e($field_name, $extra_cache_ref = null)
1256
+	{
1257
+		echo wp_kses($this->get_pretty($field_name, $extra_cache_ref), AllowedTags::getWithFormTags());
1258
+	}
1259
+
1260
+
1261
+	/**
1262
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1263
+	 * can be easily used as the value of form input.
1264
+	 *
1265
+	 * @param string $field_name
1266
+	 * @return void
1267
+	 * @throws ReflectionException
1268
+	 * @throws InvalidArgumentException
1269
+	 * @throws InvalidInterfaceException
1270
+	 * @throws InvalidDataTypeException
1271
+	 * @throws EE_Error
1272
+	 */
1273
+	public function f($field_name)
1274
+	{
1275
+		$this->e($field_name, 'form_input');
1276
+	}
1277
+
1278
+
1279
+	/**
1280
+	 * Same as `f()` but just returns the value instead of echoing it
1281
+	 *
1282
+	 * @param string $field_name
1283
+	 * @return string
1284
+	 * @throws ReflectionException
1285
+	 * @throws InvalidArgumentException
1286
+	 * @throws InvalidInterfaceException
1287
+	 * @throws InvalidDataTypeException
1288
+	 * @throws EE_Error
1289
+	 */
1290
+	public function get_f($field_name)
1291
+	{
1292
+		return (string) $this->get_pretty($field_name, 'form_input');
1293
+	}
1294
+
1295
+
1296
+	/**
1297
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1298
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1299
+	 * to see what options are available.
1300
+	 *
1301
+	 * @param string $field_name
1302
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1303
+	 *                                (in cases where the same property may be used for different outputs
1304
+	 *                                - i.e. datetime, money etc.)
1305
+	 * @return mixed
1306
+	 * @throws ReflectionException
1307
+	 * @throws InvalidArgumentException
1308
+	 * @throws InvalidInterfaceException
1309
+	 * @throws InvalidDataTypeException
1310
+	 * @throws EE_Error
1311
+	 */
1312
+	public function get_pretty($field_name, $extra_cache_ref = null)
1313
+	{
1314
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1315
+	}
1316
+
1317
+
1318
+	/**
1319
+	 * This simply returns the datetime for the given field name
1320
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1321
+	 * (and the equivalent e_date, e_time, e_datetime).
1322
+	 *
1323
+	 * @access   protected
1324
+	 * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1325
+	 * @param string   $dt_frmt      valid datetime format used for date
1326
+	 *                               (if '' then we just use the default on the field,
1327
+	 *                               if NULL we use the last-used format)
1328
+	 * @param string   $tm_frmt      Same as above except this is for time format
1329
+	 * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1330
+	 * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1331
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1332
+	 *                               if field is not a valid dtt field, or void if echoing
1333
+	 * @throws ReflectionException
1334
+	 * @throws InvalidArgumentException
1335
+	 * @throws InvalidInterfaceException
1336
+	 * @throws InvalidDataTypeException
1337
+	 * @throws EE_Error
1338
+	 */
1339
+	protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1340
+	{
1341
+		// clear cached property
1342
+		$this->_clear_cached_property($field_name);
1343
+		// reset format properties because they are used in get()
1344
+		$this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1345
+		$this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1346
+		if ($echo) {
1347
+			$this->e($field_name, $date_or_time);
1348
+			return '';
1349
+		}
1350
+		return $this->get($field_name, $date_or_time);
1351
+	}
1352
+
1353
+
1354
+	/**
1355
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1356
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1357
+	 * other echoes the pretty value for dtt)
1358
+	 *
1359
+	 * @param  string $field_name name of model object datetime field holding the value
1360
+	 * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1361
+	 * @return string            datetime value formatted
1362
+	 * @throws ReflectionException
1363
+	 * @throws InvalidArgumentException
1364
+	 * @throws InvalidInterfaceException
1365
+	 * @throws InvalidDataTypeException
1366
+	 * @throws EE_Error
1367
+	 */
1368
+	public function get_date($field_name, $format = '')
1369
+	{
1370
+		return $this->_get_datetime($field_name, $format, null, 'D');
1371
+	}
1372
+
1373
+
1374
+	/**
1375
+	 * @param        $field_name
1376
+	 * @param string $format
1377
+	 * @throws ReflectionException
1378
+	 * @throws InvalidArgumentException
1379
+	 * @throws InvalidInterfaceException
1380
+	 * @throws InvalidDataTypeException
1381
+	 * @throws EE_Error
1382
+	 */
1383
+	public function e_date($field_name, $format = '')
1384
+	{
1385
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1386
+	}
1387
+
1388
+
1389
+	/**
1390
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1391
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1392
+	 * other echoes the pretty value for dtt)
1393
+	 *
1394
+	 * @param  string $field_name name of model object datetime field holding the value
1395
+	 * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1396
+	 * @return string             datetime value formatted
1397
+	 * @throws ReflectionException
1398
+	 * @throws InvalidArgumentException
1399
+	 * @throws InvalidInterfaceException
1400
+	 * @throws InvalidDataTypeException
1401
+	 * @throws EE_Error
1402
+	 */
1403
+	public function get_time($field_name, $format = '')
1404
+	{
1405
+		return $this->_get_datetime($field_name, null, $format, 'T');
1406
+	}
1407
+
1408
+
1409
+	/**
1410
+	 * @param        $field_name
1411
+	 * @param string $format
1412
+	 * @throws ReflectionException
1413
+	 * @throws InvalidArgumentException
1414
+	 * @throws InvalidInterfaceException
1415
+	 * @throws InvalidDataTypeException
1416
+	 * @throws EE_Error
1417
+	 */
1418
+	public function e_time($field_name, $format = '')
1419
+	{
1420
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1421
+	}
1422
+
1423
+
1424
+	/**
1425
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1426
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1427
+	 * other echoes the pretty value for dtt)
1428
+	 *
1429
+	 * @param  string $field_name name of model object datetime field holding the value
1430
+	 * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1431
+	 * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1432
+	 * @return string             datetime value formatted
1433
+	 * @throws ReflectionException
1434
+	 * @throws InvalidArgumentException
1435
+	 * @throws InvalidInterfaceException
1436
+	 * @throws InvalidDataTypeException
1437
+	 * @throws EE_Error
1438
+	 */
1439
+	public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1440
+	{
1441
+		return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1442
+	}
1443
+
1444
+
1445
+	/**
1446
+	 * @param string $field_name
1447
+	 * @param string $dt_frmt
1448
+	 * @param string $tm_frmt
1449
+	 * @throws ReflectionException
1450
+	 * @throws InvalidArgumentException
1451
+	 * @throws InvalidInterfaceException
1452
+	 * @throws InvalidDataTypeException
1453
+	 * @throws EE_Error
1454
+	 */
1455
+	public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1456
+	{
1457
+		$this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1458
+	}
1459
+
1460
+
1461
+	/**
1462
+	 * Get the i8ln value for a date using the WordPress @see date_i18n function.
1463
+	 *
1464
+	 * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1465
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1466
+	 *                           on the object will be used.
1467
+	 * @return string Date and time string in set locale or false if no field exists for the given
1468
+	 * @throws ReflectionException
1469
+	 * @throws InvalidArgumentException
1470
+	 * @throws InvalidInterfaceException
1471
+	 * @throws InvalidDataTypeException
1472
+	 * @throws EE_Error
1473
+	 *                           field name.
1474
+	 */
1475
+	public function get_i18n_datetime($field_name, $format = '')
1476
+	{
1477
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1478
+		return date_i18n(
1479
+			$format,
1480
+			EEH_DTT_Helper::get_timestamp_with_offset(
1481
+				$this->get_raw($field_name),
1482
+				$this->_timezone
1483
+			)
1484
+		);
1485
+	}
1486
+
1487
+
1488
+	/**
1489
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
1490
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1491
+	 * thrown.
1492
+	 *
1493
+	 * @param  string $field_name The field name being checked
1494
+	 * @throws ReflectionException
1495
+	 * @throws InvalidArgumentException
1496
+	 * @throws InvalidInterfaceException
1497
+	 * @throws InvalidDataTypeException
1498
+	 * @throws EE_Error
1499
+	 * @return EE_Datetime_Field
1500
+	 */
1501
+	protected function _get_dtt_field_settings($field_name)
1502
+	{
1503
+		$field = $this->get_model()->field_settings_for($field_name);
1504
+		// check if field is dtt
1505
+		if ($field instanceof EE_Datetime_Field) {
1506
+			return $field;
1507
+		}
1508
+		throw new EE_Error(
1509
+			sprintf(
1510
+				esc_html__(
1511
+					'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1512
+					'event_espresso'
1513
+				),
1514
+				$field_name,
1515
+				self::_get_model_classname(get_class($this))
1516
+			)
1517
+		);
1518
+	}
1519
+
1520
+
1521
+
1522
+
1523
+	/**
1524
+	 * NOTE ABOUT BELOW:
1525
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1526
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1527
+	 * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1528
+	 * method and make sure you send the entire datetime value for setting.
1529
+	 */
1530
+	/**
1531
+	 * sets the time on a datetime property
1532
+	 *
1533
+	 * @access protected
1534
+	 * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1535
+	 * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1536
+	 * @throws ReflectionException
1537
+	 * @throws InvalidArgumentException
1538
+	 * @throws InvalidInterfaceException
1539
+	 * @throws InvalidDataTypeException
1540
+	 * @throws EE_Error
1541
+	 */
1542
+	protected function _set_time_for($time, $fieldname)
1543
+	{
1544
+		$this->_set_date_time('T', $time, $fieldname);
1545
+	}
1546
+
1547
+
1548
+	/**
1549
+	 * sets the date on a datetime property
1550
+	 *
1551
+	 * @access protected
1552
+	 * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1553
+	 * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1554
+	 * @throws ReflectionException
1555
+	 * @throws InvalidArgumentException
1556
+	 * @throws InvalidInterfaceException
1557
+	 * @throws InvalidDataTypeException
1558
+	 * @throws EE_Error
1559
+	 */
1560
+	protected function _set_date_for($date, $fieldname)
1561
+	{
1562
+		$this->_set_date_time('D', $date, $fieldname);
1563
+	}
1564
+
1565
+
1566
+	/**
1567
+	 * This takes care of setting a date or time independently on a given model object property. This method also
1568
+	 * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1569
+	 *
1570
+	 * @access protected
1571
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1572
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1573
+	 * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1574
+	 *                                        EE_Datetime_Field property)
1575
+	 * @throws ReflectionException
1576
+	 * @throws InvalidArgumentException
1577
+	 * @throws InvalidInterfaceException
1578
+	 * @throws InvalidDataTypeException
1579
+	 * @throws EE_Error
1580
+	 */
1581
+	protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1582
+	{
1583
+		$field = $this->_get_dtt_field_settings($fieldname);
1584
+		$field->set_timezone($this->_timezone);
1585
+		$field->set_date_format($this->_dt_frmt);
1586
+		$field->set_time_format($this->_tm_frmt);
1587
+		switch ($what) {
1588
+			case 'T':
1589
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1590
+					$datetime_value,
1591
+					$this->_fields[ $fieldname ]
1592
+				);
1593
+				$this->_has_changes = true;
1594
+				break;
1595
+			case 'D':
1596
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1597
+					$datetime_value,
1598
+					$this->_fields[ $fieldname ]
1599
+				);
1600
+				$this->_has_changes = true;
1601
+				break;
1602
+			case 'B':
1603
+				$this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1604
+				$this->_has_changes = true;
1605
+				break;
1606
+		}
1607
+		$this->_clear_cached_property($fieldname);
1608
+	}
1609
+
1610
+
1611
+	/**
1612
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1613
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1614
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1615
+	 * that could lead to some unexpected results!
1616
+	 *
1617
+	 * @access public
1618
+	 * @param string $field_name               This is the name of the field on the object that contains the date/time
1619
+	 *                                         value being returned.
1620
+	 * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1621
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1622
+	 * @param string $prepend                  You can include something to prepend on the timestamp
1623
+	 * @param string $append                   You can include something to append on the timestamp
1624
+	 * @throws ReflectionException
1625
+	 * @throws InvalidArgumentException
1626
+	 * @throws InvalidInterfaceException
1627
+	 * @throws InvalidDataTypeException
1628
+	 * @throws EE_Error
1629
+	 * @return string timestamp
1630
+	 */
1631
+	public function display_in_my_timezone(
1632
+		$field_name,
1633
+		$callback = 'get_datetime',
1634
+		$args = null,
1635
+		$prepend = '',
1636
+		$append = ''
1637
+	) {
1638
+		$timezone = EEH_DTT_Helper::get_timezone();
1639
+		if ($timezone === $this->_timezone) {
1640
+			return '';
1641
+		}
1642
+		$original_timezone = $this->_timezone;
1643
+		$this->set_timezone($timezone);
1644
+		$fn = (array) $field_name;
1645
+		$args = array_merge($fn, (array) $args);
1646
+		if (! method_exists($this, $callback)) {
1647
+			throw new EE_Error(
1648
+				sprintf(
1649
+					esc_html__(
1650
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1651
+						'event_espresso'
1652
+					),
1653
+					$callback
1654
+				)
1655
+			);
1656
+		}
1657
+		$args = (array) $args;
1658
+		$return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1659
+		$this->set_timezone($original_timezone);
1660
+		return $return;
1661
+	}
1662
+
1663
+
1664
+	/**
1665
+	 * Deletes this model object.
1666
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1667
+	 * override
1668
+	 * `EE_Base_Class::_delete` NOT this class.
1669
+	 *
1670
+	 * @return boolean | int
1671
+	 * @throws ReflectionException
1672
+	 * @throws InvalidArgumentException
1673
+	 * @throws InvalidInterfaceException
1674
+	 * @throws InvalidDataTypeException
1675
+	 * @throws EE_Error
1676
+	 */
1677
+	public function delete()
1678
+	{
1679
+		/**
1680
+		 * Called just before the `EE_Base_Class::_delete` method call.
1681
+		 * Note:
1682
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1683
+		 * should be aware that `_delete` may not always result in a permanent delete.
1684
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
1685
+		 * soft deletes (trash) the object and does not permanently delete it.
1686
+		 *
1687
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1688
+		 */
1689
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
1690
+		$result = $this->_delete();
1691
+		/**
1692
+		 * Called just after the `EE_Base_Class::_delete` method call.
1693
+		 * Note:
1694
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1695
+		 * should be aware that `_delete` may not always result in a permanent delete.
1696
+		 * For example `EE_Soft_Base_Class::_delete`
1697
+		 * soft deletes (trash) the object and does not permanently delete it.
1698
+		 *
1699
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1700
+		 * @param boolean       $result
1701
+		 */
1702
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1703
+		return $result;
1704
+	}
1705
+
1706
+
1707
+	/**
1708
+	 * Calls the specific delete method for the instantiated class.
1709
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1710
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1711
+	 * `EE_Base_Class::delete`
1712
+	 *
1713
+	 * @return bool|int
1714
+	 * @throws ReflectionException
1715
+	 * @throws InvalidArgumentException
1716
+	 * @throws InvalidInterfaceException
1717
+	 * @throws InvalidDataTypeException
1718
+	 * @throws EE_Error
1719
+	 */
1720
+	protected function _delete()
1721
+	{
1722
+		return $this->delete_permanently();
1723
+	}
1724
+
1725
+
1726
+	/**
1727
+	 * Deletes this model object permanently from db
1728
+	 * (but keep in mind related models may block the delete and return an error)
1729
+	 *
1730
+	 * @return bool | int
1731
+	 * @throws ReflectionException
1732
+	 * @throws InvalidArgumentException
1733
+	 * @throws InvalidInterfaceException
1734
+	 * @throws InvalidDataTypeException
1735
+	 * @throws EE_Error
1736
+	 */
1737
+	public function delete_permanently()
1738
+	{
1739
+		/**
1740
+		 * Called just before HARD deleting a model object
1741
+		 *
1742
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1743
+		 */
1744
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1745
+		$model = $this->get_model();
1746
+		$result = $model->delete_permanently_by_ID($this->ID());
1747
+		$this->refresh_cache_of_related_objects();
1748
+		/**
1749
+		 * Called just after HARD deleting a model object
1750
+		 *
1751
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1752
+		 * @param boolean       $result
1753
+		 */
1754
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1755
+		return $result;
1756
+	}
1757
+
1758
+
1759
+	/**
1760
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1761
+	 * related model objects
1762
+	 *
1763
+	 * @throws ReflectionException
1764
+	 * @throws InvalidArgumentException
1765
+	 * @throws InvalidInterfaceException
1766
+	 * @throws InvalidDataTypeException
1767
+	 * @throws EE_Error
1768
+	 */
1769
+	public function refresh_cache_of_related_objects()
1770
+	{
1771
+		$model = $this->get_model();
1772
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1773
+			if (! empty($this->_model_relations[ $relation_name ])) {
1774
+				$related_objects = $this->_model_relations[ $relation_name ];
1775
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
1776
+					// this relation only stores a single model object, not an array
1777
+					// but let's make it consistent
1778
+					$related_objects = array($related_objects);
1779
+				}
1780
+				foreach ($related_objects as $related_object) {
1781
+					// only refresh their cache if they're in memory
1782
+					if ($related_object instanceof EE_Base_Class) {
1783
+						$related_object->clear_cache(
1784
+							$model->get_this_model_name(),
1785
+							$this
1786
+						);
1787
+					}
1788
+				}
1789
+			}
1790
+		}
1791
+	}
1792
+
1793
+
1794
+	/**
1795
+	 *        Saves this object to the database. An array may be supplied to set some values on this
1796
+	 * object just before saving.
1797
+	 *
1798
+	 * @access public
1799
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
1800
+	 *                                 if provided during the save() method (often client code will change the fields'
1801
+	 *                                 values before calling save)
1802
+	 * @return bool|int|string         1 on a successful update
1803
+	 *                                 the ID of the new entry on insert
1804
+	 *                                 0 on failure or if the model object isn't allowed to persist
1805
+	 *                                 (as determined by EE_Base_Class::allow_persist())
1806
+	 * @throws InvalidInterfaceException
1807
+	 * @throws InvalidDataTypeException
1808
+	 * @throws EE_Error
1809
+	 * @throws InvalidArgumentException
1810
+	 * @throws ReflectionException
1811
+	 * @throws ReflectionException
1812
+	 * @throws ReflectionException
1813
+	 */
1814
+	public function save($set_cols_n_values = array())
1815
+	{
1816
+		$model = $this->get_model();
1817
+		/**
1818
+		 * Filters the fields we're about to save on the model object
1819
+		 *
1820
+		 * @param array         $set_cols_n_values
1821
+		 * @param EE_Base_Class $model_object
1822
+		 */
1823
+		$set_cols_n_values = (array) apply_filters(
1824
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
1825
+			$set_cols_n_values,
1826
+			$this
1827
+		);
1828
+		// set attributes as provided in $set_cols_n_values
1829
+		foreach ($set_cols_n_values as $column => $value) {
1830
+			$this->set($column, $value);
1831
+		}
1832
+		// no changes ? then don't do anything
1833
+		if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1834
+			return 0;
1835
+		}
1836
+		/**
1837
+		 * Saving a model object.
1838
+		 * Before we perform a save, this action is fired.
1839
+		 *
1840
+		 * @param EE_Base_Class $model_object the model object about to be saved.
1841
+		 */
1842
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
1843
+		if (! $this->allow_persist()) {
1844
+			return 0;
1845
+		}
1846
+		// now get current attribute values
1847
+		$save_cols_n_values = $this->_fields;
1848
+		// if the object already has an ID, update it. Otherwise, insert it
1849
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
1850
+		// They have been
1851
+		$old_assumption_concerning_value_preparation = $model
1852
+			->get_assumption_concerning_values_already_prepared_by_model_object();
1853
+		$model->assume_values_already_prepared_by_model_object(true);
1854
+		// does this model have an autoincrement PK?
1855
+		if ($model->has_primary_key_field()) {
1856
+			if ($model->get_primary_key_field()->is_auto_increment()) {
1857
+				// ok check if it's set, if so: update; if not, insert
1858
+				if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1859
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1860
+				} else {
1861
+					unset($save_cols_n_values[ $model->primary_key_name() ]);
1862
+					$results = $model->insert($save_cols_n_values);
1863
+					if ($results) {
1864
+						// if successful, set the primary key
1865
+						// but don't use the normal SET method, because it will check if
1866
+						// an item with the same ID exists in the mapper & db, then
1867
+						// will find it in the db (because we just added it) and THAT object
1868
+						// will get added to the mapper before we can add this one!
1869
+						// but if we just avoid using the SET method, all that headache can be avoided
1870
+						$pk_field_name = $model->primary_key_name();
1871
+						$this->_fields[ $pk_field_name ] = $results;
1872
+						$this->_clear_cached_property($pk_field_name);
1873
+						$model->add_to_entity_map($this);
1874
+						$this->_update_cached_related_model_objs_fks();
1875
+					}
1876
+				}
1877
+			} else {// PK is NOT auto-increment
1878
+				// so check if one like it already exists in the db
1879
+				if ($model->exists_by_ID($this->ID())) {
1880
+					if (WP_DEBUG && ! $this->in_entity_map()) {
1881
+						throw new EE_Error(
1882
+							sprintf(
1883
+								esc_html__(
1884
+									'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1885
+									'event_espresso'
1886
+								),
1887
+								get_class($this),
1888
+								get_class($model) . '::instance()->add_to_entity_map()',
1889
+								get_class($model) . '::instance()->get_one_by_ID()',
1890
+								'<br />'
1891
+							)
1892
+						);
1893
+					}
1894
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1895
+				} else {
1896
+					$results = $model->insert($save_cols_n_values);
1897
+					$this->_update_cached_related_model_objs_fks();
1898
+				}
1899
+			}
1900
+		} else {// there is NO primary key
1901
+			$already_in_db = false;
1902
+			foreach ($model->unique_indexes() as $index) {
1903
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1904
+				if ($model->exists(array($uniqueness_where_params))) {
1905
+					$already_in_db = true;
1906
+				}
1907
+			}
1908
+			if ($already_in_db) {
1909
+				$combined_pk_fields_n_values = array_intersect_key(
1910
+					$save_cols_n_values,
1911
+					$model->get_combined_primary_key_fields()
1912
+				);
1913
+				$results = $model->update(
1914
+					$save_cols_n_values,
1915
+					$combined_pk_fields_n_values
1916
+				);
1917
+			} else {
1918
+				$results = $model->insert($save_cols_n_values);
1919
+			}
1920
+		}
1921
+		// restore the old assumption about values being prepared by the model object
1922
+		$model->assume_values_already_prepared_by_model_object(
1923
+			$old_assumption_concerning_value_preparation
1924
+		);
1925
+		/**
1926
+		 * After saving the model object this action is called
1927
+		 *
1928
+		 * @param EE_Base_Class $model_object which was just saved
1929
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1930
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1931
+		 */
1932
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1933
+		$this->_has_changes = false;
1934
+		return $results;
1935
+	}
1936
+
1937
+
1938
+	/**
1939
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1940
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1941
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1942
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1943
+	 * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1944
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1945
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1946
+	 *
1947
+	 * @return void
1948
+	 * @throws ReflectionException
1949
+	 * @throws InvalidArgumentException
1950
+	 * @throws InvalidInterfaceException
1951
+	 * @throws InvalidDataTypeException
1952
+	 * @throws EE_Error
1953
+	 */
1954
+	protected function _update_cached_related_model_objs_fks()
1955
+	{
1956
+		$model = $this->get_model();
1957
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1958
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1959
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1960
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1961
+						$model->get_this_model_name()
1962
+					);
1963
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1964
+					if ($related_model_obj_in_cache->ID()) {
1965
+						$related_model_obj_in_cache->save();
1966
+					}
1967
+				}
1968
+			}
1969
+		}
1970
+	}
1971
+
1972
+
1973
+	/**
1974
+	 * Saves this model object and its NEW cached relations to the database.
1975
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1976
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
1977
+	 * because otherwise, there's a potential for infinite looping of saving
1978
+	 * Saves the cached related model objects, and ensures the relation between them
1979
+	 * and this object and properly setup
1980
+	 *
1981
+	 * @return int ID of new model object on save; 0 on failure+
1982
+	 * @throws ReflectionException
1983
+	 * @throws InvalidArgumentException
1984
+	 * @throws InvalidInterfaceException
1985
+	 * @throws InvalidDataTypeException
1986
+	 * @throws EE_Error
1987
+	 */
1988
+	public function save_new_cached_related_model_objs()
1989
+	{
1990
+		// make sure this has been saved
1991
+		if (! $this->ID()) {
1992
+			$id = $this->save();
1993
+		} else {
1994
+			$id = $this->ID();
1995
+		}
1996
+		// now save all the NEW cached model objects  (ie they don't exist in the DB)
1997
+		foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1998
+			if ($this->_model_relations[ $relationName ]) {
1999
+				// is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2000
+				// or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2001
+				/* @var $related_model_obj EE_Base_Class */
2002
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
2003
+					// add a relation to that relation type (which saves the appropriate thing in the process)
2004
+					// but ONLY if it DOES NOT exist in the DB
2005
+					$related_model_obj = $this->_model_relations[ $relationName ];
2006
+					// if( ! $related_model_obj->ID()){
2007
+					$this->_add_relation_to($related_model_obj, $relationName);
2008
+					$related_model_obj->save_new_cached_related_model_objs();
2009
+					// }
2010
+				} else {
2011
+					foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2012
+						// add a relation to that relation type (which saves the appropriate thing in the process)
2013
+						// but ONLY if it DOES NOT exist in the DB
2014
+						// if( ! $related_model_obj->ID()){
2015
+						$this->_add_relation_to($related_model_obj, $relationName);
2016
+						$related_model_obj->save_new_cached_related_model_objs();
2017
+						// }
2018
+					}
2019
+				}
2020
+			}
2021
+		}
2022
+		return $id;
2023
+	}
2024
+
2025
+
2026
+	/**
2027
+	 * for getting a model while instantiated.
2028
+	 *
2029
+	 * @return EEM_Base | EEM_CPT_Base
2030
+	 * @throws ReflectionException
2031
+	 * @throws InvalidArgumentException
2032
+	 * @throws InvalidInterfaceException
2033
+	 * @throws InvalidDataTypeException
2034
+	 * @throws EE_Error
2035
+	 */
2036
+	public function get_model()
2037
+	{
2038
+		if (! $this->_model) {
2039
+			$modelName = self::_get_model_classname(get_class($this));
2040
+			$this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2041
+		} else {
2042
+			$this->_model->set_timezone($this->_timezone);
2043
+		}
2044
+		return $this->_model;
2045
+	}
2046
+
2047
+
2048
+	/**
2049
+	 * @param $props_n_values
2050
+	 * @param $classname
2051
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2052
+	 * @throws ReflectionException
2053
+	 * @throws InvalidArgumentException
2054
+	 * @throws InvalidInterfaceException
2055
+	 * @throws InvalidDataTypeException
2056
+	 * @throws EE_Error
2057
+	 */
2058
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2059
+	{
2060
+		// TODO: will not work for Term_Relationships because they have no PK!
2061
+		$primary_id_ref = self::_get_primary_key_name($classname);
2062
+		if (
2063
+			array_key_exists($primary_id_ref, $props_n_values)
2064
+			&& ! empty($props_n_values[ $primary_id_ref ])
2065
+		) {
2066
+			$id = $props_n_values[ $primary_id_ref ];
2067
+			return self::_get_model($classname)->get_from_entity_map($id);
2068
+		}
2069
+		return false;
2070
+	}
2071
+
2072
+
2073
+	/**
2074
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2075
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2076
+	 * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2077
+	 * we return false.
2078
+	 *
2079
+	 * @param  array  $props_n_values   incoming array of properties and their values
2080
+	 * @param  string $classname        the classname of the child class
2081
+	 * @param null    $timezone
2082
+	 * @param array   $date_formats     incoming date_formats in an array where the first value is the
2083
+	 *                                  date_format and the second value is the time format
2084
+	 * @return mixed (EE_Base_Class|bool)
2085
+	 * @throws InvalidArgumentException
2086
+	 * @throws InvalidInterfaceException
2087
+	 * @throws InvalidDataTypeException
2088
+	 * @throws EE_Error
2089
+	 * @throws ReflectionException
2090
+	 * @throws ReflectionException
2091
+	 * @throws ReflectionException
2092
+	 */
2093
+	protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2094
+	{
2095
+		$existing = null;
2096
+		$model = self::_get_model($classname, $timezone);
2097
+		if ($model->has_primary_key_field()) {
2098
+			$primary_id_ref = self::_get_primary_key_name($classname);
2099
+			if (
2100
+				array_key_exists($primary_id_ref, $props_n_values)
2101
+				&& ! empty($props_n_values[ $primary_id_ref ])
2102
+			) {
2103
+				$existing = $model->get_one_by_ID(
2104
+					$props_n_values[ $primary_id_ref ]
2105
+				);
2106
+			}
2107
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2108
+			// no primary key on this model, but there's still a matching item in the DB
2109
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2110
+				self::_get_model($classname, $timezone)
2111
+					->get_index_primary_key_string($props_n_values)
2112
+			);
2113
+		}
2114
+		if ($existing) {
2115
+			// set date formats if present before setting values
2116
+			if (! empty($date_formats) && is_array($date_formats)) {
2117
+				$existing->set_date_format($date_formats[0]);
2118
+				$existing->set_time_format($date_formats[1]);
2119
+			} else {
2120
+				// set default formats for date and time
2121
+				$existing->set_date_format(get_option('date_format'));
2122
+				$existing->set_time_format(get_option('time_format'));
2123
+			}
2124
+			foreach ($props_n_values as $property => $field_value) {
2125
+				$existing->set($property, $field_value);
2126
+			}
2127
+			return $existing;
2128
+		}
2129
+		return false;
2130
+	}
2131
+
2132
+
2133
+	/**
2134
+	 * Gets the EEM_*_Model for this class
2135
+	 *
2136
+	 * @access public now, as this is more convenient
2137
+	 * @param      $classname
2138
+	 * @param null $timezone
2139
+	 * @throws ReflectionException
2140
+	 * @throws InvalidArgumentException
2141
+	 * @throws InvalidInterfaceException
2142
+	 * @throws InvalidDataTypeException
2143
+	 * @throws EE_Error
2144
+	 * @return EEM_Base
2145
+	 */
2146
+	protected static function _get_model($classname, $timezone = null)
2147
+	{
2148
+		// find model for this class
2149
+		if (! $classname) {
2150
+			throw new EE_Error(
2151
+				sprintf(
2152
+					esc_html__(
2153
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2154
+						'event_espresso'
2155
+					),
2156
+					$classname
2157
+				)
2158
+			);
2159
+		}
2160
+		$modelName = self::_get_model_classname($classname);
2161
+		return self::_get_model_instance_with_name($modelName, $timezone);
2162
+	}
2163
+
2164
+
2165
+	/**
2166
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2167
+	 *
2168
+	 * @param string $model_classname
2169
+	 * @param null   $timezone
2170
+	 * @return EEM_Base
2171
+	 * @throws ReflectionException
2172
+	 * @throws InvalidArgumentException
2173
+	 * @throws InvalidInterfaceException
2174
+	 * @throws InvalidDataTypeException
2175
+	 * @throws EE_Error
2176
+	 */
2177
+	protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2178
+	{
2179
+		$model_classname = str_replace('EEM_', '', $model_classname);
2180
+		$model = EE_Registry::instance()->load_model($model_classname);
2181
+		$model->set_timezone($timezone);
2182
+		return $model;
2183
+	}
2184
+
2185
+
2186
+	/**
2187
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
2188
+	 * Also works if a model class's classname is provided (eg EE_Registration).
2189
+	 *
2190
+	 * @param null $model_name
2191
+	 * @return string like EEM_Attendee
2192
+	 */
2193
+	private static function _get_model_classname($model_name = null)
2194
+	{
2195
+		if (strpos($model_name, 'EE_') === 0) {
2196
+			$model_classname = str_replace('EE_', 'EEM_', $model_name);
2197
+		} else {
2198
+			$model_classname = 'EEM_' . $model_name;
2199
+		}
2200
+		return $model_classname;
2201
+	}
2202
+
2203
+
2204
+	/**
2205
+	 * returns the name of the primary key attribute
2206
+	 *
2207
+	 * @param null $classname
2208
+	 * @throws ReflectionException
2209
+	 * @throws InvalidArgumentException
2210
+	 * @throws InvalidInterfaceException
2211
+	 * @throws InvalidDataTypeException
2212
+	 * @throws EE_Error
2213
+	 * @return string
2214
+	 */
2215
+	protected static function _get_primary_key_name($classname = null)
2216
+	{
2217
+		if (! $classname) {
2218
+			throw new EE_Error(
2219
+				sprintf(
2220
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2221
+					$classname
2222
+				)
2223
+			);
2224
+		}
2225
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
2226
+	}
2227
+
2228
+
2229
+	/**
2230
+	 * Gets the value of the primary key.
2231
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
2232
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2233
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2234
+	 *
2235
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2236
+	 * @throws ReflectionException
2237
+	 * @throws InvalidArgumentException
2238
+	 * @throws InvalidInterfaceException
2239
+	 * @throws InvalidDataTypeException
2240
+	 * @throws EE_Error
2241
+	 */
2242
+	public function ID()
2243
+	{
2244
+		$model = $this->get_model();
2245
+		// now that we know the name of the variable, use a variable variable to get its value and return its
2246
+		if ($model->has_primary_key_field()) {
2247
+			return $this->_fields[ $model->primary_key_name() ];
2248
+		}
2249
+		return $model->get_index_primary_key_string($this->_fields);
2250
+	}
2251
+
2252
+
2253
+	/**
2254
+	 * @param EE_Base_Class|int|string $otherModelObjectOrID
2255
+	 * @param string                   $relationName
2256
+	 * @return bool
2257
+	 * @throws EE_Error
2258
+	 * @throws ReflectionException
2259
+	 * @since   $VID:$
2260
+	 */
2261
+	public function hasRelation($otherModelObjectOrID, string $relationName): bool
2262
+	{
2263
+		$other_model = self::_get_model_instance_with_name(
2264
+			self::_get_model_classname($relationName),
2265
+			$this->_timezone
2266
+		);
2267
+		$primary_key = $other_model->primary_key_name();
2268
+		/** @var EE_Base_Class $otherModelObject */
2269
+		$otherModelObject = $other_model->ensure_is_obj($otherModelObjectOrID, $relationName);
2270
+		return $this->count_related($relationName, [[$primary_key => $otherModelObject->ID()]]) > 0;
2271
+	}
2272
+
2273
+
2274
+	/**
2275
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2276
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2277
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2278
+	 *
2279
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2280
+	 * @param string $relationName                     eg 'Events','Question',etc.
2281
+	 *                                                 an attendee to a group, you also want to specify which role they
2282
+	 *                                                 will have in that group. So you would use this parameter to
2283
+	 *                                                 specify array('role-column-name'=>'role-id')
2284
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2285
+	 *                                                 allow you to further constrict the relation to being added.
2286
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2287
+	 *                                                 column on the JOIN table and currently only the HABTM models
2288
+	 *                                                 accept these additional conditions.  Also remember that if an
2289
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2290
+	 *                                                 NEW row is created in the join table.
2291
+	 * @param null   $cache_id
2292
+	 * @throws ReflectionException
2293
+	 * @throws InvalidArgumentException
2294
+	 * @throws InvalidInterfaceException
2295
+	 * @throws InvalidDataTypeException
2296
+	 * @throws EE_Error
2297
+	 * @return EE_Base_Class the object the relation was added to
2298
+	 */
2299
+	public function _add_relation_to(
2300
+		$otherObjectModelObjectOrID,
2301
+		$relationName,
2302
+		$extra_join_model_fields_n_values = array(),
2303
+		$cache_id = null
2304
+	) {
2305
+		$model = $this->get_model();
2306
+		// if this thing exists in the DB, save the relation to the DB
2307
+		if ($this->ID()) {
2308
+			$otherObject = $model->add_relationship_to(
2309
+				$this,
2310
+				$otherObjectModelObjectOrID,
2311
+				$relationName,
2312
+				$extra_join_model_fields_n_values
2313
+			);
2314
+			// clear cache so future get_many_related and get_first_related() return new results.
2315
+			$this->clear_cache($relationName, $otherObject, true);
2316
+			if ($otherObject instanceof EE_Base_Class) {
2317
+				$otherObject->clear_cache($model->get_this_model_name(), $this);
2318
+			}
2319
+		} else {
2320
+			// this thing doesn't exist in the DB,  so just cache it
2321
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2322
+				throw new EE_Error(
2323
+					sprintf(
2324
+						esc_html__(
2325
+							'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2326
+							'event_espresso'
2327
+						),
2328
+						$otherObjectModelObjectOrID,
2329
+						get_class($this)
2330
+					)
2331
+				);
2332
+			}
2333
+			$otherObject = $otherObjectModelObjectOrID;
2334
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2335
+		}
2336
+		if ($otherObject instanceof EE_Base_Class) {
2337
+			// fix the reciprocal relation too
2338
+			if ($otherObject->ID()) {
2339
+				// its saved so assumed relations exist in the DB, so we can just
2340
+				// clear the cache so future queries use the updated info in the DB
2341
+				$otherObject->clear_cache(
2342
+					$model->get_this_model_name(),
2343
+					null,
2344
+					true
2345
+				);
2346
+			} else {
2347
+				// it's not saved, so it caches relations like this
2348
+				$otherObject->cache($model->get_this_model_name(), $this);
2349
+			}
2350
+		}
2351
+		return $otherObject;
2352
+	}
2353
+
2354
+
2355
+	/**
2356
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2357
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2358
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2359
+	 * from the cache
2360
+	 *
2361
+	 * @param mixed  $otherObjectModelObjectOrID
2362
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2363
+	 *                to the DB yet
2364
+	 * @param string $relationName
2365
+	 * @param array  $where_query
2366
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2367
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2368
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2369
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2370
+	 *                deleted.
2371
+	 * @return EE_Base_Class the relation was removed from
2372
+	 * @throws ReflectionException
2373
+	 * @throws InvalidArgumentException
2374
+	 * @throws InvalidInterfaceException
2375
+	 * @throws InvalidDataTypeException
2376
+	 * @throws EE_Error
2377
+	 */
2378
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2379
+	{
2380
+		if ($this->ID()) {
2381
+			// if this exists in the DB, save the relation change to the DB too
2382
+			$otherObject = $this->get_model()->remove_relationship_to(
2383
+				$this,
2384
+				$otherObjectModelObjectOrID,
2385
+				$relationName,
2386
+				$where_query
2387
+			);
2388
+			$this->clear_cache(
2389
+				$relationName,
2390
+				$otherObject
2391
+			);
2392
+		} else {
2393
+			// this doesn't exist in the DB, just remove it from the cache
2394
+			$otherObject = $this->clear_cache(
2395
+				$relationName,
2396
+				$otherObjectModelObjectOrID
2397
+			);
2398
+		}
2399
+		if ($otherObject instanceof EE_Base_Class) {
2400
+			$otherObject->clear_cache(
2401
+				$this->get_model()->get_this_model_name(),
2402
+				$this
2403
+			);
2404
+		}
2405
+		return $otherObject;
2406
+	}
2407
+
2408
+
2409
+	/**
2410
+	 * Removes ALL the related things for the $relationName.
2411
+	 *
2412
+	 * @param string $relationName
2413
+	 * @param array  $where_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2414
+	 * @return EE_Base_Class
2415
+	 * @throws ReflectionException
2416
+	 * @throws InvalidArgumentException
2417
+	 * @throws InvalidInterfaceException
2418
+	 * @throws InvalidDataTypeException
2419
+	 * @throws EE_Error
2420
+	 */
2421
+	public function _remove_relations($relationName, $where_query_params = array())
2422
+	{
2423
+		if ($this->ID()) {
2424
+			// if this exists in the DB, save the relation change to the DB too
2425
+			$otherObjects = $this->get_model()->remove_relations(
2426
+				$this,
2427
+				$relationName,
2428
+				$where_query_params
2429
+			);
2430
+			$this->clear_cache(
2431
+				$relationName,
2432
+				null,
2433
+				true
2434
+			);
2435
+		} else {
2436
+			// this doesn't exist in the DB, just remove it from the cache
2437
+			$otherObjects = $this->clear_cache(
2438
+				$relationName,
2439
+				null,
2440
+				true
2441
+			);
2442
+		}
2443
+		if (is_array($otherObjects)) {
2444
+			foreach ($otherObjects as $otherObject) {
2445
+				$otherObject->clear_cache(
2446
+					$this->get_model()->get_this_model_name(),
2447
+					$this
2448
+				);
2449
+			}
2450
+		}
2451
+		return $otherObjects;
2452
+	}
2453
+
2454
+
2455
+	/**
2456
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
2457
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2458
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2459
+	 * because we want to get even deleted items etc.
2460
+	 *
2461
+	 * @param string $relationName key in the model's _model_relations array
2462
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2463
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2464
+	 *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2465
+	 *                             results if you want IDs
2466
+	 * @throws ReflectionException
2467
+	 * @throws InvalidArgumentException
2468
+	 * @throws InvalidInterfaceException
2469
+	 * @throws InvalidDataTypeException
2470
+	 * @throws EE_Error
2471
+	 */
2472
+	public function get_many_related($relationName, $query_params = array())
2473
+	{
2474
+		if ($this->ID()) {
2475
+			// this exists in the DB, so get the related things from either the cache or the DB
2476
+			// if there are query parameters, forget about caching the related model objects.
2477
+			if ($query_params) {
2478
+				$related_model_objects = $this->get_model()->get_all_related(
2479
+					$this,
2480
+					$relationName,
2481
+					$query_params
2482
+				);
2483
+			} else {
2484
+				// did we already cache the result of this query?
2485
+				$cached_results = $this->get_all_from_cache($relationName);
2486
+				if (! $cached_results) {
2487
+					$related_model_objects = $this->get_model()->get_all_related(
2488
+						$this,
2489
+						$relationName,
2490
+						$query_params
2491
+					);
2492
+					// if no query parameters were passed, then we got all the related model objects
2493
+					// for that relation. We can cache them then.
2494
+					foreach ($related_model_objects as $related_model_object) {
2495
+						$this->cache($relationName, $related_model_object);
2496
+					}
2497
+				} else {
2498
+					$related_model_objects = $cached_results;
2499
+				}
2500
+			}
2501
+		} else {
2502
+			// this doesn't exist in the DB, so just get the related things from the cache
2503
+			$related_model_objects = $this->get_all_from_cache($relationName);
2504
+		}
2505
+		return $related_model_objects;
2506
+	}
2507
+
2508
+
2509
+	/**
2510
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2511
+	 * unless otherwise specified in the $query_params
2512
+	 *
2513
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2514
+	 * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2515
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2516
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2517
+	 *                               that by the setting $distinct to TRUE;
2518
+	 * @return int
2519
+	 * @throws ReflectionException
2520
+	 * @throws InvalidArgumentException
2521
+	 * @throws InvalidInterfaceException
2522
+	 * @throws InvalidDataTypeException
2523
+	 * @throws EE_Error
2524
+	 */
2525
+	public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2526
+	{
2527
+		return $this->get_model()->count_related(
2528
+			$this,
2529
+			$relation_name,
2530
+			$query_params,
2531
+			$field_to_count,
2532
+			$distinct
2533
+		);
2534
+	}
2535
+
2536
+
2537
+	/**
2538
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2539
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2540
+	 *
2541
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2542
+	 * @param array  $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2543
+	 * @param string $field_to_sum  name of field to count by.
2544
+	 *                              By default, uses primary key
2545
+	 *                              (which doesn't make much sense, so you should probably change it)
2546
+	 * @return int
2547
+	 * @throws ReflectionException
2548
+	 * @throws InvalidArgumentException
2549
+	 * @throws InvalidInterfaceException
2550
+	 * @throws InvalidDataTypeException
2551
+	 * @throws EE_Error
2552
+	 */
2553
+	public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2554
+	{
2555
+		return $this->get_model()->sum_related(
2556
+			$this,
2557
+			$relation_name,
2558
+			$query_params,
2559
+			$field_to_sum
2560
+		);
2561
+	}
2562
+
2563
+
2564
+	/**
2565
+	 * Gets the first (ie, one) related model object of the specified type.
2566
+	 *
2567
+	 * @param string $relationName key in the model's _model_relations array
2568
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2569
+	 * @return EE_Base_Class (not an array, a single object)
2570
+	 * @throws ReflectionException
2571
+	 * @throws InvalidArgumentException
2572
+	 * @throws InvalidInterfaceException
2573
+	 * @throws InvalidDataTypeException
2574
+	 * @throws EE_Error
2575
+	 */
2576
+	public function get_first_related($relationName, $query_params = array())
2577
+	{
2578
+		$model = $this->get_model();
2579
+		if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2580
+			// if they've provided some query parameters, don't bother trying to cache the result
2581
+			// also make sure we're not caching the result of get_first_related
2582
+			// on a relation which should have an array of objects (because the cache might have an array of objects)
2583
+			if (
2584
+				$query_params
2585
+				|| ! $model->related_settings_for($relationName)
2586
+					 instanceof
2587
+					 EE_Belongs_To_Relation
2588
+			) {
2589
+				$related_model_object = $model->get_first_related(
2590
+					$this,
2591
+					$relationName,
2592
+					$query_params
2593
+				);
2594
+			} else {
2595
+				// first, check if we've already cached the result of this query
2596
+				$cached_result = $this->get_one_from_cache($relationName);
2597
+				if (! $cached_result) {
2598
+					$related_model_object = $model->get_first_related(
2599
+						$this,
2600
+						$relationName,
2601
+						$query_params
2602
+					);
2603
+					$this->cache($relationName, $related_model_object);
2604
+				} else {
2605
+					$related_model_object = $cached_result;
2606
+				}
2607
+			}
2608
+		} else {
2609
+			$related_model_object = null;
2610
+			// this doesn't exist in the Db,
2611
+			// but maybe the relation is of type belongs to, and so the related thing might
2612
+			if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2613
+				$related_model_object = $model->get_first_related(
2614
+					$this,
2615
+					$relationName,
2616
+					$query_params
2617
+				);
2618
+			}
2619
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2620
+			// just get what's cached on this object
2621
+			if (! $related_model_object) {
2622
+				$related_model_object = $this->get_one_from_cache($relationName);
2623
+			}
2624
+		}
2625
+		return $related_model_object;
2626
+	}
2627
+
2628
+
2629
+	/**
2630
+	 * Does a delete on all related objects of type $relationName and removes
2631
+	 * the current model object's relation to them. If they can't be deleted (because
2632
+	 * of blocking related model objects) does nothing. If the related model objects are
2633
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2634
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2635
+	 *
2636
+	 * @param string $relationName
2637
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2638
+	 * @return int how many deleted
2639
+	 * @throws ReflectionException
2640
+	 * @throws InvalidArgumentException
2641
+	 * @throws InvalidInterfaceException
2642
+	 * @throws InvalidDataTypeException
2643
+	 * @throws EE_Error
2644
+	 */
2645
+	public function delete_related($relationName, $query_params = array())
2646
+	{
2647
+		if ($this->ID()) {
2648
+			$count = $this->get_model()->delete_related(
2649
+				$this,
2650
+				$relationName,
2651
+				$query_params
2652
+			);
2653
+		} else {
2654
+			$count = count($this->get_all_from_cache($relationName));
2655
+			$this->clear_cache($relationName, null, true);
2656
+		}
2657
+		return $count;
2658
+	}
2659
+
2660
+
2661
+	/**
2662
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2663
+	 * the current model object's relation to them. If they can't be deleted (because
2664
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2665
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2666
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2667
+	 *
2668
+	 * @param string $relationName
2669
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2670
+	 * @return int how many deleted (including those soft deleted)
2671
+	 * @throws ReflectionException
2672
+	 * @throws InvalidArgumentException
2673
+	 * @throws InvalidInterfaceException
2674
+	 * @throws InvalidDataTypeException
2675
+	 * @throws EE_Error
2676
+	 */
2677
+	public function delete_related_permanently($relationName, $query_params = array())
2678
+	{
2679
+		if ($this->ID()) {
2680
+			$count = $this->get_model()->delete_related_permanently(
2681
+				$this,
2682
+				$relationName,
2683
+				$query_params
2684
+			);
2685
+		} else {
2686
+			$count = count($this->get_all_from_cache($relationName));
2687
+		}
2688
+		$this->clear_cache($relationName, null, true);
2689
+		return $count;
2690
+	}
2691
+
2692
+
2693
+	/**
2694
+	 * is_set
2695
+	 * Just a simple utility function children can use for checking if property exists
2696
+	 *
2697
+	 * @access  public
2698
+	 * @param  string $field_name property to check
2699
+	 * @return bool                              TRUE if existing,FALSE if not.
2700
+	 */
2701
+	public function is_set($field_name)
2702
+	{
2703
+		return isset($this->_fields[ $field_name ]);
2704
+	}
2705
+
2706
+
2707
+	/**
2708
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2709
+	 * EE_Error exception if they don't
2710
+	 *
2711
+	 * @param  mixed (string|array) $properties properties to check
2712
+	 * @throws EE_Error
2713
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
2714
+	 */
2715
+	protected function _property_exists($properties)
2716
+	{
2717
+		foreach ((array) $properties as $property_name) {
2718
+			// first make sure this property exists
2719
+			if (! $this->_fields[ $property_name ]) {
2720
+				throw new EE_Error(
2721
+					sprintf(
2722
+						esc_html__(
2723
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2724
+							'event_espresso'
2725
+						),
2726
+						$property_name
2727
+					)
2728
+				);
2729
+			}
2730
+		}
2731
+		return true;
2732
+	}
2733
+
2734
+
2735
+	/**
2736
+	 * This simply returns an array of model fields for this object
2737
+	 *
2738
+	 * @return array
2739
+	 * @throws ReflectionException
2740
+	 * @throws InvalidArgumentException
2741
+	 * @throws InvalidInterfaceException
2742
+	 * @throws InvalidDataTypeException
2743
+	 * @throws EE_Error
2744
+	 */
2745
+	public function model_field_array()
2746
+	{
2747
+		$fields = $this->get_model()->field_settings(false);
2748
+		$properties = array();
2749
+		// remove prepended underscore
2750
+		foreach ($fields as $field_name => $settings) {
2751
+			$properties[ $field_name ] = $this->get($field_name);
2752
+		}
2753
+		return $properties;
2754
+	}
2755
+
2756
+
2757
+	/**
2758
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2759
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2760
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2761
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2762
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2763
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2764
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2765
+	 * and accepts 2 arguments: the object on which the function was called,
2766
+	 * and an array of the original arguments passed to the function.
2767
+	 * Whatever their callback function returns will be returned by this function.
2768
+	 * Example: in functions.php (or in a plugin):
2769
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2770
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2771
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2772
+	 *          return $previousReturnValue.$returnString;
2773
+	 *      }
2774
+	 * require('EE_Answer.class.php');
2775
+	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2776
+	 * echo $answer->my_callback('monkeys',100);
2777
+	 * //will output "you called my_callback! and passed args:monkeys,100"
2778
+	 *
2779
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2780
+	 * @param array  $args       array of original arguments passed to the function
2781
+	 * @throws EE_Error
2782
+	 * @return mixed whatever the plugin which calls add_filter decides
2783
+	 */
2784
+	public function __call($methodName, $args)
2785
+	{
2786
+		$className = get_class($this);
2787
+		$tagName = "FHEE__{$className}__{$methodName}";
2788
+		if (! has_filter($tagName)) {
2789
+			throw new EE_Error(
2790
+				sprintf(
2791
+					esc_html__(
2792
+						"Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2793
+						'event_espresso'
2794
+					),
2795
+					$methodName,
2796
+					$className,
2797
+					$tagName
2798
+				)
2799
+			);
2800
+		}
2801
+		return apply_filters($tagName, null, $this, $args);
2802
+	}
2803
+
2804
+
2805
+	/**
2806
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2807
+	 * A $previous_value can be specified in case there are many meta rows with the same key
2808
+	 *
2809
+	 * @param string $meta_key
2810
+	 * @param mixed  $meta_value
2811
+	 * @param mixed  $previous_value
2812
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2813
+	 *                  NOTE: if the values haven't changed, returns 0
2814
+	 * @throws InvalidArgumentException
2815
+	 * @throws InvalidInterfaceException
2816
+	 * @throws InvalidDataTypeException
2817
+	 * @throws EE_Error
2818
+	 * @throws ReflectionException
2819
+	 */
2820
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2821
+	{
2822
+		$query_params = array(
2823
+			array(
2824
+				'EXM_key'  => $meta_key,
2825
+				'OBJ_ID'   => $this->ID(),
2826
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2827
+			),
2828
+		);
2829
+		if ($previous_value !== null) {
2830
+			$query_params[0]['EXM_value'] = $meta_value;
2831
+		}
2832
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2833
+		if (! $existing_rows_like_that) {
2834
+			return $this->add_extra_meta($meta_key, $meta_value);
2835
+		}
2836
+		foreach ($existing_rows_like_that as $existing_row) {
2837
+			$existing_row->save(array('EXM_value' => $meta_value));
2838
+		}
2839
+		return count($existing_rows_like_that);
2840
+	}
2841
+
2842
+
2843
+	/**
2844
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2845
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
2846
+	 * extra meta row was entered, false if not
2847
+	 *
2848
+	 * @param string  $meta_key
2849
+	 * @param mixed   $meta_value
2850
+	 * @param boolean $unique
2851
+	 * @return boolean
2852
+	 * @throws InvalidArgumentException
2853
+	 * @throws InvalidInterfaceException
2854
+	 * @throws InvalidDataTypeException
2855
+	 * @throws EE_Error
2856
+	 * @throws ReflectionException
2857
+	 * @throws ReflectionException
2858
+	 */
2859
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
2860
+	{
2861
+		if ($unique) {
2862
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2863
+				array(
2864
+					array(
2865
+						'EXM_key'  => $meta_key,
2866
+						'OBJ_ID'   => $this->ID(),
2867
+						'EXM_type' => $this->get_model()->get_this_model_name(),
2868
+					),
2869
+				)
2870
+			);
2871
+			if ($existing_extra_meta) {
2872
+				return false;
2873
+			}
2874
+		}
2875
+		$new_extra_meta = EE_Extra_Meta::new_instance(
2876
+			array(
2877
+				'EXM_key'   => $meta_key,
2878
+				'EXM_value' => $meta_value,
2879
+				'OBJ_ID'    => $this->ID(),
2880
+				'EXM_type'  => $this->get_model()->get_this_model_name(),
2881
+			)
2882
+		);
2883
+		$new_extra_meta->save();
2884
+		return true;
2885
+	}
2886
+
2887
+
2888
+	/**
2889
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2890
+	 * is specified, only deletes extra meta records with that value.
2891
+	 *
2892
+	 * @param string $meta_key
2893
+	 * @param mixed  $meta_value
2894
+	 * @return int number of extra meta rows deleted
2895
+	 * @throws InvalidArgumentException
2896
+	 * @throws InvalidInterfaceException
2897
+	 * @throws InvalidDataTypeException
2898
+	 * @throws EE_Error
2899
+	 * @throws ReflectionException
2900
+	 */
2901
+	public function delete_extra_meta($meta_key, $meta_value = null)
2902
+	{
2903
+		$query_params = array(
2904
+			array(
2905
+				'EXM_key'  => $meta_key,
2906
+				'OBJ_ID'   => $this->ID(),
2907
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2908
+			),
2909
+		);
2910
+		if ($meta_value !== null) {
2911
+			$query_params[0]['EXM_value'] = $meta_value;
2912
+		}
2913
+		return EEM_Extra_Meta::instance()->delete($query_params);
2914
+	}
2915
+
2916
+
2917
+	/**
2918
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2919
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2920
+	 * You can specify $default is case you haven't found the extra meta
2921
+	 *
2922
+	 * @param string  $meta_key
2923
+	 * @param boolean $single
2924
+	 * @param mixed   $default if we don't find anything, what should we return?
2925
+	 * @return mixed single value if $single; array if ! $single
2926
+	 * @throws ReflectionException
2927
+	 * @throws InvalidArgumentException
2928
+	 * @throws InvalidInterfaceException
2929
+	 * @throws InvalidDataTypeException
2930
+	 * @throws EE_Error
2931
+	 */
2932
+	public function get_extra_meta($meta_key, $single = false, $default = null)
2933
+	{
2934
+		if ($single) {
2935
+			$result = $this->get_first_related(
2936
+				'Extra_Meta',
2937
+				array(array('EXM_key' => $meta_key))
2938
+			);
2939
+			if ($result instanceof EE_Extra_Meta) {
2940
+				return $result->value();
2941
+			}
2942
+		} else {
2943
+			$results = $this->get_many_related(
2944
+				'Extra_Meta',
2945
+				array(array('EXM_key' => $meta_key))
2946
+			);
2947
+			if ($results) {
2948
+				$values = array();
2949
+				foreach ($results as $result) {
2950
+					if ($result instanceof EE_Extra_Meta) {
2951
+						$values[ $result->ID() ] = $result->value();
2952
+					}
2953
+				}
2954
+				return $values;
2955
+			}
2956
+		}
2957
+		// if nothing discovered yet return default.
2958
+		return apply_filters(
2959
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
2960
+			$default,
2961
+			$meta_key,
2962
+			$single,
2963
+			$this
2964
+		);
2965
+	}
2966
+
2967
+
2968
+	/**
2969
+	 * Returns a simple array of all the extra meta associated with this model object.
2970
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2971
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2972
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2973
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2974
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2975
+	 * finally the extra meta's value as each sub-value. (eg
2976
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2977
+	 *
2978
+	 * @param boolean $one_of_each_key
2979
+	 * @return array
2980
+	 * @throws ReflectionException
2981
+	 * @throws InvalidArgumentException
2982
+	 * @throws InvalidInterfaceException
2983
+	 * @throws InvalidDataTypeException
2984
+	 * @throws EE_Error
2985
+	 */
2986
+	public function all_extra_meta_array($one_of_each_key = true)
2987
+	{
2988
+		$return_array = array();
2989
+		if ($one_of_each_key) {
2990
+			$extra_meta_objs = $this->get_many_related(
2991
+				'Extra_Meta',
2992
+				array('group_by' => 'EXM_key')
2993
+			);
2994
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2995
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2996
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2997
+				}
2998
+			}
2999
+		} else {
3000
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
3001
+			foreach ($extra_meta_objs as $extra_meta_obj) {
3002
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
3003
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
3004
+						$return_array[ $extra_meta_obj->key() ] = array();
3005
+					}
3006
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
3007
+				}
3008
+			}
3009
+		}
3010
+		return $return_array;
3011
+	}
3012
+
3013
+
3014
+	/**
3015
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
3016
+	 *
3017
+	 * @return string
3018
+	 * @throws ReflectionException
3019
+	 * @throws InvalidArgumentException
3020
+	 * @throws InvalidInterfaceException
3021
+	 * @throws InvalidDataTypeException
3022
+	 * @throws EE_Error
3023
+	 */
3024
+	public function name()
3025
+	{
3026
+		// find a field that's not a text field
3027
+		$field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
3028
+		if ($field_we_can_use) {
3029
+			return $this->get($field_we_can_use->get_name());
3030
+		}
3031
+		$first_few_properties = $this->model_field_array();
3032
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
3033
+		$name_parts = array();
3034
+		foreach ($first_few_properties as $name => $value) {
3035
+			$name_parts[] = "$name:$value";
3036
+		}
3037
+		return implode(',', $name_parts);
3038
+	}
3039
+
3040
+
3041
+	/**
3042
+	 * in_entity_map
3043
+	 * Checks if this model object has been proven to already be in the entity map
3044
+	 *
3045
+	 * @return boolean
3046
+	 * @throws ReflectionException
3047
+	 * @throws InvalidArgumentException
3048
+	 * @throws InvalidInterfaceException
3049
+	 * @throws InvalidDataTypeException
3050
+	 * @throws EE_Error
3051
+	 */
3052
+	public function in_entity_map()
3053
+	{
3054
+		// well, if we looked, did we find it in the entity map?
3055
+		return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3056
+	}
3057
+
3058
+
3059
+	/**
3060
+	 * refresh_from_db
3061
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
3062
+	 *
3063
+	 * @throws ReflectionException
3064
+	 * @throws InvalidArgumentException
3065
+	 * @throws InvalidInterfaceException
3066
+	 * @throws InvalidDataTypeException
3067
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3068
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3069
+	 */
3070
+	public function refresh_from_db()
3071
+	{
3072
+		if ($this->ID() && $this->in_entity_map()) {
3073
+			$this->get_model()->refresh_entity_map_from_db($this->ID());
3074
+		} else {
3075
+			// if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3076
+			// if it has an ID but it's not in the map, and you're asking me to refresh it
3077
+			// that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3078
+			// absolutely nothing in it for this ID
3079
+			if (WP_DEBUG) {
3080
+				throw new EE_Error(
3081
+					sprintf(
3082
+						esc_html__(
3083
+							'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3084
+							'event_espresso'
3085
+						),
3086
+						$this->ID(),
3087
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3088
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3089
+					)
3090
+				);
3091
+			}
3092
+		}
3093
+	}
3094
+
3095
+
3096
+	/**
3097
+	 * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3098
+	 *
3099
+	 * @since 4.9.80.p
3100
+	 * @param EE_Model_Field_Base[] $fields
3101
+	 * @param string $new_value_sql
3102
+	 *      example: 'column_name=123',
3103
+	 *      or 'column_name=column_name+1',
3104
+	 *      or 'column_name= CASE
3105
+	 *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3106
+	 *          THEN `column_name` + 5
3107
+	 *          ELSE `column_name`
3108
+	 *      END'
3109
+	 *      Also updates $field on this model object with the latest value from the database.
3110
+	 * @return bool
3111
+	 * @throws EE_Error
3112
+	 * @throws InvalidArgumentException
3113
+	 * @throws InvalidDataTypeException
3114
+	 * @throws InvalidInterfaceException
3115
+	 * @throws ReflectionException
3116
+	 */
3117
+	protected function updateFieldsInDB($fields, $new_value_sql)
3118
+	{
3119
+		// First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3120
+		// if it wasn't even there to start off.
3121
+		if (! $this->ID()) {
3122
+			$this->save();
3123
+		}
3124
+		global $wpdb;
3125
+		if (empty($fields)) {
3126
+			throw new InvalidArgumentException(
3127
+				esc_html__(
3128
+					'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3129
+					'event_espresso'
3130
+				)
3131
+			);
3132
+		}
3133
+		$first_field = reset($fields);
3134
+		$table_alias = $first_field->get_table_alias();
3135
+		foreach ($fields as $field) {
3136
+			if ($table_alias !== $field->get_table_alias()) {
3137
+				throw new InvalidArgumentException(
3138
+					sprintf(
3139
+						esc_html__(
3140
+							// @codingStandardsIgnoreStart
3141
+							'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
3142
+							// @codingStandardsIgnoreEnd
3143
+							'event_espresso'
3144
+						),
3145
+						$table_alias,
3146
+						$field->get_table_alias()
3147
+					)
3148
+				);
3149
+			}
3150
+		}
3151
+		// Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3152
+		$table_obj = $this->get_model()->get_table_obj_by_alias($table_alias);
3153
+		$table_pk_value = $this->ID();
3154
+		$table_name = $table_obj->get_table_name();
3155
+		if ($table_obj instanceof EE_Secondary_Table) {
3156
+			$table_pk_field_name = $table_obj->get_fk_on_table();
3157
+		} else {
3158
+			$table_pk_field_name = $table_obj->get_pk_column();
3159
+		}
3160
+
3161
+		$query =
3162
+			"UPDATE `{$table_name}`
3163 3163
             SET "
3164
-            . $new_value_sql
3165
-            . $wpdb->prepare(
3166
-                "
3164
+			. $new_value_sql
3165
+			. $wpdb->prepare(
3166
+				"
3167 3167
             WHERE `{$table_pk_field_name}` = %d;",
3168
-                $table_pk_value
3169
-            );
3170
-        $result = $wpdb->query($query);
3171
-        foreach ($fields as $field) {
3172
-            // If it was successful, we'd like to know the new value.
3173
-            // If it failed, we'd also like to know the new value.
3174
-            $new_value = $this->get_model()->get_var(
3175
-                $this->get_model()->alter_query_params_to_restrict_by_ID(
3176
-                    $this->get_model()->get_index_primary_key_string(
3177
-                        $this->model_field_array()
3178
-                    ),
3179
-                    array(
3180
-                        'default_where_conditions' => 'minimum',
3181
-                    )
3182
-                ),
3183
-                $field->get_name()
3184
-            );
3185
-            $this->set_from_db(
3186
-                $field->get_name(),
3187
-                $new_value
3188
-            );
3189
-        }
3190
-        return (bool) $result;
3191
-    }
3192
-
3193
-
3194
-    /**
3195
-     * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3196
-     * Does not allow negative values, however.
3197
-     *
3198
-     * @since 4.9.80.p
3199
-     * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3200
-     *                                   (positive or negative). One important gotcha: all these values must be
3201
-     *                                   on the same table (eg don't pass in one field for the posts table and
3202
-     *                                   another for the event meta table.)
3203
-     * @return bool
3204
-     * @throws EE_Error
3205
-     * @throws InvalidArgumentException
3206
-     * @throws InvalidDataTypeException
3207
-     * @throws InvalidInterfaceException
3208
-     * @throws ReflectionException
3209
-     */
3210
-    public function adjustNumericFieldsInDb(array $fields_n_quantities)
3211
-    {
3212
-        global $wpdb;
3213
-        if (empty($fields_n_quantities)) {
3214
-            // No fields to update? Well sure, we updated them to that value just fine.
3215
-            return true;
3216
-        }
3217
-        $fields = [];
3218
-        $set_sql_statements = [];
3219
-        foreach ($fields_n_quantities as $field_name => $quantity) {
3220
-            $field = $this->get_model()->field_settings_for($field_name, true);
3221
-            $fields[] = $field;
3222
-            $column_name = $field->get_table_column();
3223
-
3224
-            $abs_qty = absint($quantity);
3225
-            if ($quantity > 0) {
3226
-                // don't let the value be negative as often these fields are unsigned
3227
-                $set_sql_statements[] = $wpdb->prepare(
3228
-                    "`{$column_name}` = `{$column_name}` + %d",
3229
-                    $abs_qty
3230
-                );
3231
-            } else {
3232
-                $set_sql_statements[] = $wpdb->prepare(
3233
-                    "`{$column_name}` = CASE
3168
+				$table_pk_value
3169
+			);
3170
+		$result = $wpdb->query($query);
3171
+		foreach ($fields as $field) {
3172
+			// If it was successful, we'd like to know the new value.
3173
+			// If it failed, we'd also like to know the new value.
3174
+			$new_value = $this->get_model()->get_var(
3175
+				$this->get_model()->alter_query_params_to_restrict_by_ID(
3176
+					$this->get_model()->get_index_primary_key_string(
3177
+						$this->model_field_array()
3178
+					),
3179
+					array(
3180
+						'default_where_conditions' => 'minimum',
3181
+					)
3182
+				),
3183
+				$field->get_name()
3184
+			);
3185
+			$this->set_from_db(
3186
+				$field->get_name(),
3187
+				$new_value
3188
+			);
3189
+		}
3190
+		return (bool) $result;
3191
+	}
3192
+
3193
+
3194
+	/**
3195
+	 * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3196
+	 * Does not allow negative values, however.
3197
+	 *
3198
+	 * @since 4.9.80.p
3199
+	 * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3200
+	 *                                   (positive or negative). One important gotcha: all these values must be
3201
+	 *                                   on the same table (eg don't pass in one field for the posts table and
3202
+	 *                                   another for the event meta table.)
3203
+	 * @return bool
3204
+	 * @throws EE_Error
3205
+	 * @throws InvalidArgumentException
3206
+	 * @throws InvalidDataTypeException
3207
+	 * @throws InvalidInterfaceException
3208
+	 * @throws ReflectionException
3209
+	 */
3210
+	public function adjustNumericFieldsInDb(array $fields_n_quantities)
3211
+	{
3212
+		global $wpdb;
3213
+		if (empty($fields_n_quantities)) {
3214
+			// No fields to update? Well sure, we updated them to that value just fine.
3215
+			return true;
3216
+		}
3217
+		$fields = [];
3218
+		$set_sql_statements = [];
3219
+		foreach ($fields_n_quantities as $field_name => $quantity) {
3220
+			$field = $this->get_model()->field_settings_for($field_name, true);
3221
+			$fields[] = $field;
3222
+			$column_name = $field->get_table_column();
3223
+
3224
+			$abs_qty = absint($quantity);
3225
+			if ($quantity > 0) {
3226
+				// don't let the value be negative as often these fields are unsigned
3227
+				$set_sql_statements[] = $wpdb->prepare(
3228
+					"`{$column_name}` = `{$column_name}` + %d",
3229
+					$abs_qty
3230
+				);
3231
+			} else {
3232
+				$set_sql_statements[] = $wpdb->prepare(
3233
+					"`{$column_name}` = CASE
3234 3234
                        WHEN (`{$column_name}` >= %d)
3235 3235
                        THEN `{$column_name}` - %d
3236 3236
                        ELSE 0
3237 3237
                     END",
3238
-                    $abs_qty,
3239
-                    $abs_qty
3240
-                );
3241
-            }
3242
-        }
3243
-        return $this->updateFieldsInDB(
3244
-            $fields,
3245
-            implode(', ', $set_sql_statements)
3246
-        );
3247
-    }
3248
-
3249
-
3250
-    /**
3251
-     * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3252
-     * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3253
-     * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3254
-     * Returns true if the value was successfully bumped, and updates the value on this model object.
3255
-     * Otherwise returns false.
3256
-     *
3257
-     * @since 4.9.80.p
3258
-     * @param string $field_name_to_bump
3259
-     * @param string $field_name_affecting_total
3260
-     * @param string $limit_field_name
3261
-     * @param int    $quantity
3262
-     * @return bool
3263
-     * @throws EE_Error
3264
-     * @throws InvalidArgumentException
3265
-     * @throws InvalidDataTypeException
3266
-     * @throws InvalidInterfaceException
3267
-     * @throws ReflectionException
3268
-     */
3269
-    public function incrementFieldConditionallyInDb($field_name_to_bump, $field_name_affecting_total, $limit_field_name, $quantity)
3270
-    {
3271
-        global $wpdb;
3272
-        $field = $this->get_model()->field_settings_for($field_name_to_bump, true);
3273
-        $column_name = $field->get_table_column();
3274
-
3275
-        $field_affecting_total = $this->get_model()->field_settings_for($field_name_affecting_total, true);
3276
-        $column_affecting_total = $field_affecting_total->get_table_column();
3277
-
3278
-        $limiting_field = $this->get_model()->field_settings_for($limit_field_name, true);
3279
-        $limiting_column = $limiting_field->get_table_column();
3280
-        return $this->updateFieldsInDB(
3281
-            [$field],
3282
-            $wpdb->prepare(
3283
-                "`{$column_name}` =
3238
+					$abs_qty,
3239
+					$abs_qty
3240
+				);
3241
+			}
3242
+		}
3243
+		return $this->updateFieldsInDB(
3244
+			$fields,
3245
+			implode(', ', $set_sql_statements)
3246
+		);
3247
+	}
3248
+
3249
+
3250
+	/**
3251
+	 * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3252
+	 * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3253
+	 * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3254
+	 * Returns true if the value was successfully bumped, and updates the value on this model object.
3255
+	 * Otherwise returns false.
3256
+	 *
3257
+	 * @since 4.9.80.p
3258
+	 * @param string $field_name_to_bump
3259
+	 * @param string $field_name_affecting_total
3260
+	 * @param string $limit_field_name
3261
+	 * @param int    $quantity
3262
+	 * @return bool
3263
+	 * @throws EE_Error
3264
+	 * @throws InvalidArgumentException
3265
+	 * @throws InvalidDataTypeException
3266
+	 * @throws InvalidInterfaceException
3267
+	 * @throws ReflectionException
3268
+	 */
3269
+	public function incrementFieldConditionallyInDb($field_name_to_bump, $field_name_affecting_total, $limit_field_name, $quantity)
3270
+	{
3271
+		global $wpdb;
3272
+		$field = $this->get_model()->field_settings_for($field_name_to_bump, true);
3273
+		$column_name = $field->get_table_column();
3274
+
3275
+		$field_affecting_total = $this->get_model()->field_settings_for($field_name_affecting_total, true);
3276
+		$column_affecting_total = $field_affecting_total->get_table_column();
3277
+
3278
+		$limiting_field = $this->get_model()->field_settings_for($limit_field_name, true);
3279
+		$limiting_column = $limiting_field->get_table_column();
3280
+		return $this->updateFieldsInDB(
3281
+			[$field],
3282
+			$wpdb->prepare(
3283
+				"`{$column_name}` =
3284 3284
             CASE
3285 3285
                WHEN ((`{$column_name}` + `{$column_affecting_total}` + %d) <= `{$limiting_column}`) OR `{$limiting_column}` = %d
3286 3286
                THEN `{$column_name}` + %d
3287 3287
                ELSE `{$column_name}`
3288 3288
             END",
3289
-                $quantity,
3290
-                EE_INF_IN_DB,
3291
-                $quantity
3292
-            )
3293
-        );
3294
-    }
3295
-
3296
-
3297
-    /**
3298
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3299
-     * (probably a bad assumption they have made, oh well)
3300
-     *
3301
-     * @return string
3302
-     */
3303
-    public function __toString()
3304
-    {
3305
-        try {
3306
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3307
-        } catch (Exception $e) {
3308
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3309
-            return '';
3310
-        }
3311
-    }
3312
-
3313
-
3314
-    /**
3315
-     * Clear related model objects if they're already in the DB, because otherwise when we
3316
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3317
-     * This means if we have made changes to those related model objects, and want to unserialize
3318
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3319
-     * Instead, those related model objects should be directly serialized and stored.
3320
-     * Eg, the following won't work:
3321
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3322
-     * $att = $reg->attendee();
3323
-     * $att->set( 'ATT_fname', 'Dirk' );
3324
-     * update_option( 'my_option', serialize( $reg ) );
3325
-     * //END REQUEST
3326
-     * //START NEXT REQUEST
3327
-     * $reg = get_option( 'my_option' );
3328
-     * $reg->attendee()->save();
3329
-     * And would need to be replace with:
3330
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3331
-     * $att = $reg->attendee();
3332
-     * $att->set( 'ATT_fname', 'Dirk' );
3333
-     * update_option( 'my_option', serialize( $reg ) );
3334
-     * //END REQUEST
3335
-     * //START NEXT REQUEST
3336
-     * $att = get_option( 'my_option' );
3337
-     * $att->save();
3338
-     *
3339
-     * @return array
3340
-     * @throws ReflectionException
3341
-     * @throws InvalidArgumentException
3342
-     * @throws InvalidInterfaceException
3343
-     * @throws InvalidDataTypeException
3344
-     * @throws EE_Error
3345
-     */
3346
-    public function __sleep()
3347
-    {
3348
-        $model = $this->get_model();
3349
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3350
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3351
-                $classname = 'EE_' . $model->get_this_model_name();
3352
-                if (
3353
-                    $this->get_one_from_cache($relation_name) instanceof $classname
3354
-                    && $this->get_one_from_cache($relation_name)->ID()
3355
-                ) {
3356
-                    $this->clear_cache(
3357
-                        $relation_name,
3358
-                        $this->get_one_from_cache($relation_name)->ID()
3359
-                    );
3360
-                }
3361
-            }
3362
-        }
3363
-        $this->_props_n_values_provided_in_constructor = array();
3364
-        $properties_to_serialize = get_object_vars($this);
3365
-        // don't serialize the model. It's big and that risks recursion
3366
-        unset($properties_to_serialize['_model']);
3367
-        return array_keys($properties_to_serialize);
3368
-    }
3369
-
3370
-
3371
-    /**
3372
-     * restore _props_n_values_provided_in_constructor
3373
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3374
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3375
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3376
-     */
3377
-    public function __wakeup()
3378
-    {
3379
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3380
-    }
3381
-
3382
-
3383
-    /**
3384
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3385
-     * distinct with the clone host instance are also cloned.
3386
-     */
3387
-    public function __clone()
3388
-    {
3389
-        // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3390
-        foreach ($this->_fields as $field => $value) {
3391
-            if ($value instanceof DateTime) {
3392
-                $this->_fields[ $field ] = clone $value;
3393
-            }
3394
-        }
3395
-    }
3289
+				$quantity,
3290
+				EE_INF_IN_DB,
3291
+				$quantity
3292
+			)
3293
+		);
3294
+	}
3295
+
3296
+
3297
+	/**
3298
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3299
+	 * (probably a bad assumption they have made, oh well)
3300
+	 *
3301
+	 * @return string
3302
+	 */
3303
+	public function __toString()
3304
+	{
3305
+		try {
3306
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3307
+		} catch (Exception $e) {
3308
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3309
+			return '';
3310
+		}
3311
+	}
3312
+
3313
+
3314
+	/**
3315
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3316
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3317
+	 * This means if we have made changes to those related model objects, and want to unserialize
3318
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3319
+	 * Instead, those related model objects should be directly serialized and stored.
3320
+	 * Eg, the following won't work:
3321
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3322
+	 * $att = $reg->attendee();
3323
+	 * $att->set( 'ATT_fname', 'Dirk' );
3324
+	 * update_option( 'my_option', serialize( $reg ) );
3325
+	 * //END REQUEST
3326
+	 * //START NEXT REQUEST
3327
+	 * $reg = get_option( 'my_option' );
3328
+	 * $reg->attendee()->save();
3329
+	 * And would need to be replace with:
3330
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3331
+	 * $att = $reg->attendee();
3332
+	 * $att->set( 'ATT_fname', 'Dirk' );
3333
+	 * update_option( 'my_option', serialize( $reg ) );
3334
+	 * //END REQUEST
3335
+	 * //START NEXT REQUEST
3336
+	 * $att = get_option( 'my_option' );
3337
+	 * $att->save();
3338
+	 *
3339
+	 * @return array
3340
+	 * @throws ReflectionException
3341
+	 * @throws InvalidArgumentException
3342
+	 * @throws InvalidInterfaceException
3343
+	 * @throws InvalidDataTypeException
3344
+	 * @throws EE_Error
3345
+	 */
3346
+	public function __sleep()
3347
+	{
3348
+		$model = $this->get_model();
3349
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3350
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3351
+				$classname = 'EE_' . $model->get_this_model_name();
3352
+				if (
3353
+					$this->get_one_from_cache($relation_name) instanceof $classname
3354
+					&& $this->get_one_from_cache($relation_name)->ID()
3355
+				) {
3356
+					$this->clear_cache(
3357
+						$relation_name,
3358
+						$this->get_one_from_cache($relation_name)->ID()
3359
+					);
3360
+				}
3361
+			}
3362
+		}
3363
+		$this->_props_n_values_provided_in_constructor = array();
3364
+		$properties_to_serialize = get_object_vars($this);
3365
+		// don't serialize the model. It's big and that risks recursion
3366
+		unset($properties_to_serialize['_model']);
3367
+		return array_keys($properties_to_serialize);
3368
+	}
3369
+
3370
+
3371
+	/**
3372
+	 * restore _props_n_values_provided_in_constructor
3373
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3374
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3375
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3376
+	 */
3377
+	public function __wakeup()
3378
+	{
3379
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3380
+	}
3381
+
3382
+
3383
+	/**
3384
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3385
+	 * distinct with the clone host instance are also cloned.
3386
+	 */
3387
+	public function __clone()
3388
+	{
3389
+		// handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3390
+		foreach ($this->_fields as $field => $value) {
3391
+			if ($value instanceof DateTime) {
3392
+				$this->_fields[ $field ] = clone $value;
3393
+			}
3394
+		}
3395
+	}
3396 3396
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Event.class.php 1 patch
Indentation   +1560 added lines, -1560 removed lines patch added patch discarded remove patch
@@ -16,1564 +16,1564 @@
 block discarded – undo
16 16
  */
17 17
 class EE_Event extends EE_CPT_Base implements EEI_Line_Item_Object, EEI_Admin_Links, EEI_Has_Icon, EEI_Event
18 18
 {
19
-    /**
20
-     * cached value for the the logical active status for the event
21
-     *
22
-     * @see get_active_status()
23
-     * @var string
24
-     */
25
-    protected $_active_status = '';
26
-
27
-    /**
28
-     * This is just used for caching the Primary Datetime for the Event on initial retrieval
29
-     *
30
-     * @var EE_Datetime
31
-     */
32
-    protected $_Primary_Datetime;
33
-
34
-    /**
35
-     * @var EventSpacesCalculator $available_spaces_calculator
36
-     */
37
-    protected $available_spaces_calculator;
38
-
39
-
40
-    /**
41
-     * @param array  $props_n_values          incoming values
42
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
43
-     *                                        used.)
44
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
45
-     *                                        date_format and the second value is the time format
46
-     * @return EE_Event
47
-     * @throws EE_Error
48
-     * @throws ReflectionException
49
-     */
50
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
51
-    {
52
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
53
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
54
-    }
55
-
56
-
57
-    /**
58
-     * @param array  $props_n_values  incoming values from the database
59
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
-     *                                the website will be used.
61
-     * @return EE_Event
62
-     * @throws EE_Error
63
-     * @throws ReflectionException
64
-     */
65
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
66
-    {
67
-        return new self($props_n_values, true, $timezone);
68
-    }
69
-
70
-
71
-    /**
72
-     * @return EventSpacesCalculator
73
-     * @throws \EE_Error
74
-     */
75
-    public function getAvailableSpacesCalculator()
76
-    {
77
-        if (! $this->available_spaces_calculator instanceof EventSpacesCalculator) {
78
-            $this->available_spaces_calculator = new EventSpacesCalculator($this);
79
-        }
80
-        return $this->available_spaces_calculator;
81
-    }
82
-
83
-
84
-    /**
85
-     * Overrides parent set() method so that all calls to set( 'status', $status ) can be routed to internal methods
86
-     *
87
-     * @param string $field_name
88
-     * @param mixed  $field_value
89
-     * @param bool   $use_default
90
-     * @throws EE_Error
91
-     * @throws ReflectionException
92
-     */
93
-    public function set($field_name, $field_value, $use_default = false)
94
-    {
95
-        switch ($field_name) {
96
-            case 'status':
97
-                $this->set_status($field_value, $use_default);
98
-                break;
99
-            default:
100
-                parent::set($field_name, $field_value, $use_default);
101
-        }
102
-    }
103
-
104
-
105
-    /**
106
-     *    set_status
107
-     * Checks if event status is being changed to SOLD OUT
108
-     * and updates event meta data with previous event status
109
-     * so that we can revert things if/when the event is no longer sold out
110
-     *
111
-     * @access public
112
-     * @param string $new_status
113
-     * @param bool   $use_default
114
-     * @return void
115
-     * @throws EE_Error
116
-     * @throws ReflectionException
117
-     */
118
-    public function set_status($new_status = null, $use_default = false)
119
-    {
120
-        // if nothing is set, and we aren't explicitly wanting to reset the status, then just leave
121
-        if (empty($new_status) && ! $use_default) {
122
-            return;
123
-        }
124
-        // get current Event status
125
-        $old_status = $this->status();
126
-        // if status has changed
127
-        if ($old_status !== $new_status) {
128
-            // TO sold_out
129
-            if ($new_status === EEM_Event::sold_out) {
130
-                // save the previous event status so that we can revert if the event is no longer sold out
131
-                $this->add_post_meta('_previous_event_status', $old_status);
132
-                do_action('AHEE__EE_Event__set_status__to_sold_out', $this, $old_status, $new_status);
133
-            // OR FROM  sold_out
134
-            } elseif ($old_status === EEM_Event::sold_out) {
135
-                $this->delete_post_meta('_previous_event_status');
136
-                do_action('AHEE__EE_Event__set_status__from_sold_out', $this, $old_status, $new_status);
137
-            }
138
-            // clear out the active status so that it gets reset the next time it is requested
139
-            $this->_active_status = null;
140
-            // update status
141
-            parent::set('status', $new_status, $use_default);
142
-            do_action('AHEE__EE_Event__set_status__after_update', $this);
143
-            return;
144
-        }
145
-        // even though the old value matches the new value, it's still good to
146
-        // allow the parent set method to have a say
147
-        parent::set('status', $new_status, $use_default);
148
-    }
149
-
150
-
151
-    /**
152
-     * Gets all the datetimes for this event
153
-     *
154
-     * @param array $query_params @see
155
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
156
-     * @return EE_Base_Class[]|EE_Datetime[]
157
-     * @throws EE_Error
158
-     * @throws ReflectionException
159
-     */
160
-    public function datetimes($query_params = array())
161
-    {
162
-        return $this->get_many_related('Datetime', $query_params);
163
-    }
164
-
165
-
166
-    /**
167
-     * Gets all the datetimes for this event that are currently ACTIVE,
168
-     * meaning the datetime has started and has not yet ended.
169
-     *
170
-     * @param int|null $start_date      timestamp to use for event date start time, defaults to NOW unless set to null
171
-     * @param array|null $query_params  will recursively replace default values
172
-     * @throws EE_Error
173
-     * @throws ReflectionException
174
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
175
-     */
176
-    public function activeDatetimes(?int $start_date = 0, ?array $query_params = []): array
177
-    {
178
-        $where = [];
179
-        if ($start_date !== null) {
180
-            $start_date = $start_date ?: time();
181
-            $where['DTT_EVT_start'] = ['<', $start_date];
182
-            $where['DTT_EVT_end']   = ['>', $start_date];
183
-        }
184
-        return $this->get_many_related(
185
-            'Datetime',
186
-            array_replace_recursive(
187
-                [
188
-                    $where,
189
-                    'order_by' => ['DTT_EVT_start' => 'ASC']
190
-                ],
191
-                $query_params
192
-            )
193
-        );
194
-    }
195
-
196
-
197
-    /**
198
-     * Gets all the datetimes for this event, ordered by DTT_EVT_start in ascending order
199
-     *
200
-     * @return EE_Base_Class[]|EE_Datetime[]
201
-     * @throws EE_Error
202
-     * @throws ReflectionException
203
-     */
204
-    public function datetimes_in_chronological_order()
205
-    {
206
-        return $this->get_many_related('Datetime', array('order_by' => array('DTT_EVT_start' => 'ASC')));
207
-    }
208
-
209
-
210
-    /**
211
-     * Gets all the datetimes for this event, ordered by the DTT_order on the datetime.
212
-     * @darren, we should probably UNSET timezone on the EEM_Datetime model
213
-     * after running our query, so that this timezone isn't set for EVERY query
214
-     * on EEM_Datetime for the rest of the request, no?
215
-     *
216
-     * @param boolean $show_expired whether or not to include expired events
217
-     * @param boolean $show_deleted whether or not to include deleted events
218
-     * @param null    $limit
219
-     * @return EE_Datetime[]
220
-     * @throws EE_Error
221
-     * @throws ReflectionException
222
-     */
223
-    public function datetimes_ordered($show_expired = true, $show_deleted = false, $limit = null)
224
-    {
225
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_event_ordered_by_DTT_order(
226
-            $this->ID(),
227
-            $show_expired,
228
-            $show_deleted,
229
-            $limit
230
-        );
231
-    }
232
-
233
-
234
-    /**
235
-     * Returns one related datetime. Mostly only used by some legacy code.
236
-     *
237
-     * @return EE_Base_Class|EE_Datetime
238
-     * @throws EE_Error
239
-     * @throws ReflectionException
240
-     */
241
-    public function first_datetime()
242
-    {
243
-        return $this->get_first_related('Datetime');
244
-    }
245
-
246
-
247
-    /**
248
-     * Returns the 'primary' datetime for the event
249
-     *
250
-     * @param bool $try_to_exclude_expired
251
-     * @param bool $try_to_exclude_deleted
252
-     * @return EE_Datetime
253
-     * @throws EE_Error
254
-     * @throws ReflectionException
255
-     */
256
-    public function primary_datetime($try_to_exclude_expired = true, $try_to_exclude_deleted = true)
257
-    {
258
-        if (! empty($this->_Primary_Datetime)) {
259
-            return $this->_Primary_Datetime;
260
-        }
261
-        $this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
262
-            $this->ID(),
263
-            $try_to_exclude_expired,
264
-            $try_to_exclude_deleted
265
-        );
266
-        return $this->_Primary_Datetime;
267
-    }
268
-
269
-
270
-    /**
271
-     * Gets all the tickets available for purchase of this event
272
-     *
273
-     * @param array $query_params @see
274
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
275
-     * @return EE_Base_Class[]|EE_Ticket[]
276
-     * @throws EE_Error
277
-     * @throws ReflectionException
278
-     */
279
-    public function tickets($query_params = array())
280
-    {
281
-        // first get all datetimes
282
-        $datetimes = $this->datetimes_ordered();
283
-        if (! $datetimes) {
284
-            return array();
285
-        }
286
-        $datetime_ids = array();
287
-        foreach ($datetimes as $datetime) {
288
-            $datetime_ids[] = $datetime->ID();
289
-        }
290
-        $where_params = array('Datetime.DTT_ID' => array('IN', $datetime_ids));
291
-        // if incoming $query_params has where conditions let's merge but not override existing.
292
-        if (is_array($query_params) && isset($query_params[0])) {
293
-            $where_params = array_merge($query_params[0], $where_params);
294
-            unset($query_params[0]);
295
-        }
296
-        // now add $where_params to $query_params
297
-        $query_params[0] = $where_params;
298
-        return EEM_Ticket::instance()->get_all($query_params);
299
-    }
300
-
301
-
302
-    /**
303
-     * get all unexpired untrashed tickets
304
-     *
305
-     * @return EE_Ticket[]
306
-     * @throws EE_Error
307
-     */
308
-    public function active_tickets()
309
-    {
310
-        return $this->tickets(
311
-            array(
312
-                array(
313
-                    'TKT_end_date' => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
314
-                    'TKT_deleted'  => false,
315
-                ),
316
-            )
317
-        );
318
-    }
319
-
320
-
321
-    /**
322
-     * @return bool
323
-     * @throws EE_Error
324
-     * @throws ReflectionException
325
-     */
326
-    public function additional_limit()
327
-    {
328
-        return $this->get('EVT_additional_limit');
329
-    }
330
-
331
-
332
-    /**
333
-     * @return bool
334
-     * @throws EE_Error
335
-     * @throws ReflectionException
336
-     */
337
-    public function allow_overflow()
338
-    {
339
-        return $this->get('EVT_allow_overflow');
340
-    }
341
-
342
-
343
-    /**
344
-     * @return bool
345
-     * @throws EE_Error
346
-     * @throws ReflectionException
347
-     */
348
-    public function created()
349
-    {
350
-        return $this->get('EVT_created');
351
-    }
352
-
353
-
354
-    /**
355
-     * @return bool
356
-     * @throws EE_Error
357
-     * @throws ReflectionException
358
-     */
359
-    public function description()
360
-    {
361
-        return $this->get('EVT_desc');
362
-    }
363
-
364
-
365
-    /**
366
-     * Runs do_shortcode and wpautop on the description
367
-     *
368
-     * @return string of html
369
-     * @throws EE_Error
370
-     * @throws ReflectionException
371
-     */
372
-    public function description_filtered()
373
-    {
374
-        return $this->get_pretty('EVT_desc');
375
-    }
376
-
377
-
378
-    /**
379
-     * @return bool
380
-     * @throws EE_Error
381
-     * @throws ReflectionException
382
-     */
383
-    public function display_description()
384
-    {
385
-        return $this->get('EVT_display_desc');
386
-    }
387
-
388
-
389
-    /**
390
-     * @return bool
391
-     * @throws EE_Error
392
-     * @throws ReflectionException
393
-     */
394
-    public function display_ticket_selector()
395
-    {
396
-        return (bool) $this->get('EVT_display_ticket_selector');
397
-    }
398
-
399
-
400
-    /**
401
-     * @return string
402
-     * @throws EE_Error
403
-     * @throws ReflectionException
404
-     */
405
-    public function external_url()
406
-    {
407
-        return $this->get('EVT_external_URL');
408
-    }
409
-
410
-
411
-    /**
412
-     * @return bool
413
-     * @throws EE_Error
414
-     * @throws ReflectionException
415
-     */
416
-    public function member_only()
417
-    {
418
-        return $this->get('EVT_member_only');
419
-    }
420
-
421
-
422
-    /**
423
-     * @return bool
424
-     * @throws EE_Error
425
-     * @throws ReflectionException
426
-     */
427
-    public function phone()
428
-    {
429
-        return $this->get('EVT_phone');
430
-    }
431
-
432
-
433
-    /**
434
-     * @return bool
435
-     * @throws EE_Error
436
-     * @throws ReflectionException
437
-     */
438
-    public function modified()
439
-    {
440
-        return $this->get('EVT_modified');
441
-    }
442
-
443
-
444
-    /**
445
-     * @return bool
446
-     * @throws EE_Error
447
-     * @throws ReflectionException
448
-     */
449
-    public function name()
450
-    {
451
-        return $this->get('EVT_name');
452
-    }
453
-
454
-
455
-    /**
456
-     * @return bool
457
-     * @throws EE_Error
458
-     * @throws ReflectionException
459
-     */
460
-    public function order()
461
-    {
462
-        return $this->get('EVT_order');
463
-    }
464
-
465
-
466
-    /**
467
-     * @return bool|string
468
-     * @throws EE_Error
469
-     * @throws ReflectionException
470
-     */
471
-    public function default_registration_status()
472
-    {
473
-        $event_default_registration_status = $this->get('EVT_default_registration_status');
474
-        return ! empty($event_default_registration_status)
475
-            ? $event_default_registration_status
476
-            : EE_Registry::instance()->CFG->registration->default_STS_ID;
477
-    }
478
-
479
-
480
-    /**
481
-     * @param int  $num_words
482
-     * @param null $more
483
-     * @param bool $not_full_desc
484
-     * @return bool|string
485
-     * @throws EE_Error
486
-     * @throws ReflectionException
487
-     */
488
-    public function short_description($num_words = 55, $more = null, $not_full_desc = false)
489
-    {
490
-        $short_desc = $this->get('EVT_short_desc');
491
-        if (! empty($short_desc) || $not_full_desc) {
492
-            return $short_desc;
493
-        }
494
-        $full_desc = $this->get('EVT_desc');
495
-        return wp_trim_words($full_desc, $num_words, $more);
496
-    }
497
-
498
-
499
-    /**
500
-     * @return bool
501
-     * @throws EE_Error
502
-     * @throws ReflectionException
503
-     */
504
-    public function slug()
505
-    {
506
-        return $this->get('EVT_slug');
507
-    }
508
-
509
-
510
-    /**
511
-     * @return bool
512
-     * @throws EE_Error
513
-     * @throws ReflectionException
514
-     */
515
-    public function timezone_string()
516
-    {
517
-        return $this->get('EVT_timezone_string');
518
-    }
519
-
520
-
521
-    /**
522
-     * @return bool
523
-     * @throws EE_Error
524
-     * @throws ReflectionException
525
-     */
526
-    public function visible_on()
527
-    {
528
-        return $this->get('EVT_visible_on');
529
-    }
530
-
531
-
532
-    /**
533
-     * @return int
534
-     * @throws EE_Error
535
-     * @throws ReflectionException
536
-     */
537
-    public function wp_user()
538
-    {
539
-        return $this->get('EVT_wp_user');
540
-    }
541
-
542
-
543
-    /**
544
-     * @return bool
545
-     * @throws EE_Error
546
-     * @throws ReflectionException
547
-     */
548
-    public function donations()
549
-    {
550
-        return $this->get('EVT_donations');
551
-    }
552
-
553
-
554
-    /**
555
-     * @param $limit
556
-     * @throws EE_Error
557
-     */
558
-    public function set_additional_limit($limit)
559
-    {
560
-        $this->set('EVT_additional_limit', $limit);
561
-    }
562
-
563
-
564
-    /**
565
-     * @param $created
566
-     * @throws EE_Error
567
-     */
568
-    public function set_created($created)
569
-    {
570
-        $this->set('EVT_created', $created);
571
-    }
572
-
573
-
574
-    /**
575
-     * @param $desc
576
-     * @throws EE_Error
577
-     */
578
-    public function set_description($desc)
579
-    {
580
-        $this->set('EVT_desc', $desc);
581
-    }
582
-
583
-
584
-    /**
585
-     * @param $display_desc
586
-     * @throws EE_Error
587
-     */
588
-    public function set_display_description($display_desc)
589
-    {
590
-        $this->set('EVT_display_desc', $display_desc);
591
-    }
592
-
593
-
594
-    /**
595
-     * @param $display_ticket_selector
596
-     * @throws EE_Error
597
-     */
598
-    public function set_display_ticket_selector($display_ticket_selector)
599
-    {
600
-        $this->set('EVT_display_ticket_selector', $display_ticket_selector);
601
-    }
602
-
603
-
604
-    /**
605
-     * @param $external_url
606
-     * @throws EE_Error
607
-     */
608
-    public function set_external_url($external_url)
609
-    {
610
-        $this->set('EVT_external_URL', $external_url);
611
-    }
612
-
613
-
614
-    /**
615
-     * @param $member_only
616
-     * @throws EE_Error
617
-     */
618
-    public function set_member_only($member_only)
619
-    {
620
-        $this->set('EVT_member_only', $member_only);
621
-    }
622
-
623
-
624
-    /**
625
-     * @param $event_phone
626
-     * @throws EE_Error
627
-     */
628
-    public function set_event_phone($event_phone)
629
-    {
630
-        $this->set('EVT_phone', $event_phone);
631
-    }
632
-
633
-
634
-    /**
635
-     * @param $modified
636
-     * @throws EE_Error
637
-     */
638
-    public function set_modified($modified)
639
-    {
640
-        $this->set('EVT_modified', $modified);
641
-    }
642
-
643
-
644
-    /**
645
-     * @param $name
646
-     * @throws EE_Error
647
-     */
648
-    public function set_name($name)
649
-    {
650
-        $this->set('EVT_name', $name);
651
-    }
652
-
653
-
654
-    /**
655
-     * @param $order
656
-     * @throws EE_Error
657
-     */
658
-    public function set_order($order)
659
-    {
660
-        $this->set('EVT_order', $order);
661
-    }
662
-
663
-
664
-    /**
665
-     * @param $short_desc
666
-     * @throws EE_Error
667
-     */
668
-    public function set_short_description($short_desc)
669
-    {
670
-        $this->set('EVT_short_desc', $short_desc);
671
-    }
672
-
673
-
674
-    /**
675
-     * @param $slug
676
-     * @throws EE_Error
677
-     */
678
-    public function set_slug($slug)
679
-    {
680
-        $this->set('EVT_slug', $slug);
681
-    }
682
-
683
-
684
-    /**
685
-     * @param $timezone_string
686
-     * @throws EE_Error
687
-     */
688
-    public function set_timezone_string($timezone_string)
689
-    {
690
-        $this->set('EVT_timezone_string', $timezone_string);
691
-    }
692
-
693
-
694
-    /**
695
-     * @param $visible_on
696
-     * @throws EE_Error
697
-     */
698
-    public function set_visible_on($visible_on)
699
-    {
700
-        $this->set('EVT_visible_on', $visible_on);
701
-    }
702
-
703
-
704
-    /**
705
-     * @param $wp_user
706
-     * @throws EE_Error
707
-     */
708
-    public function set_wp_user($wp_user)
709
-    {
710
-        $this->set('EVT_wp_user', $wp_user);
711
-    }
712
-
713
-
714
-    /**
715
-     * @param $default_registration_status
716
-     * @throws EE_Error
717
-     */
718
-    public function set_default_registration_status($default_registration_status)
719
-    {
720
-        $this->set('EVT_default_registration_status', $default_registration_status);
721
-    }
722
-
723
-
724
-    /**
725
-     * @param $donations
726
-     * @throws EE_Error
727
-     */
728
-    public function set_donations($donations)
729
-    {
730
-        $this->set('EVT_donations', $donations);
731
-    }
732
-
733
-
734
-    /**
735
-     * Adds a venue to this event
736
-     *
737
-     * @param int|EE_Venue /int $venue_id_or_obj
738
-     * @return EE_Base_Class|EE_Venue
739
-     * @throws EE_Error
740
-     * @throws ReflectionException
741
-     */
742
-    public function add_venue($venue_id_or_obj): EE_Venue
743
-    {
744
-        return $this->_add_relation_to($venue_id_or_obj, 'Venue');
745
-    }
746
-
747
-
748
-    /**
749
-     * Removes a venue from the event
750
-     *
751
-     * @param EE_Venue /int $venue_id_or_obj
752
-     * @return EE_Base_Class|EE_Venue
753
-     * @throws EE_Error
754
-     * @throws ReflectionException
755
-     */
756
-    public function remove_venue($venue_id_or_obj): EE_Venue
757
-    {
758
-        $venue_id_or_obj = ! empty($venue_id_or_obj) ? $venue_id_or_obj : $this->venue();
759
-        return $this->_remove_relation_to($venue_id_or_obj, 'Venue');
760
-    }
761
-
762
-
763
-    /**
764
-     * Gets the venue related to the event. May provide additional $query_params if desired
765
-     *
766
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
767
-     * @return int
768
-     * @throws EE_Error
769
-     * @throws ReflectionException
770
-     */
771
-    public function venue_ID(array $query_params = array()): int
772
-    {
773
-        $venue = $this->get_first_related('Venue', $query_params);
774
-        return $venue instanceof EE_Venue ? $venue->ID() : 0;
775
-    }
776
-
777
-
778
-    /**
779
-     * Gets the venue related to the event. May provide additional $query_params if desired
780
-     *
781
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
782
-     * @return EE_Base_Class|EE_Venue
783
-     * @throws EE_Error
784
-     * @throws ReflectionException
785
-     */
786
-    public function venue(array $query_params = array())
787
-    {
788
-        return $this->get_first_related('Venue', $query_params);
789
-    }
790
-
791
-
792
-    /**
793
-     * @param array $query_params
794
-     * @return EE_Base_Class[]|EE_Venue[]
795
-     * @throws EE_Error
796
-     * @throws ReflectionException
797
-     * @deprecated $VID:$
798
-     */
799
-    public function venues(array $query_params = array()): array
800
-    {
801
-        return [$this->venue($query_params)];
802
-    }
803
-
804
-
805
-    /**
806
-     * check if event id is present and if event is published
807
-     *
808
-     * @access public
809
-     * @return boolean true yes, false no
810
-     * @throws EE_Error
811
-     * @throws ReflectionException
812
-     */
813
-    private function _has_ID_and_is_published()
814
-    {
815
-        // first check if event id is present and not NULL,
816
-        // then check if this event is published (or any of the equivalent "published" statuses)
817
-        return
818
-            $this->ID() && $this->ID() !== null
819
-            && (
820
-                $this->status() === 'publish'
821
-                || $this->status() === EEM_Event::sold_out
822
-                || $this->status() === EEM_Event::postponed
823
-                || $this->status() === EEM_Event::cancelled
824
-            );
825
-    }
826
-
827
-
828
-    /**
829
-     * This simply compares the internal dates with NOW and determines if the event is upcoming or not.
830
-     *
831
-     * @access public
832
-     * @return boolean true yes, false no
833
-     * @throws EE_Error
834
-     * @throws ReflectionException
835
-     */
836
-    public function is_upcoming()
837
-    {
838
-        // check if event id is present and if this event is published
839
-        if ($this->is_inactive()) {
840
-            return false;
841
-        }
842
-        // set initial value
843
-        $upcoming = false;
844
-        // next let's get all datetimes and loop through them
845
-        $datetimes = $this->datetimes_in_chronological_order();
846
-        foreach ($datetimes as $datetime) {
847
-            if ($datetime instanceof EE_Datetime) {
848
-                // if this dtt is expired then we continue cause one of the other datetimes might be upcoming.
849
-                if ($datetime->is_expired()) {
850
-                    continue;
851
-                }
852
-                // if this dtt is active then we return false.
853
-                if ($datetime->is_active()) {
854
-                    return false;
855
-                }
856
-                // otherwise let's check upcoming status
857
-                $upcoming = $datetime->is_upcoming();
858
-            }
859
-        }
860
-        return $upcoming;
861
-    }
862
-
863
-
864
-    /**
865
-     * @return bool
866
-     * @throws EE_Error
867
-     * @throws ReflectionException
868
-     */
869
-    public function is_active()
870
-    {
871
-        // check if event id is present and if this event is published
872
-        if ($this->is_inactive()) {
873
-            return false;
874
-        }
875
-        // set initial value
876
-        $active = false;
877
-        // next let's get all datetimes and loop through them
878
-        $datetimes = $this->datetimes_in_chronological_order();
879
-        foreach ($datetimes as $datetime) {
880
-            if ($datetime instanceof EE_Datetime) {
881
-                // if this dtt is expired then we continue cause one of the other datetimes might be active.
882
-                if ($datetime->is_expired()) {
883
-                    continue;
884
-                }
885
-                // if this dtt is upcoming then we return false.
886
-                if ($datetime->is_upcoming()) {
887
-                    return false;
888
-                }
889
-                // otherwise let's check active status
890
-                $active = $datetime->is_active();
891
-            }
892
-        }
893
-        return $active;
894
-    }
895
-
896
-
897
-    /**
898
-     * @return bool
899
-     * @throws EE_Error
900
-     * @throws ReflectionException
901
-     */
902
-    public function is_expired()
903
-    {
904
-        // check if event id is present and if this event is published
905
-        if ($this->is_inactive()) {
906
-            return false;
907
-        }
908
-        // set initial value
909
-        $expired = false;
910
-        // first let's get all datetimes and loop through them
911
-        $datetimes = $this->datetimes_in_chronological_order();
912
-        foreach ($datetimes as $datetime) {
913
-            if ($datetime instanceof EE_Datetime) {
914
-                // if this dtt is upcoming or active then we return false.
915
-                if ($datetime->is_upcoming() || $datetime->is_active()) {
916
-                    return false;
917
-                }
918
-                // otherwise let's check active status
919
-                $expired = $datetime->is_expired();
920
-            }
921
-        }
922
-        return $expired;
923
-    }
924
-
925
-
926
-    /**
927
-     * @return bool
928
-     * @throws EE_Error
929
-     */
930
-    public function is_inactive()
931
-    {
932
-        // check if event id is present and if this event is published
933
-        if ($this->_has_ID_and_is_published()) {
934
-            return false;
935
-        }
936
-        return true;
937
-    }
938
-
939
-
940
-    /**
941
-     * calculate spaces remaining based on "saleable" tickets
942
-     *
943
-     * @param array $tickets
944
-     * @param bool  $filtered
945
-     * @return int|float
946
-     * @throws EE_Error
947
-     * @throws DomainException
948
-     * @throws UnexpectedEntityException
949
-     */
950
-    public function spaces_remaining($tickets = array(), $filtered = true)
951
-    {
952
-        $this->getAvailableSpacesCalculator()->setActiveTickets($tickets);
953
-        $spaces_remaining = $this->getAvailableSpacesCalculator()->spacesRemaining();
954
-        return $filtered
955
-            ? apply_filters(
956
-                'FHEE_EE_Event__spaces_remaining',
957
-                $spaces_remaining,
958
-                $this,
959
-                $tickets
960
-            )
961
-            : $spaces_remaining;
962
-    }
963
-
964
-
965
-    /**
966
-     *    perform_sold_out_status_check
967
-     *    checks all of this events's datetime  reg_limit - sold values to determine if ANY datetimes have spaces
968
-     *    available... if NOT, then the event status will get toggled to 'sold_out'
969
-     *
970
-     * @return bool    return the ACTUAL sold out state.
971
-     * @throws EE_Error
972
-     * @throws DomainException
973
-     * @throws UnexpectedEntityException
974
-     * @throws ReflectionException
975
-     */
976
-    public function perform_sold_out_status_check()
977
-    {
978
-        // get all tickets
979
-        $tickets = $this->tickets(
980
-            array(
981
-                'default_where_conditions' => 'none',
982
-                'order_by' => array('TKT_qty' => 'ASC'),
983
-            )
984
-        );
985
-        $all_expired = true;
986
-        foreach ($tickets as $ticket) {
987
-            if (! $ticket->is_expired()) {
988
-                $all_expired = false;
989
-                break;
990
-            }
991
-        }
992
-        // if all the tickets are just expired, then don't update the event status to sold out
993
-        if ($all_expired) {
994
-            return true;
995
-        }
996
-        $spaces_remaining = $this->spaces_remaining($tickets);
997
-        if ($spaces_remaining < 1) {
998
-            if ($this->status() !== EEM_Event::post_status_private) {
999
-                $this->set_status(EEM_Event::sold_out);
1000
-                $this->save();
1001
-            }
1002
-            $sold_out = true;
1003
-        } else {
1004
-            $sold_out = false;
1005
-            // was event previously marked as sold out ?
1006
-            if ($this->status() === EEM_Event::sold_out) {
1007
-                // revert status to previous value, if it was set
1008
-                $previous_event_status = $this->get_post_meta('_previous_event_status', true);
1009
-                if ($previous_event_status) {
1010
-                    $this->set_status($previous_event_status);
1011
-                    $this->save();
1012
-                }
1013
-            }
1014
-        }
1015
-        do_action('AHEE__EE_Event__perform_sold_out_status_check__end', $this, $sold_out, $spaces_remaining, $tickets);
1016
-        return $sold_out;
1017
-    }
1018
-
1019
-
1020
-    /**
1021
-     * This returns the total remaining spaces for sale on this event.
1022
-     *
1023
-     * @uses EE_Event::total_available_spaces()
1024
-     * @return float|int
1025
-     * @throws EE_Error
1026
-     * @throws DomainException
1027
-     * @throws UnexpectedEntityException
1028
-     */
1029
-    public function spaces_remaining_for_sale()
1030
-    {
1031
-        return $this->total_available_spaces(true);
1032
-    }
1033
-
1034
-
1035
-    /**
1036
-     * This returns the total spaces available for an event
1037
-     * while considering all the qtys on the tickets and the reg limits
1038
-     * on the datetimes attached to this event.
1039
-     *
1040
-     * @param   bool $consider_sold Whether to consider any tickets that have already sold in our calculation.
1041
-     *                              If this is false, then we return the most tickets that could ever be sold
1042
-     *                              for this event with the datetime and tickets setup on the event under optimal
1043
-     *                              selling conditions.  Otherwise we return a live calculation of spaces available
1044
-     *                              based on tickets sold.  Depending on setup and stage of sales, this
1045
-     *                              may appear to equal remaining tickets.  However, the more tickets are
1046
-     *                              sold out, the more accurate the "live" total is.
1047
-     * @return float|int
1048
-     * @throws EE_Error
1049
-     * @throws DomainException
1050
-     * @throws UnexpectedEntityException
1051
-     */
1052
-    public function total_available_spaces($consider_sold = false)
1053
-    {
1054
-        $spaces_available = $consider_sold
1055
-            ? $this->getAvailableSpacesCalculator()->spacesRemaining()
1056
-            : $this->getAvailableSpacesCalculator()->totalSpacesAvailable();
1057
-        return apply_filters(
1058
-            'FHEE_EE_Event__total_available_spaces__spaces_available',
1059
-            $spaces_available,
1060
-            $this,
1061
-            $this->getAvailableSpacesCalculator()->getDatetimes(),
1062
-            $this->getAvailableSpacesCalculator()->getActiveTickets()
1063
-        );
1064
-    }
1065
-
1066
-
1067
-    /**
1068
-     * Checks if the event is set to sold out
1069
-     *
1070
-     * @param  bool $actual whether or not to perform calculations to not only figure the
1071
-     *                      actual status but also to flip the status if necessary to sold
1072
-     *                      out If false, we just check the existing status of the event
1073
-     * @return boolean
1074
-     * @throws EE_Error
1075
-     */
1076
-    public function is_sold_out($actual = false)
1077
-    {
1078
-        if (! $actual) {
1079
-            return $this->status() === EEM_Event::sold_out;
1080
-        }
1081
-        return $this->perform_sold_out_status_check();
1082
-    }
1083
-
1084
-
1085
-    /**
1086
-     * Checks if the event is marked as postponed
1087
-     *
1088
-     * @return boolean
1089
-     */
1090
-    public function is_postponed()
1091
-    {
1092
-        return $this->status() === EEM_Event::postponed;
1093
-    }
1094
-
1095
-
1096
-    /**
1097
-     * Checks if the event is marked as cancelled
1098
-     *
1099
-     * @return boolean
1100
-     */
1101
-    public function is_cancelled()
1102
-    {
1103
-        return $this->status() === EEM_Event::cancelled;
1104
-    }
1105
-
1106
-
1107
-    /**
1108
-     * Get the logical active status in a hierarchical order for all the datetimes.  Note
1109
-     * Basically, we order the datetimes by EVT_start_date.  Then first test on whether the event is published.  If its
1110
-     * NOT published then we test for whether its expired or not.  IF it IS published then we test first on whether an
1111
-     * event has any active dates.  If no active dates then we check for any upcoming dates.  If no upcoming dates then
1112
-     * the event is considered expired.
1113
-     * NOTE: this method does NOT calculate whether the datetimes are sold out when event is published.  Sold Out is a
1114
-     * status set on the EVENT when it is not published and thus is done
1115
-     *
1116
-     * @param bool $reset
1117
-     * @return bool | string - based on EE_Datetime active constants or FALSE if error.
1118
-     * @throws EE_Error
1119
-     * @throws ReflectionException
1120
-     */
1121
-    public function get_active_status($reset = false)
1122
-    {
1123
-        // if the active status has already been set, then just use that value (unless we are resetting it)
1124
-        if (! empty($this->_active_status) && ! $reset) {
1125
-            return $this->_active_status;
1126
-        }
1127
-        // first check if event id is present on this object
1128
-        if (! $this->ID()) {
1129
-            return false;
1130
-        }
1131
-        $where_params_for_event = array(array('EVT_ID' => $this->ID()));
1132
-        // if event is published:
1133
-        if ($this->status() === EEM_Event::post_status_publish || $this->status() === EEM_Event::post_status_private) {
1134
-            // active?
1135
-            if (
1136
-                EEM_Datetime::instance()->get_datetime_count_for_status(
1137
-                    EE_Datetime::active,
1138
-                    $where_params_for_event
1139
-                ) > 0
1140
-            ) {
1141
-                $this->_active_status = EE_Datetime::active;
1142
-            } else {
1143
-                // upcoming?
1144
-                if (
1145
-                    EEM_Datetime::instance()->get_datetime_count_for_status(
1146
-                        EE_Datetime::upcoming,
1147
-                        $where_params_for_event
1148
-                    ) > 0
1149
-                ) {
1150
-                    $this->_active_status = EE_Datetime::upcoming;
1151
-                } else {
1152
-                    // expired?
1153
-                    if (
1154
-                        EEM_Datetime::instance()->get_datetime_count_for_status(
1155
-                            EE_Datetime::expired,
1156
-                            $where_params_for_event
1157
-                        ) > 0
1158
-                    ) {
1159
-                        $this->_active_status = EE_Datetime::expired;
1160
-                    } else {
1161
-                        // it would be odd if things make it this far because it basically means there are no datetime's
1162
-                        // attached to the event.  So in this case it will just be considered inactive.
1163
-                        $this->_active_status = EE_Datetime::inactive;
1164
-                    }
1165
-                }
1166
-            }
1167
-        } else {
1168
-            // the event is not published, so let's just set it's active status according to its' post status
1169
-            switch ($this->status()) {
1170
-                case EEM_Event::sold_out:
1171
-                    $this->_active_status = EE_Datetime::sold_out;
1172
-                    break;
1173
-                case EEM_Event::cancelled:
1174
-                    $this->_active_status = EE_Datetime::cancelled;
1175
-                    break;
1176
-                case EEM_Event::postponed:
1177
-                    $this->_active_status = EE_Datetime::postponed;
1178
-                    break;
1179
-                default:
1180
-                    $this->_active_status = EE_Datetime::inactive;
1181
-            }
1182
-        }
1183
-        return $this->_active_status;
1184
-    }
1185
-
1186
-
1187
-    /**
1188
-     *    pretty_active_status
1189
-     *
1190
-     * @access public
1191
-     * @param boolean $echo whether to return (FALSE), or echo out the result (TRUE)
1192
-     * @return mixed void|string
1193
-     * @throws EE_Error
1194
-     * @throws ReflectionException
1195
-     */
1196
-    public function pretty_active_status($echo = true)
1197
-    {
1198
-        $active_status = $this->get_active_status();
1199
-        $status = '<span class="ee-status ee-status-bg--' . esc_attr($active_status) . ' event-active-status-' .
1200
-                  esc_attr($active_status) . '">'
1201
-                  . EEH_Template::pretty_status($active_status, false, 'sentence')
1202
-                  . '</span>';
1203
-        if ($echo) {
1204
-            echo wp_kses($status, AllowedTags::getAllowedTags());
1205
-            return '';
1206
-        }
1207
-        return $status; // already escaped
1208
-    }
1209
-
1210
-
1211
-    /**
1212
-     * @return bool|int
1213
-     * @throws EE_Error
1214
-     * @throws ReflectionException
1215
-     */
1216
-    public function get_number_of_tickets_sold()
1217
-    {
1218
-        $tkt_sold = 0;
1219
-        if (! $this->ID()) {
1220
-            return 0;
1221
-        }
1222
-        $datetimes = $this->datetimes();
1223
-        foreach ($datetimes as $datetime) {
1224
-            if ($datetime instanceof EE_Datetime) {
1225
-                $tkt_sold += $datetime->sold();
1226
-            }
1227
-        }
1228
-        return $tkt_sold;
1229
-    }
1230
-
1231
-
1232
-    /**
1233
-     * This just returns a count of all the registrations for this event
1234
-     *
1235
-     * @access  public
1236
-     * @return int
1237
-     * @throws EE_Error
1238
-     */
1239
-    public function get_count_of_all_registrations()
1240
-    {
1241
-        return EEM_Event::instance()->count_related($this, 'Registration');
1242
-    }
1243
-
1244
-
1245
-    /**
1246
-     * This returns the ticket with the earliest start time that is
1247
-     * available for this event (across all datetimes attached to the event)
1248
-     *
1249
-     * @return EE_Base_Class|EE_Ticket|null
1250
-     * @throws EE_Error
1251
-     * @throws ReflectionException
1252
-     */
1253
-    public function get_ticket_with_earliest_start_time()
1254
-    {
1255
-        $where['Datetime.EVT_ID'] = $this->ID();
1256
-        $query_params = array($where, 'order_by' => array('TKT_start_date' => 'ASC'));
1257
-        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1258
-    }
1259
-
1260
-
1261
-    /**
1262
-     * This returns the ticket with the latest end time that is available
1263
-     * for this event (across all datetimes attached to the event)
1264
-     *
1265
-     * @return EE_Base_Class|EE_Ticket|null
1266
-     * @throws EE_Error
1267
-     * @throws ReflectionException
1268
-     */
1269
-    public function get_ticket_with_latest_end_time()
1270
-    {
1271
-        $where['Datetime.EVT_ID'] = $this->ID();
1272
-        $query_params = array($where, 'order_by' => array('TKT_end_date' => 'DESC'));
1273
-        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1274
-    }
1275
-
1276
-
1277
-    /**
1278
-     * This returns the number of different ticket types currently on sale for this event.
1279
-     *
1280
-     * @return int
1281
-     * @throws EE_Error
1282
-     * @throws ReflectionException
1283
-     */
1284
-    public function countTicketsOnSale()
1285
-    {
1286
-        $where = array(
1287
-            'Datetime.EVT_ID' => $this->ID(),
1288
-            'TKT_start_date'  => array('<', time()),
1289
-            'TKT_end_date'    => array('>', time()),
1290
-        );
1291
-        return EEM_Ticket::instance()->count(array($where));
1292
-    }
1293
-
1294
-
1295
-    /**
1296
-     * This returns whether there are any tickets on sale for this event.
1297
-     *
1298
-     * @return bool true = YES tickets on sale.
1299
-     * @throws EE_Error
1300
-     */
1301
-    public function tickets_on_sale()
1302
-    {
1303
-        return $this->countTicketsOnSale() > 0;
1304
-    }
1305
-
1306
-
1307
-    /**
1308
-     * Gets the URL for viewing this event on the front-end. Overrides parent
1309
-     * to check for an external URL first
1310
-     *
1311
-     * @return string
1312
-     * @throws EE_Error
1313
-     */
1314
-    public function get_permalink()
1315
-    {
1316
-        if ($this->external_url()) {
1317
-            return $this->external_url();
1318
-        }
1319
-        return parent::get_permalink();
1320
-    }
1321
-
1322
-
1323
-    /**
1324
-     * Gets the first term for 'espresso_event_categories' we can find
1325
-     *
1326
-     * @param array $query_params @see
1327
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1328
-     * @return EE_Base_Class|EE_Term|null
1329
-     * @throws EE_Error
1330
-     * @throws ReflectionException
1331
-     */
1332
-    public function first_event_category($query_params = array())
1333
-    {
1334
-        $query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1335
-        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1336
-        return EEM_Term::instance()->get_one($query_params);
1337
-    }
1338
-
1339
-
1340
-    /**
1341
-     * Gets all terms for 'espresso_event_categories' we can find
1342
-     *
1343
-     * @param array $query_params
1344
-     * @return EE_Base_Class[]|EE_Term[]
1345
-     * @throws EE_Error
1346
-     * @throws ReflectionException
1347
-     */
1348
-    public function get_all_event_categories($query_params = array())
1349
-    {
1350
-        $query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1351
-        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1352
-        return EEM_Term::instance()->get_all($query_params);
1353
-    }
1354
-
1355
-
1356
-    /**
1357
-     * Adds a question group to this event
1358
-     *
1359
-     * @param EE_Question_Group|int $question_group_id_or_obj
1360
-     * @param bool $for_primary if true, the question group will be added for the primary
1361
-     *                                           registrant, if false will be added for others. default: false
1362
-     * @return EE_Base_Class|EE_Question_Group
1363
-     * @throws EE_Error
1364
-     * @throws InvalidArgumentException
1365
-     * @throws InvalidDataTypeException
1366
-     * @throws InvalidInterfaceException
1367
-     * @throws ReflectionException
1368
-     */
1369
-    public function add_question_group($question_group_id_or_obj, $for_primary = false)
1370
-    {
1371
-        // If the row already exists, it will be updated. If it doesn't, it will be inserted.
1372
-        // That's in EE_HABTM_Relation::add_relation_to().
1373
-        return $this->_add_relation_to(
1374
-            $question_group_id_or_obj,
1375
-            'Question_Group',
1376
-            [
1377
-                EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary) => true
1378
-            ]
1379
-        );
1380
-    }
1381
-
1382
-
1383
-    /**
1384
-     * Removes a question group from the event
1385
-     *
1386
-     * @param EE_Question_Group|int $question_group_id_or_obj
1387
-     * @param bool $for_primary if true, the question group will be removed from the primary
1388
-     *                                           registrant, if false will be removed from others. default: false
1389
-     * @return EE_Base_Class|EE_Question_Group
1390
-     * @throws EE_Error
1391
-     * @throws InvalidArgumentException
1392
-     * @throws ReflectionException
1393
-     * @throws InvalidDataTypeException
1394
-     * @throws InvalidInterfaceException
1395
-     */
1396
-    public function remove_question_group($question_group_id_or_obj, $for_primary = false)
1397
-    {
1398
-        // If the question group is used for the other type (primary or additional)
1399
-        // then just update it. If not, delete it outright.
1400
-        $existing_relation = $this->get_first_related(
1401
-            'Event_Question_Group',
1402
-            [
1403
-                [
1404
-                    'QSG_ID' => EEM_Question_Group::instance()->ensure_is_ID($question_group_id_or_obj)
1405
-                ]
1406
-            ]
1407
-        );
1408
-        $field_to_update = EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary);
1409
-        $other_field = EEM_Event_Question_Group::instance()->fieldNameForContext(! $for_primary);
1410
-        if ($existing_relation->get($other_field) === false) {
1411
-            // Delete it. It's now no longer for primary or additional question groups.
1412
-            return $this->_remove_relation_to($question_group_id_or_obj, 'Question_Group');
1413
-        }
1414
-        // Just update it. They'll still use this question group for the other category
1415
-        $existing_relation->save(
1416
-            [
1417
-                $field_to_update => false
1418
-            ]
1419
-        );
1420
-    }
1421
-
1422
-
1423
-    /**
1424
-     * Gets all the question groups, ordering them by QSG_order ascending
1425
-     *
1426
-     * @param array $query_params @see
1427
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1428
-     * @return EE_Base_Class[]|EE_Question_Group[]
1429
-     * @throws EE_Error
1430
-     * @throws ReflectionException
1431
-     */
1432
-    public function question_groups($query_params = array())
1433
-    {
1434
-        $query_params = ! empty($query_params) ? $query_params : array('order_by' => array('QSG_order' => 'ASC'));
1435
-        return $this->get_many_related('Question_Group', $query_params);
1436
-    }
1437
-
1438
-
1439
-    /**
1440
-     * Implementation for EEI_Has_Icon interface method.
1441
-     *
1442
-     * @see EEI_Visual_Representation for comments
1443
-     * @return string
1444
-     */
1445
-    public function get_icon()
1446
-    {
1447
-        return '<span class="dashicons dashicons-flag"></span>';
1448
-    }
1449
-
1450
-
1451
-    /**
1452
-     * Implementation for EEI_Admin_Links interface method.
1453
-     *
1454
-     * @see EEI_Admin_Links for comments
1455
-     * @return string
1456
-     * @throws EE_Error
1457
-     */
1458
-    public function get_admin_details_link()
1459
-    {
1460
-        return $this->get_admin_edit_link();
1461
-    }
1462
-
1463
-
1464
-    /**
1465
-     * Implementation for EEI_Admin_Links interface method.
1466
-     *
1467
-     * @return string
1468
-     * @throws EE_Error*@throws ReflectionException
1469
-     * @see EEI_Admin_Links for comments
1470
-     */
1471
-    public function get_admin_edit_link()
1472
-    {
1473
-        return EEH_URL::add_query_args_and_nonce(
1474
-            array(
1475
-                'page'   => 'espresso_events',
1476
-                'action' => 'edit',
1477
-                'post'   => $this->ID(),
1478
-            ),
1479
-            admin_url('admin.php')
1480
-        );
1481
-    }
1482
-
1483
-
1484
-    /**
1485
-     * Implementation for EEI_Admin_Links interface method.
1486
-     *
1487
-     * @see EEI_Admin_Links for comments
1488
-     * @return string
1489
-     */
1490
-    public function get_admin_settings_link()
1491
-    {
1492
-        return EEH_URL::add_query_args_and_nonce(
1493
-            array(
1494
-                'page'   => 'espresso_events',
1495
-                'action' => 'default_event_settings',
1496
-            ),
1497
-            admin_url('admin.php')
1498
-        );
1499
-    }
1500
-
1501
-
1502
-    /**
1503
-     * Implementation for EEI_Admin_Links interface method.
1504
-     *
1505
-     * @see EEI_Admin_Links for comments
1506
-     * @return string
1507
-     */
1508
-    public function get_admin_overview_link()
1509
-    {
1510
-        return EEH_URL::add_query_args_and_nonce(
1511
-            array(
1512
-                'page'   => 'espresso_events',
1513
-                'action' => 'default',
1514
-            ),
1515
-            admin_url('admin.php')
1516
-        );
1517
-    }
1518
-
1519
-
1520
-    /**
1521
-     * @return string|null
1522
-     * @throws EE_Error
1523
-     * @throws ReflectionException
1524
-     */
1525
-    public function registrationFormUuid(): ?string
1526
-    {
1527
-        return $this->get('FSC_UUID');
1528
-    }
1529
-
1530
-
1531
-    /**
1532
-     * Gets all the form sections for this event
1533
-     *
1534
-     * @return EE_Base_Class[]|EE_Form_Section[]
1535
-     * @throws EE_Error
1536
-     * @throws ReflectionException
1537
-     */
1538
-    public function registrationForm()
1539
-    {
1540
-        $FSC_UUID = $this->registrationFormUuid();
1541
-
1542
-        if (empty($FSC_UUID)) {
1543
-            return [];
1544
-        }
1545
-
1546
-        return EEM_Form_Section::instance()->get_all([
1547
-            [
1548
-                'OR' => [
1549
-                    'FSC_UUID'      => $FSC_UUID, // top level form
1550
-                    'FSC_belongsTo' => $FSC_UUID, // child form sections
1551
-                ]
1552
-                ],
1553
-            'order_by' => ['FSC_order' => 'ASC'],
1554
-        ]);
1555
-    }
1556
-
1557
-
1558
-    /**
1559
-     * @param string $UUID
1560
-     * @throws EE_Error
1561
-     */
1562
-    public function setRegistrationFormUuid(string $UUID): void
1563
-    {
1564
-        if (! Cuid::isCuid($UUID)) {
1565
-            throw new InvalidArgumentException(
1566
-                sprintf(
1567
-                /* translators: 1: UUID value, 2: UUID generator function. */
1568
-                    esc_html__(
1569
-                        'The supplied UUID "%1$s" is invalid or missing. Please use %2$s to generate a valid one.',
1570
-                        'event_espresso'
1571
-                    ),
1572
-                    $UUID,
1573
-                    '`Cuid::cuid()`'
1574
-                )
1575
-            );
1576
-        }
1577
-        $this->set('FSC_UUID', $UUID);
1578
-    }
19
+	/**
20
+	 * cached value for the the logical active status for the event
21
+	 *
22
+	 * @see get_active_status()
23
+	 * @var string
24
+	 */
25
+	protected $_active_status = '';
26
+
27
+	/**
28
+	 * This is just used for caching the Primary Datetime for the Event on initial retrieval
29
+	 *
30
+	 * @var EE_Datetime
31
+	 */
32
+	protected $_Primary_Datetime;
33
+
34
+	/**
35
+	 * @var EventSpacesCalculator $available_spaces_calculator
36
+	 */
37
+	protected $available_spaces_calculator;
38
+
39
+
40
+	/**
41
+	 * @param array  $props_n_values          incoming values
42
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
43
+	 *                                        used.)
44
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
45
+	 *                                        date_format and the second value is the time format
46
+	 * @return EE_Event
47
+	 * @throws EE_Error
48
+	 * @throws ReflectionException
49
+	 */
50
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
51
+	{
52
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
53
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
54
+	}
55
+
56
+
57
+	/**
58
+	 * @param array  $props_n_values  incoming values from the database
59
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
+	 *                                the website will be used.
61
+	 * @return EE_Event
62
+	 * @throws EE_Error
63
+	 * @throws ReflectionException
64
+	 */
65
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
66
+	{
67
+		return new self($props_n_values, true, $timezone);
68
+	}
69
+
70
+
71
+	/**
72
+	 * @return EventSpacesCalculator
73
+	 * @throws \EE_Error
74
+	 */
75
+	public function getAvailableSpacesCalculator()
76
+	{
77
+		if (! $this->available_spaces_calculator instanceof EventSpacesCalculator) {
78
+			$this->available_spaces_calculator = new EventSpacesCalculator($this);
79
+		}
80
+		return $this->available_spaces_calculator;
81
+	}
82
+
83
+
84
+	/**
85
+	 * Overrides parent set() method so that all calls to set( 'status', $status ) can be routed to internal methods
86
+	 *
87
+	 * @param string $field_name
88
+	 * @param mixed  $field_value
89
+	 * @param bool   $use_default
90
+	 * @throws EE_Error
91
+	 * @throws ReflectionException
92
+	 */
93
+	public function set($field_name, $field_value, $use_default = false)
94
+	{
95
+		switch ($field_name) {
96
+			case 'status':
97
+				$this->set_status($field_value, $use_default);
98
+				break;
99
+			default:
100
+				parent::set($field_name, $field_value, $use_default);
101
+		}
102
+	}
103
+
104
+
105
+	/**
106
+	 *    set_status
107
+	 * Checks if event status is being changed to SOLD OUT
108
+	 * and updates event meta data with previous event status
109
+	 * so that we can revert things if/when the event is no longer sold out
110
+	 *
111
+	 * @access public
112
+	 * @param string $new_status
113
+	 * @param bool   $use_default
114
+	 * @return void
115
+	 * @throws EE_Error
116
+	 * @throws ReflectionException
117
+	 */
118
+	public function set_status($new_status = null, $use_default = false)
119
+	{
120
+		// if nothing is set, and we aren't explicitly wanting to reset the status, then just leave
121
+		if (empty($new_status) && ! $use_default) {
122
+			return;
123
+		}
124
+		// get current Event status
125
+		$old_status = $this->status();
126
+		// if status has changed
127
+		if ($old_status !== $new_status) {
128
+			// TO sold_out
129
+			if ($new_status === EEM_Event::sold_out) {
130
+				// save the previous event status so that we can revert if the event is no longer sold out
131
+				$this->add_post_meta('_previous_event_status', $old_status);
132
+				do_action('AHEE__EE_Event__set_status__to_sold_out', $this, $old_status, $new_status);
133
+			// OR FROM  sold_out
134
+			} elseif ($old_status === EEM_Event::sold_out) {
135
+				$this->delete_post_meta('_previous_event_status');
136
+				do_action('AHEE__EE_Event__set_status__from_sold_out', $this, $old_status, $new_status);
137
+			}
138
+			// clear out the active status so that it gets reset the next time it is requested
139
+			$this->_active_status = null;
140
+			// update status
141
+			parent::set('status', $new_status, $use_default);
142
+			do_action('AHEE__EE_Event__set_status__after_update', $this);
143
+			return;
144
+		}
145
+		// even though the old value matches the new value, it's still good to
146
+		// allow the parent set method to have a say
147
+		parent::set('status', $new_status, $use_default);
148
+	}
149
+
150
+
151
+	/**
152
+	 * Gets all the datetimes for this event
153
+	 *
154
+	 * @param array $query_params @see
155
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
156
+	 * @return EE_Base_Class[]|EE_Datetime[]
157
+	 * @throws EE_Error
158
+	 * @throws ReflectionException
159
+	 */
160
+	public function datetimes($query_params = array())
161
+	{
162
+		return $this->get_many_related('Datetime', $query_params);
163
+	}
164
+
165
+
166
+	/**
167
+	 * Gets all the datetimes for this event that are currently ACTIVE,
168
+	 * meaning the datetime has started and has not yet ended.
169
+	 *
170
+	 * @param int|null $start_date      timestamp to use for event date start time, defaults to NOW unless set to null
171
+	 * @param array|null $query_params  will recursively replace default values
172
+	 * @throws EE_Error
173
+	 * @throws ReflectionException
174
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
175
+	 */
176
+	public function activeDatetimes(?int $start_date = 0, ?array $query_params = []): array
177
+	{
178
+		$where = [];
179
+		if ($start_date !== null) {
180
+			$start_date = $start_date ?: time();
181
+			$where['DTT_EVT_start'] = ['<', $start_date];
182
+			$where['DTT_EVT_end']   = ['>', $start_date];
183
+		}
184
+		return $this->get_many_related(
185
+			'Datetime',
186
+			array_replace_recursive(
187
+				[
188
+					$where,
189
+					'order_by' => ['DTT_EVT_start' => 'ASC']
190
+				],
191
+				$query_params
192
+			)
193
+		);
194
+	}
195
+
196
+
197
+	/**
198
+	 * Gets all the datetimes for this event, ordered by DTT_EVT_start in ascending order
199
+	 *
200
+	 * @return EE_Base_Class[]|EE_Datetime[]
201
+	 * @throws EE_Error
202
+	 * @throws ReflectionException
203
+	 */
204
+	public function datetimes_in_chronological_order()
205
+	{
206
+		return $this->get_many_related('Datetime', array('order_by' => array('DTT_EVT_start' => 'ASC')));
207
+	}
208
+
209
+
210
+	/**
211
+	 * Gets all the datetimes for this event, ordered by the DTT_order on the datetime.
212
+	 * @darren, we should probably UNSET timezone on the EEM_Datetime model
213
+	 * after running our query, so that this timezone isn't set for EVERY query
214
+	 * on EEM_Datetime for the rest of the request, no?
215
+	 *
216
+	 * @param boolean $show_expired whether or not to include expired events
217
+	 * @param boolean $show_deleted whether or not to include deleted events
218
+	 * @param null    $limit
219
+	 * @return EE_Datetime[]
220
+	 * @throws EE_Error
221
+	 * @throws ReflectionException
222
+	 */
223
+	public function datetimes_ordered($show_expired = true, $show_deleted = false, $limit = null)
224
+	{
225
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_event_ordered_by_DTT_order(
226
+			$this->ID(),
227
+			$show_expired,
228
+			$show_deleted,
229
+			$limit
230
+		);
231
+	}
232
+
233
+
234
+	/**
235
+	 * Returns one related datetime. Mostly only used by some legacy code.
236
+	 *
237
+	 * @return EE_Base_Class|EE_Datetime
238
+	 * @throws EE_Error
239
+	 * @throws ReflectionException
240
+	 */
241
+	public function first_datetime()
242
+	{
243
+		return $this->get_first_related('Datetime');
244
+	}
245
+
246
+
247
+	/**
248
+	 * Returns the 'primary' datetime for the event
249
+	 *
250
+	 * @param bool $try_to_exclude_expired
251
+	 * @param bool $try_to_exclude_deleted
252
+	 * @return EE_Datetime
253
+	 * @throws EE_Error
254
+	 * @throws ReflectionException
255
+	 */
256
+	public function primary_datetime($try_to_exclude_expired = true, $try_to_exclude_deleted = true)
257
+	{
258
+		if (! empty($this->_Primary_Datetime)) {
259
+			return $this->_Primary_Datetime;
260
+		}
261
+		$this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
262
+			$this->ID(),
263
+			$try_to_exclude_expired,
264
+			$try_to_exclude_deleted
265
+		);
266
+		return $this->_Primary_Datetime;
267
+	}
268
+
269
+
270
+	/**
271
+	 * Gets all the tickets available for purchase of this event
272
+	 *
273
+	 * @param array $query_params @see
274
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
275
+	 * @return EE_Base_Class[]|EE_Ticket[]
276
+	 * @throws EE_Error
277
+	 * @throws ReflectionException
278
+	 */
279
+	public function tickets($query_params = array())
280
+	{
281
+		// first get all datetimes
282
+		$datetimes = $this->datetimes_ordered();
283
+		if (! $datetimes) {
284
+			return array();
285
+		}
286
+		$datetime_ids = array();
287
+		foreach ($datetimes as $datetime) {
288
+			$datetime_ids[] = $datetime->ID();
289
+		}
290
+		$where_params = array('Datetime.DTT_ID' => array('IN', $datetime_ids));
291
+		// if incoming $query_params has where conditions let's merge but not override existing.
292
+		if (is_array($query_params) && isset($query_params[0])) {
293
+			$where_params = array_merge($query_params[0], $where_params);
294
+			unset($query_params[0]);
295
+		}
296
+		// now add $where_params to $query_params
297
+		$query_params[0] = $where_params;
298
+		return EEM_Ticket::instance()->get_all($query_params);
299
+	}
300
+
301
+
302
+	/**
303
+	 * get all unexpired untrashed tickets
304
+	 *
305
+	 * @return EE_Ticket[]
306
+	 * @throws EE_Error
307
+	 */
308
+	public function active_tickets()
309
+	{
310
+		return $this->tickets(
311
+			array(
312
+				array(
313
+					'TKT_end_date' => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
314
+					'TKT_deleted'  => false,
315
+				),
316
+			)
317
+		);
318
+	}
319
+
320
+
321
+	/**
322
+	 * @return bool
323
+	 * @throws EE_Error
324
+	 * @throws ReflectionException
325
+	 */
326
+	public function additional_limit()
327
+	{
328
+		return $this->get('EVT_additional_limit');
329
+	}
330
+
331
+
332
+	/**
333
+	 * @return bool
334
+	 * @throws EE_Error
335
+	 * @throws ReflectionException
336
+	 */
337
+	public function allow_overflow()
338
+	{
339
+		return $this->get('EVT_allow_overflow');
340
+	}
341
+
342
+
343
+	/**
344
+	 * @return bool
345
+	 * @throws EE_Error
346
+	 * @throws ReflectionException
347
+	 */
348
+	public function created()
349
+	{
350
+		return $this->get('EVT_created');
351
+	}
352
+
353
+
354
+	/**
355
+	 * @return bool
356
+	 * @throws EE_Error
357
+	 * @throws ReflectionException
358
+	 */
359
+	public function description()
360
+	{
361
+		return $this->get('EVT_desc');
362
+	}
363
+
364
+
365
+	/**
366
+	 * Runs do_shortcode and wpautop on the description
367
+	 *
368
+	 * @return string of html
369
+	 * @throws EE_Error
370
+	 * @throws ReflectionException
371
+	 */
372
+	public function description_filtered()
373
+	{
374
+		return $this->get_pretty('EVT_desc');
375
+	}
376
+
377
+
378
+	/**
379
+	 * @return bool
380
+	 * @throws EE_Error
381
+	 * @throws ReflectionException
382
+	 */
383
+	public function display_description()
384
+	{
385
+		return $this->get('EVT_display_desc');
386
+	}
387
+
388
+
389
+	/**
390
+	 * @return bool
391
+	 * @throws EE_Error
392
+	 * @throws ReflectionException
393
+	 */
394
+	public function display_ticket_selector()
395
+	{
396
+		return (bool) $this->get('EVT_display_ticket_selector');
397
+	}
398
+
399
+
400
+	/**
401
+	 * @return string
402
+	 * @throws EE_Error
403
+	 * @throws ReflectionException
404
+	 */
405
+	public function external_url()
406
+	{
407
+		return $this->get('EVT_external_URL');
408
+	}
409
+
410
+
411
+	/**
412
+	 * @return bool
413
+	 * @throws EE_Error
414
+	 * @throws ReflectionException
415
+	 */
416
+	public function member_only()
417
+	{
418
+		return $this->get('EVT_member_only');
419
+	}
420
+
421
+
422
+	/**
423
+	 * @return bool
424
+	 * @throws EE_Error
425
+	 * @throws ReflectionException
426
+	 */
427
+	public function phone()
428
+	{
429
+		return $this->get('EVT_phone');
430
+	}
431
+
432
+
433
+	/**
434
+	 * @return bool
435
+	 * @throws EE_Error
436
+	 * @throws ReflectionException
437
+	 */
438
+	public function modified()
439
+	{
440
+		return $this->get('EVT_modified');
441
+	}
442
+
443
+
444
+	/**
445
+	 * @return bool
446
+	 * @throws EE_Error
447
+	 * @throws ReflectionException
448
+	 */
449
+	public function name()
450
+	{
451
+		return $this->get('EVT_name');
452
+	}
453
+
454
+
455
+	/**
456
+	 * @return bool
457
+	 * @throws EE_Error
458
+	 * @throws ReflectionException
459
+	 */
460
+	public function order()
461
+	{
462
+		return $this->get('EVT_order');
463
+	}
464
+
465
+
466
+	/**
467
+	 * @return bool|string
468
+	 * @throws EE_Error
469
+	 * @throws ReflectionException
470
+	 */
471
+	public function default_registration_status()
472
+	{
473
+		$event_default_registration_status = $this->get('EVT_default_registration_status');
474
+		return ! empty($event_default_registration_status)
475
+			? $event_default_registration_status
476
+			: EE_Registry::instance()->CFG->registration->default_STS_ID;
477
+	}
478
+
479
+
480
+	/**
481
+	 * @param int  $num_words
482
+	 * @param null $more
483
+	 * @param bool $not_full_desc
484
+	 * @return bool|string
485
+	 * @throws EE_Error
486
+	 * @throws ReflectionException
487
+	 */
488
+	public function short_description($num_words = 55, $more = null, $not_full_desc = false)
489
+	{
490
+		$short_desc = $this->get('EVT_short_desc');
491
+		if (! empty($short_desc) || $not_full_desc) {
492
+			return $short_desc;
493
+		}
494
+		$full_desc = $this->get('EVT_desc');
495
+		return wp_trim_words($full_desc, $num_words, $more);
496
+	}
497
+
498
+
499
+	/**
500
+	 * @return bool
501
+	 * @throws EE_Error
502
+	 * @throws ReflectionException
503
+	 */
504
+	public function slug()
505
+	{
506
+		return $this->get('EVT_slug');
507
+	}
508
+
509
+
510
+	/**
511
+	 * @return bool
512
+	 * @throws EE_Error
513
+	 * @throws ReflectionException
514
+	 */
515
+	public function timezone_string()
516
+	{
517
+		return $this->get('EVT_timezone_string');
518
+	}
519
+
520
+
521
+	/**
522
+	 * @return bool
523
+	 * @throws EE_Error
524
+	 * @throws ReflectionException
525
+	 */
526
+	public function visible_on()
527
+	{
528
+		return $this->get('EVT_visible_on');
529
+	}
530
+
531
+
532
+	/**
533
+	 * @return int
534
+	 * @throws EE_Error
535
+	 * @throws ReflectionException
536
+	 */
537
+	public function wp_user()
538
+	{
539
+		return $this->get('EVT_wp_user');
540
+	}
541
+
542
+
543
+	/**
544
+	 * @return bool
545
+	 * @throws EE_Error
546
+	 * @throws ReflectionException
547
+	 */
548
+	public function donations()
549
+	{
550
+		return $this->get('EVT_donations');
551
+	}
552
+
553
+
554
+	/**
555
+	 * @param $limit
556
+	 * @throws EE_Error
557
+	 */
558
+	public function set_additional_limit($limit)
559
+	{
560
+		$this->set('EVT_additional_limit', $limit);
561
+	}
562
+
563
+
564
+	/**
565
+	 * @param $created
566
+	 * @throws EE_Error
567
+	 */
568
+	public function set_created($created)
569
+	{
570
+		$this->set('EVT_created', $created);
571
+	}
572
+
573
+
574
+	/**
575
+	 * @param $desc
576
+	 * @throws EE_Error
577
+	 */
578
+	public function set_description($desc)
579
+	{
580
+		$this->set('EVT_desc', $desc);
581
+	}
582
+
583
+
584
+	/**
585
+	 * @param $display_desc
586
+	 * @throws EE_Error
587
+	 */
588
+	public function set_display_description($display_desc)
589
+	{
590
+		$this->set('EVT_display_desc', $display_desc);
591
+	}
592
+
593
+
594
+	/**
595
+	 * @param $display_ticket_selector
596
+	 * @throws EE_Error
597
+	 */
598
+	public function set_display_ticket_selector($display_ticket_selector)
599
+	{
600
+		$this->set('EVT_display_ticket_selector', $display_ticket_selector);
601
+	}
602
+
603
+
604
+	/**
605
+	 * @param $external_url
606
+	 * @throws EE_Error
607
+	 */
608
+	public function set_external_url($external_url)
609
+	{
610
+		$this->set('EVT_external_URL', $external_url);
611
+	}
612
+
613
+
614
+	/**
615
+	 * @param $member_only
616
+	 * @throws EE_Error
617
+	 */
618
+	public function set_member_only($member_only)
619
+	{
620
+		$this->set('EVT_member_only', $member_only);
621
+	}
622
+
623
+
624
+	/**
625
+	 * @param $event_phone
626
+	 * @throws EE_Error
627
+	 */
628
+	public function set_event_phone($event_phone)
629
+	{
630
+		$this->set('EVT_phone', $event_phone);
631
+	}
632
+
633
+
634
+	/**
635
+	 * @param $modified
636
+	 * @throws EE_Error
637
+	 */
638
+	public function set_modified($modified)
639
+	{
640
+		$this->set('EVT_modified', $modified);
641
+	}
642
+
643
+
644
+	/**
645
+	 * @param $name
646
+	 * @throws EE_Error
647
+	 */
648
+	public function set_name($name)
649
+	{
650
+		$this->set('EVT_name', $name);
651
+	}
652
+
653
+
654
+	/**
655
+	 * @param $order
656
+	 * @throws EE_Error
657
+	 */
658
+	public function set_order($order)
659
+	{
660
+		$this->set('EVT_order', $order);
661
+	}
662
+
663
+
664
+	/**
665
+	 * @param $short_desc
666
+	 * @throws EE_Error
667
+	 */
668
+	public function set_short_description($short_desc)
669
+	{
670
+		$this->set('EVT_short_desc', $short_desc);
671
+	}
672
+
673
+
674
+	/**
675
+	 * @param $slug
676
+	 * @throws EE_Error
677
+	 */
678
+	public function set_slug($slug)
679
+	{
680
+		$this->set('EVT_slug', $slug);
681
+	}
682
+
683
+
684
+	/**
685
+	 * @param $timezone_string
686
+	 * @throws EE_Error
687
+	 */
688
+	public function set_timezone_string($timezone_string)
689
+	{
690
+		$this->set('EVT_timezone_string', $timezone_string);
691
+	}
692
+
693
+
694
+	/**
695
+	 * @param $visible_on
696
+	 * @throws EE_Error
697
+	 */
698
+	public function set_visible_on($visible_on)
699
+	{
700
+		$this->set('EVT_visible_on', $visible_on);
701
+	}
702
+
703
+
704
+	/**
705
+	 * @param $wp_user
706
+	 * @throws EE_Error
707
+	 */
708
+	public function set_wp_user($wp_user)
709
+	{
710
+		$this->set('EVT_wp_user', $wp_user);
711
+	}
712
+
713
+
714
+	/**
715
+	 * @param $default_registration_status
716
+	 * @throws EE_Error
717
+	 */
718
+	public function set_default_registration_status($default_registration_status)
719
+	{
720
+		$this->set('EVT_default_registration_status', $default_registration_status);
721
+	}
722
+
723
+
724
+	/**
725
+	 * @param $donations
726
+	 * @throws EE_Error
727
+	 */
728
+	public function set_donations($donations)
729
+	{
730
+		$this->set('EVT_donations', $donations);
731
+	}
732
+
733
+
734
+	/**
735
+	 * Adds a venue to this event
736
+	 *
737
+	 * @param int|EE_Venue /int $venue_id_or_obj
738
+	 * @return EE_Base_Class|EE_Venue
739
+	 * @throws EE_Error
740
+	 * @throws ReflectionException
741
+	 */
742
+	public function add_venue($venue_id_or_obj): EE_Venue
743
+	{
744
+		return $this->_add_relation_to($venue_id_or_obj, 'Venue');
745
+	}
746
+
747
+
748
+	/**
749
+	 * Removes a venue from the event
750
+	 *
751
+	 * @param EE_Venue /int $venue_id_or_obj
752
+	 * @return EE_Base_Class|EE_Venue
753
+	 * @throws EE_Error
754
+	 * @throws ReflectionException
755
+	 */
756
+	public function remove_venue($venue_id_or_obj): EE_Venue
757
+	{
758
+		$venue_id_or_obj = ! empty($venue_id_or_obj) ? $venue_id_or_obj : $this->venue();
759
+		return $this->_remove_relation_to($venue_id_or_obj, 'Venue');
760
+	}
761
+
762
+
763
+	/**
764
+	 * Gets the venue related to the event. May provide additional $query_params if desired
765
+	 *
766
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
767
+	 * @return int
768
+	 * @throws EE_Error
769
+	 * @throws ReflectionException
770
+	 */
771
+	public function venue_ID(array $query_params = array()): int
772
+	{
773
+		$venue = $this->get_first_related('Venue', $query_params);
774
+		return $venue instanceof EE_Venue ? $venue->ID() : 0;
775
+	}
776
+
777
+
778
+	/**
779
+	 * Gets the venue related to the event. May provide additional $query_params if desired
780
+	 *
781
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
782
+	 * @return EE_Base_Class|EE_Venue
783
+	 * @throws EE_Error
784
+	 * @throws ReflectionException
785
+	 */
786
+	public function venue(array $query_params = array())
787
+	{
788
+		return $this->get_first_related('Venue', $query_params);
789
+	}
790
+
791
+
792
+	/**
793
+	 * @param array $query_params
794
+	 * @return EE_Base_Class[]|EE_Venue[]
795
+	 * @throws EE_Error
796
+	 * @throws ReflectionException
797
+	 * @deprecated $VID:$
798
+	 */
799
+	public function venues(array $query_params = array()): array
800
+	{
801
+		return [$this->venue($query_params)];
802
+	}
803
+
804
+
805
+	/**
806
+	 * check if event id is present and if event is published
807
+	 *
808
+	 * @access public
809
+	 * @return boolean true yes, false no
810
+	 * @throws EE_Error
811
+	 * @throws ReflectionException
812
+	 */
813
+	private function _has_ID_and_is_published()
814
+	{
815
+		// first check if event id is present and not NULL,
816
+		// then check if this event is published (or any of the equivalent "published" statuses)
817
+		return
818
+			$this->ID() && $this->ID() !== null
819
+			&& (
820
+				$this->status() === 'publish'
821
+				|| $this->status() === EEM_Event::sold_out
822
+				|| $this->status() === EEM_Event::postponed
823
+				|| $this->status() === EEM_Event::cancelled
824
+			);
825
+	}
826
+
827
+
828
+	/**
829
+	 * This simply compares the internal dates with NOW and determines if the event is upcoming or not.
830
+	 *
831
+	 * @access public
832
+	 * @return boolean true yes, false no
833
+	 * @throws EE_Error
834
+	 * @throws ReflectionException
835
+	 */
836
+	public function is_upcoming()
837
+	{
838
+		// check if event id is present and if this event is published
839
+		if ($this->is_inactive()) {
840
+			return false;
841
+		}
842
+		// set initial value
843
+		$upcoming = false;
844
+		// next let's get all datetimes and loop through them
845
+		$datetimes = $this->datetimes_in_chronological_order();
846
+		foreach ($datetimes as $datetime) {
847
+			if ($datetime instanceof EE_Datetime) {
848
+				// if this dtt is expired then we continue cause one of the other datetimes might be upcoming.
849
+				if ($datetime->is_expired()) {
850
+					continue;
851
+				}
852
+				// if this dtt is active then we return false.
853
+				if ($datetime->is_active()) {
854
+					return false;
855
+				}
856
+				// otherwise let's check upcoming status
857
+				$upcoming = $datetime->is_upcoming();
858
+			}
859
+		}
860
+		return $upcoming;
861
+	}
862
+
863
+
864
+	/**
865
+	 * @return bool
866
+	 * @throws EE_Error
867
+	 * @throws ReflectionException
868
+	 */
869
+	public function is_active()
870
+	{
871
+		// check if event id is present and if this event is published
872
+		if ($this->is_inactive()) {
873
+			return false;
874
+		}
875
+		// set initial value
876
+		$active = false;
877
+		// next let's get all datetimes and loop through them
878
+		$datetimes = $this->datetimes_in_chronological_order();
879
+		foreach ($datetimes as $datetime) {
880
+			if ($datetime instanceof EE_Datetime) {
881
+				// if this dtt is expired then we continue cause one of the other datetimes might be active.
882
+				if ($datetime->is_expired()) {
883
+					continue;
884
+				}
885
+				// if this dtt is upcoming then we return false.
886
+				if ($datetime->is_upcoming()) {
887
+					return false;
888
+				}
889
+				// otherwise let's check active status
890
+				$active = $datetime->is_active();
891
+			}
892
+		}
893
+		return $active;
894
+	}
895
+
896
+
897
+	/**
898
+	 * @return bool
899
+	 * @throws EE_Error
900
+	 * @throws ReflectionException
901
+	 */
902
+	public function is_expired()
903
+	{
904
+		// check if event id is present and if this event is published
905
+		if ($this->is_inactive()) {
906
+			return false;
907
+		}
908
+		// set initial value
909
+		$expired = false;
910
+		// first let's get all datetimes and loop through them
911
+		$datetimes = $this->datetimes_in_chronological_order();
912
+		foreach ($datetimes as $datetime) {
913
+			if ($datetime instanceof EE_Datetime) {
914
+				// if this dtt is upcoming or active then we return false.
915
+				if ($datetime->is_upcoming() || $datetime->is_active()) {
916
+					return false;
917
+				}
918
+				// otherwise let's check active status
919
+				$expired = $datetime->is_expired();
920
+			}
921
+		}
922
+		return $expired;
923
+	}
924
+
925
+
926
+	/**
927
+	 * @return bool
928
+	 * @throws EE_Error
929
+	 */
930
+	public function is_inactive()
931
+	{
932
+		// check if event id is present and if this event is published
933
+		if ($this->_has_ID_and_is_published()) {
934
+			return false;
935
+		}
936
+		return true;
937
+	}
938
+
939
+
940
+	/**
941
+	 * calculate spaces remaining based on "saleable" tickets
942
+	 *
943
+	 * @param array $tickets
944
+	 * @param bool  $filtered
945
+	 * @return int|float
946
+	 * @throws EE_Error
947
+	 * @throws DomainException
948
+	 * @throws UnexpectedEntityException
949
+	 */
950
+	public function spaces_remaining($tickets = array(), $filtered = true)
951
+	{
952
+		$this->getAvailableSpacesCalculator()->setActiveTickets($tickets);
953
+		$spaces_remaining = $this->getAvailableSpacesCalculator()->spacesRemaining();
954
+		return $filtered
955
+			? apply_filters(
956
+				'FHEE_EE_Event__spaces_remaining',
957
+				$spaces_remaining,
958
+				$this,
959
+				$tickets
960
+			)
961
+			: $spaces_remaining;
962
+	}
963
+
964
+
965
+	/**
966
+	 *    perform_sold_out_status_check
967
+	 *    checks all of this events's datetime  reg_limit - sold values to determine if ANY datetimes have spaces
968
+	 *    available... if NOT, then the event status will get toggled to 'sold_out'
969
+	 *
970
+	 * @return bool    return the ACTUAL sold out state.
971
+	 * @throws EE_Error
972
+	 * @throws DomainException
973
+	 * @throws UnexpectedEntityException
974
+	 * @throws ReflectionException
975
+	 */
976
+	public function perform_sold_out_status_check()
977
+	{
978
+		// get all tickets
979
+		$tickets = $this->tickets(
980
+			array(
981
+				'default_where_conditions' => 'none',
982
+				'order_by' => array('TKT_qty' => 'ASC'),
983
+			)
984
+		);
985
+		$all_expired = true;
986
+		foreach ($tickets as $ticket) {
987
+			if (! $ticket->is_expired()) {
988
+				$all_expired = false;
989
+				break;
990
+			}
991
+		}
992
+		// if all the tickets are just expired, then don't update the event status to sold out
993
+		if ($all_expired) {
994
+			return true;
995
+		}
996
+		$spaces_remaining = $this->spaces_remaining($tickets);
997
+		if ($spaces_remaining < 1) {
998
+			if ($this->status() !== EEM_Event::post_status_private) {
999
+				$this->set_status(EEM_Event::sold_out);
1000
+				$this->save();
1001
+			}
1002
+			$sold_out = true;
1003
+		} else {
1004
+			$sold_out = false;
1005
+			// was event previously marked as sold out ?
1006
+			if ($this->status() === EEM_Event::sold_out) {
1007
+				// revert status to previous value, if it was set
1008
+				$previous_event_status = $this->get_post_meta('_previous_event_status', true);
1009
+				if ($previous_event_status) {
1010
+					$this->set_status($previous_event_status);
1011
+					$this->save();
1012
+				}
1013
+			}
1014
+		}
1015
+		do_action('AHEE__EE_Event__perform_sold_out_status_check__end', $this, $sold_out, $spaces_remaining, $tickets);
1016
+		return $sold_out;
1017
+	}
1018
+
1019
+
1020
+	/**
1021
+	 * This returns the total remaining spaces for sale on this event.
1022
+	 *
1023
+	 * @uses EE_Event::total_available_spaces()
1024
+	 * @return float|int
1025
+	 * @throws EE_Error
1026
+	 * @throws DomainException
1027
+	 * @throws UnexpectedEntityException
1028
+	 */
1029
+	public function spaces_remaining_for_sale()
1030
+	{
1031
+		return $this->total_available_spaces(true);
1032
+	}
1033
+
1034
+
1035
+	/**
1036
+	 * This returns the total spaces available for an event
1037
+	 * while considering all the qtys on the tickets and the reg limits
1038
+	 * on the datetimes attached to this event.
1039
+	 *
1040
+	 * @param   bool $consider_sold Whether to consider any tickets that have already sold in our calculation.
1041
+	 *                              If this is false, then we return the most tickets that could ever be sold
1042
+	 *                              for this event with the datetime and tickets setup on the event under optimal
1043
+	 *                              selling conditions.  Otherwise we return a live calculation of spaces available
1044
+	 *                              based on tickets sold.  Depending on setup and stage of sales, this
1045
+	 *                              may appear to equal remaining tickets.  However, the more tickets are
1046
+	 *                              sold out, the more accurate the "live" total is.
1047
+	 * @return float|int
1048
+	 * @throws EE_Error
1049
+	 * @throws DomainException
1050
+	 * @throws UnexpectedEntityException
1051
+	 */
1052
+	public function total_available_spaces($consider_sold = false)
1053
+	{
1054
+		$spaces_available = $consider_sold
1055
+			? $this->getAvailableSpacesCalculator()->spacesRemaining()
1056
+			: $this->getAvailableSpacesCalculator()->totalSpacesAvailable();
1057
+		return apply_filters(
1058
+			'FHEE_EE_Event__total_available_spaces__spaces_available',
1059
+			$spaces_available,
1060
+			$this,
1061
+			$this->getAvailableSpacesCalculator()->getDatetimes(),
1062
+			$this->getAvailableSpacesCalculator()->getActiveTickets()
1063
+		);
1064
+	}
1065
+
1066
+
1067
+	/**
1068
+	 * Checks if the event is set to sold out
1069
+	 *
1070
+	 * @param  bool $actual whether or not to perform calculations to not only figure the
1071
+	 *                      actual status but also to flip the status if necessary to sold
1072
+	 *                      out If false, we just check the existing status of the event
1073
+	 * @return boolean
1074
+	 * @throws EE_Error
1075
+	 */
1076
+	public function is_sold_out($actual = false)
1077
+	{
1078
+		if (! $actual) {
1079
+			return $this->status() === EEM_Event::sold_out;
1080
+		}
1081
+		return $this->perform_sold_out_status_check();
1082
+	}
1083
+
1084
+
1085
+	/**
1086
+	 * Checks if the event is marked as postponed
1087
+	 *
1088
+	 * @return boolean
1089
+	 */
1090
+	public function is_postponed()
1091
+	{
1092
+		return $this->status() === EEM_Event::postponed;
1093
+	}
1094
+
1095
+
1096
+	/**
1097
+	 * Checks if the event is marked as cancelled
1098
+	 *
1099
+	 * @return boolean
1100
+	 */
1101
+	public function is_cancelled()
1102
+	{
1103
+		return $this->status() === EEM_Event::cancelled;
1104
+	}
1105
+
1106
+
1107
+	/**
1108
+	 * Get the logical active status in a hierarchical order for all the datetimes.  Note
1109
+	 * Basically, we order the datetimes by EVT_start_date.  Then first test on whether the event is published.  If its
1110
+	 * NOT published then we test for whether its expired or not.  IF it IS published then we test first on whether an
1111
+	 * event has any active dates.  If no active dates then we check for any upcoming dates.  If no upcoming dates then
1112
+	 * the event is considered expired.
1113
+	 * NOTE: this method does NOT calculate whether the datetimes are sold out when event is published.  Sold Out is a
1114
+	 * status set on the EVENT when it is not published and thus is done
1115
+	 *
1116
+	 * @param bool $reset
1117
+	 * @return bool | string - based on EE_Datetime active constants or FALSE if error.
1118
+	 * @throws EE_Error
1119
+	 * @throws ReflectionException
1120
+	 */
1121
+	public function get_active_status($reset = false)
1122
+	{
1123
+		// if the active status has already been set, then just use that value (unless we are resetting it)
1124
+		if (! empty($this->_active_status) && ! $reset) {
1125
+			return $this->_active_status;
1126
+		}
1127
+		// first check if event id is present on this object
1128
+		if (! $this->ID()) {
1129
+			return false;
1130
+		}
1131
+		$where_params_for_event = array(array('EVT_ID' => $this->ID()));
1132
+		// if event is published:
1133
+		if ($this->status() === EEM_Event::post_status_publish || $this->status() === EEM_Event::post_status_private) {
1134
+			// active?
1135
+			if (
1136
+				EEM_Datetime::instance()->get_datetime_count_for_status(
1137
+					EE_Datetime::active,
1138
+					$where_params_for_event
1139
+				) > 0
1140
+			) {
1141
+				$this->_active_status = EE_Datetime::active;
1142
+			} else {
1143
+				// upcoming?
1144
+				if (
1145
+					EEM_Datetime::instance()->get_datetime_count_for_status(
1146
+						EE_Datetime::upcoming,
1147
+						$where_params_for_event
1148
+					) > 0
1149
+				) {
1150
+					$this->_active_status = EE_Datetime::upcoming;
1151
+				} else {
1152
+					// expired?
1153
+					if (
1154
+						EEM_Datetime::instance()->get_datetime_count_for_status(
1155
+							EE_Datetime::expired,
1156
+							$where_params_for_event
1157
+						) > 0
1158
+					) {
1159
+						$this->_active_status = EE_Datetime::expired;
1160
+					} else {
1161
+						// it would be odd if things make it this far because it basically means there are no datetime's
1162
+						// attached to the event.  So in this case it will just be considered inactive.
1163
+						$this->_active_status = EE_Datetime::inactive;
1164
+					}
1165
+				}
1166
+			}
1167
+		} else {
1168
+			// the event is not published, so let's just set it's active status according to its' post status
1169
+			switch ($this->status()) {
1170
+				case EEM_Event::sold_out:
1171
+					$this->_active_status = EE_Datetime::sold_out;
1172
+					break;
1173
+				case EEM_Event::cancelled:
1174
+					$this->_active_status = EE_Datetime::cancelled;
1175
+					break;
1176
+				case EEM_Event::postponed:
1177
+					$this->_active_status = EE_Datetime::postponed;
1178
+					break;
1179
+				default:
1180
+					$this->_active_status = EE_Datetime::inactive;
1181
+			}
1182
+		}
1183
+		return $this->_active_status;
1184
+	}
1185
+
1186
+
1187
+	/**
1188
+	 *    pretty_active_status
1189
+	 *
1190
+	 * @access public
1191
+	 * @param boolean $echo whether to return (FALSE), or echo out the result (TRUE)
1192
+	 * @return mixed void|string
1193
+	 * @throws EE_Error
1194
+	 * @throws ReflectionException
1195
+	 */
1196
+	public function pretty_active_status($echo = true)
1197
+	{
1198
+		$active_status = $this->get_active_status();
1199
+		$status = '<span class="ee-status ee-status-bg--' . esc_attr($active_status) . ' event-active-status-' .
1200
+				  esc_attr($active_status) . '">'
1201
+				  . EEH_Template::pretty_status($active_status, false, 'sentence')
1202
+				  . '</span>';
1203
+		if ($echo) {
1204
+			echo wp_kses($status, AllowedTags::getAllowedTags());
1205
+			return '';
1206
+		}
1207
+		return $status; // already escaped
1208
+	}
1209
+
1210
+
1211
+	/**
1212
+	 * @return bool|int
1213
+	 * @throws EE_Error
1214
+	 * @throws ReflectionException
1215
+	 */
1216
+	public function get_number_of_tickets_sold()
1217
+	{
1218
+		$tkt_sold = 0;
1219
+		if (! $this->ID()) {
1220
+			return 0;
1221
+		}
1222
+		$datetimes = $this->datetimes();
1223
+		foreach ($datetimes as $datetime) {
1224
+			if ($datetime instanceof EE_Datetime) {
1225
+				$tkt_sold += $datetime->sold();
1226
+			}
1227
+		}
1228
+		return $tkt_sold;
1229
+	}
1230
+
1231
+
1232
+	/**
1233
+	 * This just returns a count of all the registrations for this event
1234
+	 *
1235
+	 * @access  public
1236
+	 * @return int
1237
+	 * @throws EE_Error
1238
+	 */
1239
+	public function get_count_of_all_registrations()
1240
+	{
1241
+		return EEM_Event::instance()->count_related($this, 'Registration');
1242
+	}
1243
+
1244
+
1245
+	/**
1246
+	 * This returns the ticket with the earliest start time that is
1247
+	 * available for this event (across all datetimes attached to the event)
1248
+	 *
1249
+	 * @return EE_Base_Class|EE_Ticket|null
1250
+	 * @throws EE_Error
1251
+	 * @throws ReflectionException
1252
+	 */
1253
+	public function get_ticket_with_earliest_start_time()
1254
+	{
1255
+		$where['Datetime.EVT_ID'] = $this->ID();
1256
+		$query_params = array($where, 'order_by' => array('TKT_start_date' => 'ASC'));
1257
+		return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1258
+	}
1259
+
1260
+
1261
+	/**
1262
+	 * This returns the ticket with the latest end time that is available
1263
+	 * for this event (across all datetimes attached to the event)
1264
+	 *
1265
+	 * @return EE_Base_Class|EE_Ticket|null
1266
+	 * @throws EE_Error
1267
+	 * @throws ReflectionException
1268
+	 */
1269
+	public function get_ticket_with_latest_end_time()
1270
+	{
1271
+		$where['Datetime.EVT_ID'] = $this->ID();
1272
+		$query_params = array($where, 'order_by' => array('TKT_end_date' => 'DESC'));
1273
+		return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1274
+	}
1275
+
1276
+
1277
+	/**
1278
+	 * This returns the number of different ticket types currently on sale for this event.
1279
+	 *
1280
+	 * @return int
1281
+	 * @throws EE_Error
1282
+	 * @throws ReflectionException
1283
+	 */
1284
+	public function countTicketsOnSale()
1285
+	{
1286
+		$where = array(
1287
+			'Datetime.EVT_ID' => $this->ID(),
1288
+			'TKT_start_date'  => array('<', time()),
1289
+			'TKT_end_date'    => array('>', time()),
1290
+		);
1291
+		return EEM_Ticket::instance()->count(array($where));
1292
+	}
1293
+
1294
+
1295
+	/**
1296
+	 * This returns whether there are any tickets on sale for this event.
1297
+	 *
1298
+	 * @return bool true = YES tickets on sale.
1299
+	 * @throws EE_Error
1300
+	 */
1301
+	public function tickets_on_sale()
1302
+	{
1303
+		return $this->countTicketsOnSale() > 0;
1304
+	}
1305
+
1306
+
1307
+	/**
1308
+	 * Gets the URL for viewing this event on the front-end. Overrides parent
1309
+	 * to check for an external URL first
1310
+	 *
1311
+	 * @return string
1312
+	 * @throws EE_Error
1313
+	 */
1314
+	public function get_permalink()
1315
+	{
1316
+		if ($this->external_url()) {
1317
+			return $this->external_url();
1318
+		}
1319
+		return parent::get_permalink();
1320
+	}
1321
+
1322
+
1323
+	/**
1324
+	 * Gets the first term for 'espresso_event_categories' we can find
1325
+	 *
1326
+	 * @param array $query_params @see
1327
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1328
+	 * @return EE_Base_Class|EE_Term|null
1329
+	 * @throws EE_Error
1330
+	 * @throws ReflectionException
1331
+	 */
1332
+	public function first_event_category($query_params = array())
1333
+	{
1334
+		$query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1335
+		$query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1336
+		return EEM_Term::instance()->get_one($query_params);
1337
+	}
1338
+
1339
+
1340
+	/**
1341
+	 * Gets all terms for 'espresso_event_categories' we can find
1342
+	 *
1343
+	 * @param array $query_params
1344
+	 * @return EE_Base_Class[]|EE_Term[]
1345
+	 * @throws EE_Error
1346
+	 * @throws ReflectionException
1347
+	 */
1348
+	public function get_all_event_categories($query_params = array())
1349
+	{
1350
+		$query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1351
+		$query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1352
+		return EEM_Term::instance()->get_all($query_params);
1353
+	}
1354
+
1355
+
1356
+	/**
1357
+	 * Adds a question group to this event
1358
+	 *
1359
+	 * @param EE_Question_Group|int $question_group_id_or_obj
1360
+	 * @param bool $for_primary if true, the question group will be added for the primary
1361
+	 *                                           registrant, if false will be added for others. default: false
1362
+	 * @return EE_Base_Class|EE_Question_Group
1363
+	 * @throws EE_Error
1364
+	 * @throws InvalidArgumentException
1365
+	 * @throws InvalidDataTypeException
1366
+	 * @throws InvalidInterfaceException
1367
+	 * @throws ReflectionException
1368
+	 */
1369
+	public function add_question_group($question_group_id_or_obj, $for_primary = false)
1370
+	{
1371
+		// If the row already exists, it will be updated. If it doesn't, it will be inserted.
1372
+		// That's in EE_HABTM_Relation::add_relation_to().
1373
+		return $this->_add_relation_to(
1374
+			$question_group_id_or_obj,
1375
+			'Question_Group',
1376
+			[
1377
+				EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary) => true
1378
+			]
1379
+		);
1380
+	}
1381
+
1382
+
1383
+	/**
1384
+	 * Removes a question group from the event
1385
+	 *
1386
+	 * @param EE_Question_Group|int $question_group_id_or_obj
1387
+	 * @param bool $for_primary if true, the question group will be removed from the primary
1388
+	 *                                           registrant, if false will be removed from others. default: false
1389
+	 * @return EE_Base_Class|EE_Question_Group
1390
+	 * @throws EE_Error
1391
+	 * @throws InvalidArgumentException
1392
+	 * @throws ReflectionException
1393
+	 * @throws InvalidDataTypeException
1394
+	 * @throws InvalidInterfaceException
1395
+	 */
1396
+	public function remove_question_group($question_group_id_or_obj, $for_primary = false)
1397
+	{
1398
+		// If the question group is used for the other type (primary or additional)
1399
+		// then just update it. If not, delete it outright.
1400
+		$existing_relation = $this->get_first_related(
1401
+			'Event_Question_Group',
1402
+			[
1403
+				[
1404
+					'QSG_ID' => EEM_Question_Group::instance()->ensure_is_ID($question_group_id_or_obj)
1405
+				]
1406
+			]
1407
+		);
1408
+		$field_to_update = EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary);
1409
+		$other_field = EEM_Event_Question_Group::instance()->fieldNameForContext(! $for_primary);
1410
+		if ($existing_relation->get($other_field) === false) {
1411
+			// Delete it. It's now no longer for primary or additional question groups.
1412
+			return $this->_remove_relation_to($question_group_id_or_obj, 'Question_Group');
1413
+		}
1414
+		// Just update it. They'll still use this question group for the other category
1415
+		$existing_relation->save(
1416
+			[
1417
+				$field_to_update => false
1418
+			]
1419
+		);
1420
+	}
1421
+
1422
+
1423
+	/**
1424
+	 * Gets all the question groups, ordering them by QSG_order ascending
1425
+	 *
1426
+	 * @param array $query_params @see
1427
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1428
+	 * @return EE_Base_Class[]|EE_Question_Group[]
1429
+	 * @throws EE_Error
1430
+	 * @throws ReflectionException
1431
+	 */
1432
+	public function question_groups($query_params = array())
1433
+	{
1434
+		$query_params = ! empty($query_params) ? $query_params : array('order_by' => array('QSG_order' => 'ASC'));
1435
+		return $this->get_many_related('Question_Group', $query_params);
1436
+	}
1437
+
1438
+
1439
+	/**
1440
+	 * Implementation for EEI_Has_Icon interface method.
1441
+	 *
1442
+	 * @see EEI_Visual_Representation for comments
1443
+	 * @return string
1444
+	 */
1445
+	public function get_icon()
1446
+	{
1447
+		return '<span class="dashicons dashicons-flag"></span>';
1448
+	}
1449
+
1450
+
1451
+	/**
1452
+	 * Implementation for EEI_Admin_Links interface method.
1453
+	 *
1454
+	 * @see EEI_Admin_Links for comments
1455
+	 * @return string
1456
+	 * @throws EE_Error
1457
+	 */
1458
+	public function get_admin_details_link()
1459
+	{
1460
+		return $this->get_admin_edit_link();
1461
+	}
1462
+
1463
+
1464
+	/**
1465
+	 * Implementation for EEI_Admin_Links interface method.
1466
+	 *
1467
+	 * @return string
1468
+	 * @throws EE_Error*@throws ReflectionException
1469
+	 * @see EEI_Admin_Links for comments
1470
+	 */
1471
+	public function get_admin_edit_link()
1472
+	{
1473
+		return EEH_URL::add_query_args_and_nonce(
1474
+			array(
1475
+				'page'   => 'espresso_events',
1476
+				'action' => 'edit',
1477
+				'post'   => $this->ID(),
1478
+			),
1479
+			admin_url('admin.php')
1480
+		);
1481
+	}
1482
+
1483
+
1484
+	/**
1485
+	 * Implementation for EEI_Admin_Links interface method.
1486
+	 *
1487
+	 * @see EEI_Admin_Links for comments
1488
+	 * @return string
1489
+	 */
1490
+	public function get_admin_settings_link()
1491
+	{
1492
+		return EEH_URL::add_query_args_and_nonce(
1493
+			array(
1494
+				'page'   => 'espresso_events',
1495
+				'action' => 'default_event_settings',
1496
+			),
1497
+			admin_url('admin.php')
1498
+		);
1499
+	}
1500
+
1501
+
1502
+	/**
1503
+	 * Implementation for EEI_Admin_Links interface method.
1504
+	 *
1505
+	 * @see EEI_Admin_Links for comments
1506
+	 * @return string
1507
+	 */
1508
+	public function get_admin_overview_link()
1509
+	{
1510
+		return EEH_URL::add_query_args_and_nonce(
1511
+			array(
1512
+				'page'   => 'espresso_events',
1513
+				'action' => 'default',
1514
+			),
1515
+			admin_url('admin.php')
1516
+		);
1517
+	}
1518
+
1519
+
1520
+	/**
1521
+	 * @return string|null
1522
+	 * @throws EE_Error
1523
+	 * @throws ReflectionException
1524
+	 */
1525
+	public function registrationFormUuid(): ?string
1526
+	{
1527
+		return $this->get('FSC_UUID');
1528
+	}
1529
+
1530
+
1531
+	/**
1532
+	 * Gets all the form sections for this event
1533
+	 *
1534
+	 * @return EE_Base_Class[]|EE_Form_Section[]
1535
+	 * @throws EE_Error
1536
+	 * @throws ReflectionException
1537
+	 */
1538
+	public function registrationForm()
1539
+	{
1540
+		$FSC_UUID = $this->registrationFormUuid();
1541
+
1542
+		if (empty($FSC_UUID)) {
1543
+			return [];
1544
+		}
1545
+
1546
+		return EEM_Form_Section::instance()->get_all([
1547
+			[
1548
+				'OR' => [
1549
+					'FSC_UUID'      => $FSC_UUID, // top level form
1550
+					'FSC_belongsTo' => $FSC_UUID, // child form sections
1551
+				]
1552
+				],
1553
+			'order_by' => ['FSC_order' => 'ASC'],
1554
+		]);
1555
+	}
1556
+
1557
+
1558
+	/**
1559
+	 * @param string $UUID
1560
+	 * @throws EE_Error
1561
+	 */
1562
+	public function setRegistrationFormUuid(string $UUID): void
1563
+	{
1564
+		if (! Cuid::isCuid($UUID)) {
1565
+			throw new InvalidArgumentException(
1566
+				sprintf(
1567
+				/* translators: 1: UUID value, 2: UUID generator function. */
1568
+					esc_html__(
1569
+						'The supplied UUID "%1$s" is invalid or missing. Please use %2$s to generate a valid one.',
1570
+						'event_espresso'
1571
+					),
1572
+					$UUID,
1573
+					'`Cuid::cuid()`'
1574
+				)
1575
+			);
1576
+		}
1577
+		$this->set('FSC_UUID', $UUID);
1578
+	}
1579 1579
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Payment.class.php 1 patch
Indentation   +870 added lines, -870 removed lines patch added patch discarded remove patch
@@ -11,874 +11,874 @@
 block discarded – undo
11 11
  */
12 12
 class EE_Payment extends EE_Base_Class implements EEI_Payment
13 13
 {
14
-    /**
15
-     * @param array  $props_n_values          incoming values
16
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
17
-     *                                        used.)
18
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
19
-     *                                        date_format and the second value is the time format
20
-     * @return EE_Payment
21
-     * @throws \EE_Error
22
-     */
23
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
24
-    {
25
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
26
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
27
-    }
28
-
29
-
30
-    /**
31
-     * @param array  $props_n_values  incoming values from the database
32
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
33
-     *                                the website will be used.
34
-     * @return EE_Payment
35
-     * @throws \EE_Error
36
-     */
37
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
38
-    {
39
-        return new self($props_n_values, true, $timezone);
40
-    }
41
-
42
-
43
-    /**
44
-     * Set Transaction ID
45
-     *
46
-     * @access public
47
-     * @param int $TXN_ID
48
-     * @throws \EE_Error
49
-     */
50
-    public function set_transaction_id($TXN_ID = 0)
51
-    {
52
-        $this->set('TXN_ID', $TXN_ID);
53
-    }
54
-
55
-
56
-    /**
57
-     * Gets the transaction related to this payment
58
-     *
59
-     * @return EE_Transaction
60
-     * @throws \EE_Error
61
-     */
62
-    public function transaction()
63
-    {
64
-        return $this->get_first_related('Transaction');
65
-    }
66
-
67
-
68
-    /**
69
-     * Set Status
70
-     *
71
-     * @access public
72
-     * @param string $STS_ID
73
-     * @throws \EE_Error
74
-     */
75
-    public function set_status($STS_ID = '')
76
-    {
77
-        $this->set('STS_ID', $STS_ID);
78
-    }
79
-
80
-
81
-    /**
82
-     * Set Payment Timestamp
83
-     *
84
-     * @access public
85
-     * @param int $timestamp
86
-     * @throws \EE_Error
87
-     */
88
-    public function set_timestamp($timestamp = 0)
89
-    {
90
-        $this->set('PAY_timestamp', $timestamp);
91
-    }
92
-
93
-
94
-    /**
95
-     * Set Payment Method
96
-     *
97
-     * @access public
98
-     * @param string $PAY_source
99
-     * @throws \EE_Error
100
-     */
101
-    public function set_source($PAY_source = '')
102
-    {
103
-        $this->set('PAY_source', $PAY_source);
104
-    }
105
-
106
-
107
-    /**
108
-     * Set Payment Amount
109
-     *
110
-     * @access public
111
-     * @param float $amount
112
-     * @throws \EE_Error
113
-     */
114
-    public function set_amount($amount = 0.00)
115
-    {
116
-        $this->set('PAY_amount', (float) $amount);
117
-    }
118
-
119
-
120
-    /**
121
-     * Set Payment Gateway Response
122
-     *
123
-     * @access public
124
-     * @param string $gateway_response
125
-     * @throws \EE_Error
126
-     */
127
-    public function set_gateway_response($gateway_response = '')
128
-    {
129
-        $this->set('PAY_gateway_response', $gateway_response);
130
-    }
131
-
132
-
133
-    /**
134
-     * Returns the name of the payment method used on this payment (previously known merely as 'gateway')
135
-     * but since 4.6.0, payment methods are models and the payment keeps a foreign key to the payment method
136
-     * used on it
137
-     *
138
-     * @deprecated
139
-     * @return string
140
-     * @throws \EE_Error
141
-     */
142
-    public function gateway()
143
-    {
144
-        EE_Error::doing_it_wrong(
145
-            'EE_Payment::gateway',
146
-            esc_html__(
147
-                'The method EE_Payment::gateway() has been deprecated. Consider instead using EE_Payment::payment_method()->name()',
148
-                'event_espresso'
149
-            ),
150
-            '4.6.0'
151
-        );
152
-        return $this->payment_method() ? $this->payment_method()->name() : esc_html__('Unknown', 'event_espresso');
153
-    }
154
-
155
-
156
-    /**
157
-     * Set Gateway Transaction ID
158
-     *
159
-     * @access public
160
-     * @param string $txn_id_chq_nmbr
161
-     * @throws \EE_Error
162
-     */
163
-    public function set_txn_id_chq_nmbr($txn_id_chq_nmbr = '')
164
-    {
165
-        $this->set('PAY_txn_id_chq_nmbr', $txn_id_chq_nmbr);
166
-    }
167
-
168
-
169
-    /**
170
-     * Set Purchase Order Number
171
-     *
172
-     * @access public
173
-     * @param string $po_number
174
-     * @throws \EE_Error
175
-     */
176
-    public function set_po_number($po_number = '')
177
-    {
178
-        $this->set('PAY_po_number', $po_number);
179
-    }
180
-
181
-
182
-    /**
183
-     * Set Extra Accounting Field
184
-     *
185
-     * @access public
186
-     * @param string $extra_accntng
187
-     * @throws \EE_Error
188
-     */
189
-    public function set_extra_accntng($extra_accntng = '')
190
-    {
191
-        $this->set('PAY_extra_accntng', $extra_accntng);
192
-    }
193
-
194
-
195
-    /**
196
-     * Set Payment made via admin flag
197
-     *
198
-     * @access public
199
-     * @param bool $via_admin
200
-     * @throws \EE_Error
201
-     */
202
-    public function set_payment_made_via_admin($via_admin = false)
203
-    {
204
-        if ($via_admin) {
205
-            $this->set('PAY_source', EEM_Payment_Method::scope_admin);
206
-        } else {
207
-            $this->set('PAY_source', EEM_Payment_Method::scope_cart);
208
-        }
209
-    }
210
-
211
-
212
-    /**
213
-     * Set Payment Details
214
-     *
215
-     * @access public
216
-     * @param string|array $details
217
-     * @throws \EE_Error
218
-     */
219
-    public function set_details($details = '')
220
-    {
221
-        if (is_array($details)) {
222
-            array_walk_recursive($details, array($this, '_strip_all_tags_within_array'));
223
-        } else {
224
-            $details = wp_strip_all_tags($details);
225
-        }
226
-        $this->set('PAY_details', $details);
227
-    }
228
-
229
-
230
-    /**
231
-     * Sets redirect_url
232
-     *
233
-     * @param string $redirect_url
234
-     * @throws \EE_Error
235
-     */
236
-    public function set_redirect_url($redirect_url)
237
-    {
238
-        $this->set('PAY_redirect_url', $redirect_url);
239
-    }
240
-
241
-
242
-    /**
243
-     * Sets redirect_args
244
-     *
245
-     * @param array $redirect_args
246
-     * @throws \EE_Error
247
-     */
248
-    public function set_redirect_args($redirect_args)
249
-    {
250
-        $this->set('PAY_redirect_args', $redirect_args);
251
-    }
252
-
253
-
254
-    /**
255
-     * get Payment Transaction ID
256
-     *
257
-     * @access public
258
-     * @throws \EE_Error
259
-     */
260
-    public function TXN_ID()
261
-    {
262
-        return $this->get('TXN_ID');
263
-    }
264
-
265
-
266
-    /**
267
-     * get Payment Status
268
-     *
269
-     * @access public
270
-     * @throws \EE_Error
271
-     */
272
-    public function status()
273
-    {
274
-        return $this->get('STS_ID');
275
-    }
276
-
277
-
278
-    /**
279
-     * get Payment Status
280
-     *
281
-     * @access public
282
-     * @throws \EE_Error
283
-     */
284
-    public function STS_ID()
285
-    {
286
-        return $this->get('STS_ID');
287
-    }
288
-
289
-
290
-    /**
291
-     * get Payment Timestamp
292
-     *
293
-     * @access public
294
-     * @param string $dt_frmt
295
-     * @param string $tm_frmt
296
-     * @return string
297
-     * @throws \EE_Error
298
-     */
299
-    public function timestamp($dt_frmt = '', $tm_frmt = '')
300
-    {
301
-        return $this->get_i18n_datetime('PAY_timestamp', trim($dt_frmt . ' ' . $tm_frmt));
302
-    }
303
-
304
-
305
-    /**
306
-     * get Payment Source
307
-     *
308
-     * @access public
309
-     * @throws \EE_Error
310
-     */
311
-    public function source()
312
-    {
313
-        return $this->get('PAY_source');
314
-    }
315
-
316
-
317
-    /**
318
-     * get Payment Amount
319
-     *
320
-     * @access public
321
-     * @return float
322
-     * @throws \EE_Error
323
-     */
324
-    public function amount()
325
-    {
326
-        return (float) $this->get('PAY_amount');
327
-    }
328
-
329
-
330
-    /**
331
-     * @return mixed
332
-     * @throws \EE_Error
333
-     */
334
-    public function amount_no_code()
335
-    {
336
-        return $this->get_pretty('PAY_amount', 'no_currency_code');
337
-    }
338
-
339
-
340
-    /**
341
-     * get Payment Gateway Response
342
-     *
343
-     * @access public
344
-     * @throws \EE_Error
345
-     */
346
-    public function gateway_response()
347
-    {
348
-        return $this->get('PAY_gateway_response');
349
-    }
350
-
351
-
352
-    /**
353
-     * get Payment Gateway Transaction ID
354
-     *
355
-     * @access public
356
-     * @throws \EE_Error
357
-     */
358
-    public function txn_id_chq_nmbr()
359
-    {
360
-        return $this->get('PAY_txn_id_chq_nmbr');
361
-    }
362
-
363
-
364
-    /**
365
-     * get Purchase Order Number
366
-     *
367
-     * @access public
368
-     * @throws \EE_Error
369
-     */
370
-    public function po_number()
371
-    {
372
-        return $this->get('PAY_po_number');
373
-    }
374
-
375
-
376
-    /**
377
-     * get Extra Accounting Field
378
-     *
379
-     * @access public
380
-     * @throws \EE_Error
381
-     */
382
-    public function extra_accntng()
383
-    {
384
-        return $this->get('PAY_extra_accntng');
385
-    }
386
-
387
-
388
-    /**
389
-     * get Payment made via admin source
390
-     *
391
-     * @access public
392
-     * @throws \EE_Error
393
-     */
394
-    public function payment_made_via_admin()
395
-    {
396
-        return ($this->get('PAY_source') === EEM_Payment_Method::scope_admin);
397
-    }
398
-
399
-
400
-    /**
401
-     * get Payment Details
402
-     *
403
-     * @access public
404
-     * @throws \EE_Error
405
-     */
406
-    public function details()
407
-    {
408
-        return $this->get('PAY_details');
409
-    }
410
-
411
-
412
-    /**
413
-     * Gets redirect_url
414
-     *
415
-     * @return string
416
-     * @throws \EE_Error
417
-     */
418
-    public function redirect_url()
419
-    {
420
-        return $this->get('PAY_redirect_url');
421
-    }
422
-
423
-
424
-    /**
425
-     * Gets redirect_args
426
-     *
427
-     * @return array
428
-     * @throws \EE_Error
429
-     */
430
-    public function redirect_args()
431
-    {
432
-        return $this->get('PAY_redirect_args');
433
-    }
434
-
435
-
436
-    /**
437
-     * echoes $this->pretty_status()
438
-     *
439
-     * @param bool $show_icons
440
-     * @return void
441
-     * @throws \EE_Error
442
-     */
443
-    public function e_pretty_status($show_icons = false)
444
-    {
445
-        echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
446
-    }
447
-
448
-
449
-    /**
450
-     * returns a pretty version of the status, good for displaying to users
451
-     *
452
-     * @param bool $show_icons
453
-     * @return string
454
-     * @throws \EE_Error
455
-     */
456
-    public function pretty_status($show_icons = false)
457
-    {
458
-        $status = EEM_Status::instance()->localized_status(
459
-            array($this->STS_ID() => esc_html__('unknown', 'event_espresso')),
460
-            false,
461
-            'sentence'
462
-        );
463
-        $icon = '';
464
-        switch ($this->STS_ID()) {
465
-            case EEM_Payment::status_id_approved:
466
-                $icon = $show_icons
467
-                    ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>'
468
-                    : '';
469
-                break;
470
-            case EEM_Payment::status_id_pending:
471
-                $icon = $show_icons
472
-                    ? '<span class="dashicons dashicons-clock ee-icon-size-16 orange-text"></span>'
473
-                    : '';
474
-                break;
475
-            case EEM_Payment::status_id_cancelled:
476
-                $icon = $show_icons
477
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
478
-                    : '';
479
-                break;
480
-            case EEM_Payment::status_id_declined:
481
-                $icon = $show_icons
482
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
483
-                    : '';
484
-                break;
485
-        }
486
-        return $icon . $status[ $this->STS_ID() ];
487
-    }
488
-
489
-
490
-    /**
491
-     * For determining the status of the payment
492
-     *
493
-     * @return boolean whether the payment is approved or not
494
-     * @throws \EE_Error
495
-     */
496
-    public function is_approved()
497
-    {
498
-        return $this->status_is(EEM_Payment::status_id_approved);
499
-    }
500
-
501
-
502
-    /**
503
-     * Generally determines if the status of this payment equals
504
-     * the $STS_ID string
505
-     *
506
-     * @param string $STS_ID an ID from the esp_status table/
507
-     *                       one of the status_id_* on the EEM_Payment model
508
-     * @return boolean whether the status of this payment equals the status id
509
-     * @throws \EE_Error
510
-     */
511
-    protected function status_is($STS_ID)
512
-    {
513
-        return $STS_ID === $this->STS_ID() ? true : false;
514
-    }
515
-
516
-
517
-    /**
518
-     * For determining the status of the payment
519
-     *
520
-     * @return boolean whether the payment is pending or not
521
-     * @throws \EE_Error
522
-     */
523
-    public function is_pending()
524
-    {
525
-        return $this->status_is(EEM_Payment::status_id_pending);
526
-    }
527
-
528
-
529
-    /**
530
-     * For determining the status of the payment
531
-     *
532
-     * @return boolean
533
-     * @throws \EE_Error
534
-     */
535
-    public function is_cancelled()
536
-    {
537
-        return $this->status_is(EEM_Payment::status_id_cancelled);
538
-    }
539
-
540
-
541
-    /**
542
-     * For determining the status of the payment
543
-     *
544
-     * @return boolean
545
-     * @throws \EE_Error
546
-     */
547
-    public function is_declined()
548
-    {
549
-        return $this->status_is(EEM_Payment::status_id_declined);
550
-    }
551
-
552
-
553
-    /**
554
-     * For determining the status of the payment
555
-     *
556
-     * @return boolean
557
-     * @throws \EE_Error
558
-     */
559
-    public function is_failed()
560
-    {
561
-        return $this->status_is(EEM_Payment::status_id_failed);
562
-    }
563
-
564
-
565
-    /**
566
-     * For determining if the payment is actually a refund ( ie: has a negative value )
567
-     *
568
-     * @return boolean
569
-     * @throws \EE_Error
570
-     */
571
-    public function is_a_refund()
572
-    {
573
-        return $this->amount() < 0 ? true : false;
574
-    }
575
-
576
-
577
-    /**
578
-     * Get the status object of this object
579
-     *
580
-     * @return EE_Status
581
-     * @throws \EE_Error
582
-     */
583
-    public function status_obj()
584
-    {
585
-        return $this->get_first_related('Status');
586
-    }
587
-
588
-
589
-    /**
590
-     * Gets all the extra meta info on this payment
591
-     *
592
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
593
-     * @return EE_Extra_Meta
594
-     * @throws \EE_Error
595
-     */
596
-    public function extra_meta($query_params = array())
597
-    {
598
-        return $this->get_many_related('Extra_Meta', $query_params);
599
-    }
600
-
601
-
602
-    /**
603
-     * Gets the last-used payment method on this transaction
604
-     * (we COULD just use the last-made payment, but some payment methods, namely
605
-     * offline ones, dont' create payments)
606
-     *
607
-     * @return EE_Payment_Method
608
-     * @throws \EE_Error
609
-     */
610
-    public function payment_method()
611
-    {
612
-        return $this->get_first_related('Payment_Method');
613
-    }
614
-
615
-
616
-    /**
617
-     * Gets the HTML for redirecting the user to an offsite gateway
618
-     * You can pass it special content to put inside the form, or use
619
-     * the default inner content (or possibly generate this all yourself using
620
-     * redirect_url() and redirect_args() or redirect_args_as_inputs()).
621
-     * Creates a POST request by default, but if no redirect args are specified, creates a GET request instead
622
-     * (and any querystring variables in the redirect_url are converted into html inputs
623
-     * so browsers submit them properly)
624
-     *
625
-     * @param string $inside_form_html
626
-     * @return string html
627
-     * @throws \EE_Error
628
-     */
629
-    public function redirect_form($inside_form_html = null)
630
-    {
631
-        $redirect_url = $this->redirect_url();
632
-        if (! empty($redirect_url)) {
633
-            // what ? no inner form content?
634
-            if ($inside_form_html === null) {
635
-                $inside_form_html = EEH_HTML::p(
636
-                    sprintf(
637
-                        esc_html__(
638
-                            'If you are not automatically redirected to the payment website within 10 seconds... %1$s %2$s Click Here %3$s',
639
-                            'event_espresso'
640
-                        ),
641
-                        EEH_HTML::br(2),
642
-                        '<input type="submit" value="',
643
-                        '">'
644
-                    ),
645
-                    '',
646
-                    '',
647
-                    'text-align:center;'
648
-                );
649
-            }
650
-            $method = apply_filters(
651
-                'FHEE__EE_Payment__redirect_form__method',
652
-                $this->redirect_args() ? 'POST' : 'GET',
653
-                $this
654
-            );
655
-            // if it's a GET request, we need to remove all the GET params in the querystring
656
-            // and put them into the form instead
657
-            if ($method === 'GET') {
658
-                $querystring = parse_url($redirect_url, PHP_URL_QUERY);
659
-                $get_params = null;
660
-                parse_str($querystring, $get_params);
661
-                $inside_form_html .= $this->_args_as_inputs($get_params);
662
-                $redirect_url = str_replace('?' . $querystring, '', $redirect_url);
663
-            }
664
-            $form = EEH_HTML::nl(1)
665
-                    . '<form method="'
666
-                    . $method
667
-                    . '" name="gateway_form" action="'
668
-                    . $redirect_url
669
-                    . '">';
670
-            $form .= EEH_HTML::nl(1) . $this->redirect_args_as_inputs();
671
-            $form .= $inside_form_html;
672
-            $form .= EEH_HTML::nl(-1) . '</form>' . EEH_HTML::nl(-1);
673
-            return $form;
674
-        } else {
675
-            return null;
676
-        }
677
-    }
678
-
679
-
680
-    /**
681
-     * Changes all the name-value pairs of the redirect args into html inputs
682
-     * and returns the html as a string
683
-     *
684
-     * @return string
685
-     * @throws \EE_Error
686
-     */
687
-    public function redirect_args_as_inputs()
688
-    {
689
-        return $this->_args_as_inputs($this->redirect_args());
690
-    }
691
-
692
-
693
-    /**
694
-     * Converts a 2d array of key-value pairs into html hidden inputs
695
-     * and returns the string of html
696
-     *
697
-     * @param array $args key-value pairs
698
-     * @return string
699
-     */
700
-    protected function _args_as_inputs($args)
701
-    {
702
-        $html = '';
703
-        if ($args !== null && is_array($args)) {
704
-            foreach ($args as $name => $value) {
705
-                $html .= $this->generateInput($name, $value);
706
-            }
707
-        }
708
-        return $html;
709
-    }
710
-
711
-    /**
712
-     * Converts either a single name and value or array of values into html hidden inputs
713
-     * and returns the string of html
714
-     *
715
-     * @param string $name
716
-     * @param string|array $value
717
-     * @return string
718
-     */
719
-    private function generateInput($name, $value)
720
-    {
721
-        if (is_array($value)) {
722
-            $html = '';
723
-            $name = "{$name}[]";
724
-            foreach ($value as $array_value) {
725
-                $html .= $this->generateInput($name, $array_value);
726
-            }
727
-            return $html;
728
-        }
729
-        return EEH_HTML::nl()
730
-            . '<input type="hidden" name="' . $name . '"'
731
-            . ' value="' . esc_attr($value) . '"/>';
732
-    }
733
-
734
-
735
-    /**
736
-     * Returns the currency of the payment.
737
-     * (At the time of writing, this will always be the currency in the configuration;
738
-     * however in the future it is anticipated that this will be stored on the payment
739
-     * object itself)
740
-     *
741
-     * @return string for the currency code
742
-     */
743
-    public function currency_code()
744
-    {
745
-        return EE_Config::instance()->currency->code;
746
-    }
747
-
748
-
749
-    /**
750
-     * apply wp_strip_all_tags to all elements within an array
751
-     *
752
-     * @access private
753
-     * @param mixed $item
754
-     */
755
-    private function _strip_all_tags_within_array(&$item)
756
-    {
757
-        if (is_object($item)) {
758
-            $item = (array) $item;
759
-        }
760
-        if (is_array($item)) {
761
-            array_walk_recursive($item, array($this, '_strip_all_tags_within_array'));
762
-        } else {
763
-            $item = wp_strip_all_tags($item);
764
-        }
765
-    }
766
-
767
-
768
-    /**
769
-     * Returns TRUE is this payment was set to approved during this request (or
770
-     * is approved and was created during this request). False otherwise.
771
-     *
772
-     * @return boolean
773
-     * @throws \EE_Error
774
-     */
775
-    public function just_approved()
776
-    {
777
-        $original_status = EEH_Array::is_set(
778
-            $this->_props_n_values_provided_in_constructor,
779
-            'STS_ID',
780
-            $this->get_model()->field_settings_for('STS_ID')->get_default_value()
781
-        );
782
-        $current_status = $this->status();
783
-        if (
784
-            $original_status !== EEM_Payment::status_id_approved
785
-            && $current_status === EEM_Payment::status_id_approved
786
-        ) {
787
-            return true;
788
-        } else {
789
-            return false;
790
-        }
791
-    }
792
-
793
-
794
-    /**
795
-     * Overrides parents' get_pretty() function just for legacy reasons
796
-     * (to allow ticket https://events.codebasehq.com/projects/event-espresso/tickets/7420)
797
-     *
798
-     * @param string $field_name
799
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
800
-     *                                (in cases where the same property may be used for different outputs
801
-     *                                - i.e. datetime, money etc.)
802
-     * @return mixed
803
-     * @throws \EE_Error
804
-     */
805
-    public function get_pretty($field_name, $extra_cache_ref = null)
806
-    {
807
-        if ($field_name === 'PAY_gateway') {
808
-            return $this->payment_method() ? $this->payment_method()->name() : esc_html__('Unknown', 'event_espresso');
809
-        }
810
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
811
-    }
812
-
813
-
814
-    /**
815
-     * Gets details regarding which registrations this payment was applied to
816
-     *
817
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
818
-     * @return EE_Registration_Payment[]
819
-     * @throws \EE_Error
820
-     */
821
-    public function registration_payments($query_params = array())
822
-    {
823
-        return $this->get_many_related('Registration_Payment', $query_params);
824
-    }
825
-
826
-
827
-    /**
828
-     * Gets the first event for this payment (it's possible that it could be for multiple)
829
-     *
830
-     * @return EE_Event|null
831
-     */
832
-    public function get_first_event()
833
-    {
834
-        $transaction = $this->transaction();
835
-        if ($transaction instanceof EE_Transaction) {
836
-            $primary_registrant = $transaction->primary_registration();
837
-            if ($primary_registrant instanceof EE_Registration) {
838
-                return $primary_registrant->event_obj();
839
-            }
840
-        }
841
-        return null;
842
-    }
843
-
844
-
845
-    /**
846
-     * Gets the name of the first event for which is being paid
847
-     *
848
-     * @return string
849
-     */
850
-    public function get_first_event_name()
851
-    {
852
-        $event = $this->get_first_event();
853
-        return $event instanceof EE_Event ? $event->name() : esc_html__('Event', 'event_espresso');
854
-    }
855
-
856
-
857
-    /**
858
-     * Returns the payment's transaction's primary registration
859
-     *
860
-     * @return EE_Registration|null
861
-     */
862
-    public function get_primary_registration()
863
-    {
864
-        if ($this->transaction() instanceof EE_Transaction) {
865
-            return $this->transaction()->primary_registration();
866
-        }
867
-        return null;
868
-    }
869
-
870
-
871
-    /**
872
-     * Gets the payment's transaction's primary registration's attendee, or null
873
-     *
874
-     * @return EE_Attendee|null
875
-     */
876
-    public function get_primary_attendee()
877
-    {
878
-        $primary_reg = $this->get_primary_registration();
879
-        if ($primary_reg instanceof EE_Registration) {
880
-            return $primary_reg->attendee();
881
-        }
882
-        return null;
883
-    }
14
+	/**
15
+	 * @param array  $props_n_values          incoming values
16
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
17
+	 *                                        used.)
18
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
19
+	 *                                        date_format and the second value is the time format
20
+	 * @return EE_Payment
21
+	 * @throws \EE_Error
22
+	 */
23
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
24
+	{
25
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
26
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
27
+	}
28
+
29
+
30
+	/**
31
+	 * @param array  $props_n_values  incoming values from the database
32
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
33
+	 *                                the website will be used.
34
+	 * @return EE_Payment
35
+	 * @throws \EE_Error
36
+	 */
37
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
38
+	{
39
+		return new self($props_n_values, true, $timezone);
40
+	}
41
+
42
+
43
+	/**
44
+	 * Set Transaction ID
45
+	 *
46
+	 * @access public
47
+	 * @param int $TXN_ID
48
+	 * @throws \EE_Error
49
+	 */
50
+	public function set_transaction_id($TXN_ID = 0)
51
+	{
52
+		$this->set('TXN_ID', $TXN_ID);
53
+	}
54
+
55
+
56
+	/**
57
+	 * Gets the transaction related to this payment
58
+	 *
59
+	 * @return EE_Transaction
60
+	 * @throws \EE_Error
61
+	 */
62
+	public function transaction()
63
+	{
64
+		return $this->get_first_related('Transaction');
65
+	}
66
+
67
+
68
+	/**
69
+	 * Set Status
70
+	 *
71
+	 * @access public
72
+	 * @param string $STS_ID
73
+	 * @throws \EE_Error
74
+	 */
75
+	public function set_status($STS_ID = '')
76
+	{
77
+		$this->set('STS_ID', $STS_ID);
78
+	}
79
+
80
+
81
+	/**
82
+	 * Set Payment Timestamp
83
+	 *
84
+	 * @access public
85
+	 * @param int $timestamp
86
+	 * @throws \EE_Error
87
+	 */
88
+	public function set_timestamp($timestamp = 0)
89
+	{
90
+		$this->set('PAY_timestamp', $timestamp);
91
+	}
92
+
93
+
94
+	/**
95
+	 * Set Payment Method
96
+	 *
97
+	 * @access public
98
+	 * @param string $PAY_source
99
+	 * @throws \EE_Error
100
+	 */
101
+	public function set_source($PAY_source = '')
102
+	{
103
+		$this->set('PAY_source', $PAY_source);
104
+	}
105
+
106
+
107
+	/**
108
+	 * Set Payment Amount
109
+	 *
110
+	 * @access public
111
+	 * @param float $amount
112
+	 * @throws \EE_Error
113
+	 */
114
+	public function set_amount($amount = 0.00)
115
+	{
116
+		$this->set('PAY_amount', (float) $amount);
117
+	}
118
+
119
+
120
+	/**
121
+	 * Set Payment Gateway Response
122
+	 *
123
+	 * @access public
124
+	 * @param string $gateway_response
125
+	 * @throws \EE_Error
126
+	 */
127
+	public function set_gateway_response($gateway_response = '')
128
+	{
129
+		$this->set('PAY_gateway_response', $gateway_response);
130
+	}
131
+
132
+
133
+	/**
134
+	 * Returns the name of the payment method used on this payment (previously known merely as 'gateway')
135
+	 * but since 4.6.0, payment methods are models and the payment keeps a foreign key to the payment method
136
+	 * used on it
137
+	 *
138
+	 * @deprecated
139
+	 * @return string
140
+	 * @throws \EE_Error
141
+	 */
142
+	public function gateway()
143
+	{
144
+		EE_Error::doing_it_wrong(
145
+			'EE_Payment::gateway',
146
+			esc_html__(
147
+				'The method EE_Payment::gateway() has been deprecated. Consider instead using EE_Payment::payment_method()->name()',
148
+				'event_espresso'
149
+			),
150
+			'4.6.0'
151
+		);
152
+		return $this->payment_method() ? $this->payment_method()->name() : esc_html__('Unknown', 'event_espresso');
153
+	}
154
+
155
+
156
+	/**
157
+	 * Set Gateway Transaction ID
158
+	 *
159
+	 * @access public
160
+	 * @param string $txn_id_chq_nmbr
161
+	 * @throws \EE_Error
162
+	 */
163
+	public function set_txn_id_chq_nmbr($txn_id_chq_nmbr = '')
164
+	{
165
+		$this->set('PAY_txn_id_chq_nmbr', $txn_id_chq_nmbr);
166
+	}
167
+
168
+
169
+	/**
170
+	 * Set Purchase Order Number
171
+	 *
172
+	 * @access public
173
+	 * @param string $po_number
174
+	 * @throws \EE_Error
175
+	 */
176
+	public function set_po_number($po_number = '')
177
+	{
178
+		$this->set('PAY_po_number', $po_number);
179
+	}
180
+
181
+
182
+	/**
183
+	 * Set Extra Accounting Field
184
+	 *
185
+	 * @access public
186
+	 * @param string $extra_accntng
187
+	 * @throws \EE_Error
188
+	 */
189
+	public function set_extra_accntng($extra_accntng = '')
190
+	{
191
+		$this->set('PAY_extra_accntng', $extra_accntng);
192
+	}
193
+
194
+
195
+	/**
196
+	 * Set Payment made via admin flag
197
+	 *
198
+	 * @access public
199
+	 * @param bool $via_admin
200
+	 * @throws \EE_Error
201
+	 */
202
+	public function set_payment_made_via_admin($via_admin = false)
203
+	{
204
+		if ($via_admin) {
205
+			$this->set('PAY_source', EEM_Payment_Method::scope_admin);
206
+		} else {
207
+			$this->set('PAY_source', EEM_Payment_Method::scope_cart);
208
+		}
209
+	}
210
+
211
+
212
+	/**
213
+	 * Set Payment Details
214
+	 *
215
+	 * @access public
216
+	 * @param string|array $details
217
+	 * @throws \EE_Error
218
+	 */
219
+	public function set_details($details = '')
220
+	{
221
+		if (is_array($details)) {
222
+			array_walk_recursive($details, array($this, '_strip_all_tags_within_array'));
223
+		} else {
224
+			$details = wp_strip_all_tags($details);
225
+		}
226
+		$this->set('PAY_details', $details);
227
+	}
228
+
229
+
230
+	/**
231
+	 * Sets redirect_url
232
+	 *
233
+	 * @param string $redirect_url
234
+	 * @throws \EE_Error
235
+	 */
236
+	public function set_redirect_url($redirect_url)
237
+	{
238
+		$this->set('PAY_redirect_url', $redirect_url);
239
+	}
240
+
241
+
242
+	/**
243
+	 * Sets redirect_args
244
+	 *
245
+	 * @param array $redirect_args
246
+	 * @throws \EE_Error
247
+	 */
248
+	public function set_redirect_args($redirect_args)
249
+	{
250
+		$this->set('PAY_redirect_args', $redirect_args);
251
+	}
252
+
253
+
254
+	/**
255
+	 * get Payment Transaction ID
256
+	 *
257
+	 * @access public
258
+	 * @throws \EE_Error
259
+	 */
260
+	public function TXN_ID()
261
+	{
262
+		return $this->get('TXN_ID');
263
+	}
264
+
265
+
266
+	/**
267
+	 * get Payment Status
268
+	 *
269
+	 * @access public
270
+	 * @throws \EE_Error
271
+	 */
272
+	public function status()
273
+	{
274
+		return $this->get('STS_ID');
275
+	}
276
+
277
+
278
+	/**
279
+	 * get Payment Status
280
+	 *
281
+	 * @access public
282
+	 * @throws \EE_Error
283
+	 */
284
+	public function STS_ID()
285
+	{
286
+		return $this->get('STS_ID');
287
+	}
288
+
289
+
290
+	/**
291
+	 * get Payment Timestamp
292
+	 *
293
+	 * @access public
294
+	 * @param string $dt_frmt
295
+	 * @param string $tm_frmt
296
+	 * @return string
297
+	 * @throws \EE_Error
298
+	 */
299
+	public function timestamp($dt_frmt = '', $tm_frmt = '')
300
+	{
301
+		return $this->get_i18n_datetime('PAY_timestamp', trim($dt_frmt . ' ' . $tm_frmt));
302
+	}
303
+
304
+
305
+	/**
306
+	 * get Payment Source
307
+	 *
308
+	 * @access public
309
+	 * @throws \EE_Error
310
+	 */
311
+	public function source()
312
+	{
313
+		return $this->get('PAY_source');
314
+	}
315
+
316
+
317
+	/**
318
+	 * get Payment Amount
319
+	 *
320
+	 * @access public
321
+	 * @return float
322
+	 * @throws \EE_Error
323
+	 */
324
+	public function amount()
325
+	{
326
+		return (float) $this->get('PAY_amount');
327
+	}
328
+
329
+
330
+	/**
331
+	 * @return mixed
332
+	 * @throws \EE_Error
333
+	 */
334
+	public function amount_no_code()
335
+	{
336
+		return $this->get_pretty('PAY_amount', 'no_currency_code');
337
+	}
338
+
339
+
340
+	/**
341
+	 * get Payment Gateway Response
342
+	 *
343
+	 * @access public
344
+	 * @throws \EE_Error
345
+	 */
346
+	public function gateway_response()
347
+	{
348
+		return $this->get('PAY_gateway_response');
349
+	}
350
+
351
+
352
+	/**
353
+	 * get Payment Gateway Transaction ID
354
+	 *
355
+	 * @access public
356
+	 * @throws \EE_Error
357
+	 */
358
+	public function txn_id_chq_nmbr()
359
+	{
360
+		return $this->get('PAY_txn_id_chq_nmbr');
361
+	}
362
+
363
+
364
+	/**
365
+	 * get Purchase Order Number
366
+	 *
367
+	 * @access public
368
+	 * @throws \EE_Error
369
+	 */
370
+	public function po_number()
371
+	{
372
+		return $this->get('PAY_po_number');
373
+	}
374
+
375
+
376
+	/**
377
+	 * get Extra Accounting Field
378
+	 *
379
+	 * @access public
380
+	 * @throws \EE_Error
381
+	 */
382
+	public function extra_accntng()
383
+	{
384
+		return $this->get('PAY_extra_accntng');
385
+	}
386
+
387
+
388
+	/**
389
+	 * get Payment made via admin source
390
+	 *
391
+	 * @access public
392
+	 * @throws \EE_Error
393
+	 */
394
+	public function payment_made_via_admin()
395
+	{
396
+		return ($this->get('PAY_source') === EEM_Payment_Method::scope_admin);
397
+	}
398
+
399
+
400
+	/**
401
+	 * get Payment Details
402
+	 *
403
+	 * @access public
404
+	 * @throws \EE_Error
405
+	 */
406
+	public function details()
407
+	{
408
+		return $this->get('PAY_details');
409
+	}
410
+
411
+
412
+	/**
413
+	 * Gets redirect_url
414
+	 *
415
+	 * @return string
416
+	 * @throws \EE_Error
417
+	 */
418
+	public function redirect_url()
419
+	{
420
+		return $this->get('PAY_redirect_url');
421
+	}
422
+
423
+
424
+	/**
425
+	 * Gets redirect_args
426
+	 *
427
+	 * @return array
428
+	 * @throws \EE_Error
429
+	 */
430
+	public function redirect_args()
431
+	{
432
+		return $this->get('PAY_redirect_args');
433
+	}
434
+
435
+
436
+	/**
437
+	 * echoes $this->pretty_status()
438
+	 *
439
+	 * @param bool $show_icons
440
+	 * @return void
441
+	 * @throws \EE_Error
442
+	 */
443
+	public function e_pretty_status($show_icons = false)
444
+	{
445
+		echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
446
+	}
447
+
448
+
449
+	/**
450
+	 * returns a pretty version of the status, good for displaying to users
451
+	 *
452
+	 * @param bool $show_icons
453
+	 * @return string
454
+	 * @throws \EE_Error
455
+	 */
456
+	public function pretty_status($show_icons = false)
457
+	{
458
+		$status = EEM_Status::instance()->localized_status(
459
+			array($this->STS_ID() => esc_html__('unknown', 'event_espresso')),
460
+			false,
461
+			'sentence'
462
+		);
463
+		$icon = '';
464
+		switch ($this->STS_ID()) {
465
+			case EEM_Payment::status_id_approved:
466
+				$icon = $show_icons
467
+					? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>'
468
+					: '';
469
+				break;
470
+			case EEM_Payment::status_id_pending:
471
+				$icon = $show_icons
472
+					? '<span class="dashicons dashicons-clock ee-icon-size-16 orange-text"></span>'
473
+					: '';
474
+				break;
475
+			case EEM_Payment::status_id_cancelled:
476
+				$icon = $show_icons
477
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
478
+					: '';
479
+				break;
480
+			case EEM_Payment::status_id_declined:
481
+				$icon = $show_icons
482
+					? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
483
+					: '';
484
+				break;
485
+		}
486
+		return $icon . $status[ $this->STS_ID() ];
487
+	}
488
+
489
+
490
+	/**
491
+	 * For determining the status of the payment
492
+	 *
493
+	 * @return boolean whether the payment is approved or not
494
+	 * @throws \EE_Error
495
+	 */
496
+	public function is_approved()
497
+	{
498
+		return $this->status_is(EEM_Payment::status_id_approved);
499
+	}
500
+
501
+
502
+	/**
503
+	 * Generally determines if the status of this payment equals
504
+	 * the $STS_ID string
505
+	 *
506
+	 * @param string $STS_ID an ID from the esp_status table/
507
+	 *                       one of the status_id_* on the EEM_Payment model
508
+	 * @return boolean whether the status of this payment equals the status id
509
+	 * @throws \EE_Error
510
+	 */
511
+	protected function status_is($STS_ID)
512
+	{
513
+		return $STS_ID === $this->STS_ID() ? true : false;
514
+	}
515
+
516
+
517
+	/**
518
+	 * For determining the status of the payment
519
+	 *
520
+	 * @return boolean whether the payment is pending or not
521
+	 * @throws \EE_Error
522
+	 */
523
+	public function is_pending()
524
+	{
525
+		return $this->status_is(EEM_Payment::status_id_pending);
526
+	}
527
+
528
+
529
+	/**
530
+	 * For determining the status of the payment
531
+	 *
532
+	 * @return boolean
533
+	 * @throws \EE_Error
534
+	 */
535
+	public function is_cancelled()
536
+	{
537
+		return $this->status_is(EEM_Payment::status_id_cancelled);
538
+	}
539
+
540
+
541
+	/**
542
+	 * For determining the status of the payment
543
+	 *
544
+	 * @return boolean
545
+	 * @throws \EE_Error
546
+	 */
547
+	public function is_declined()
548
+	{
549
+		return $this->status_is(EEM_Payment::status_id_declined);
550
+	}
551
+
552
+
553
+	/**
554
+	 * For determining the status of the payment
555
+	 *
556
+	 * @return boolean
557
+	 * @throws \EE_Error
558
+	 */
559
+	public function is_failed()
560
+	{
561
+		return $this->status_is(EEM_Payment::status_id_failed);
562
+	}
563
+
564
+
565
+	/**
566
+	 * For determining if the payment is actually a refund ( ie: has a negative value )
567
+	 *
568
+	 * @return boolean
569
+	 * @throws \EE_Error
570
+	 */
571
+	public function is_a_refund()
572
+	{
573
+		return $this->amount() < 0 ? true : false;
574
+	}
575
+
576
+
577
+	/**
578
+	 * Get the status object of this object
579
+	 *
580
+	 * @return EE_Status
581
+	 * @throws \EE_Error
582
+	 */
583
+	public function status_obj()
584
+	{
585
+		return $this->get_first_related('Status');
586
+	}
587
+
588
+
589
+	/**
590
+	 * Gets all the extra meta info on this payment
591
+	 *
592
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
593
+	 * @return EE_Extra_Meta
594
+	 * @throws \EE_Error
595
+	 */
596
+	public function extra_meta($query_params = array())
597
+	{
598
+		return $this->get_many_related('Extra_Meta', $query_params);
599
+	}
600
+
601
+
602
+	/**
603
+	 * Gets the last-used payment method on this transaction
604
+	 * (we COULD just use the last-made payment, but some payment methods, namely
605
+	 * offline ones, dont' create payments)
606
+	 *
607
+	 * @return EE_Payment_Method
608
+	 * @throws \EE_Error
609
+	 */
610
+	public function payment_method()
611
+	{
612
+		return $this->get_first_related('Payment_Method');
613
+	}
614
+
615
+
616
+	/**
617
+	 * Gets the HTML for redirecting the user to an offsite gateway
618
+	 * You can pass it special content to put inside the form, or use
619
+	 * the default inner content (or possibly generate this all yourself using
620
+	 * redirect_url() and redirect_args() or redirect_args_as_inputs()).
621
+	 * Creates a POST request by default, but if no redirect args are specified, creates a GET request instead
622
+	 * (and any querystring variables in the redirect_url are converted into html inputs
623
+	 * so browsers submit them properly)
624
+	 *
625
+	 * @param string $inside_form_html
626
+	 * @return string html
627
+	 * @throws \EE_Error
628
+	 */
629
+	public function redirect_form($inside_form_html = null)
630
+	{
631
+		$redirect_url = $this->redirect_url();
632
+		if (! empty($redirect_url)) {
633
+			// what ? no inner form content?
634
+			if ($inside_form_html === null) {
635
+				$inside_form_html = EEH_HTML::p(
636
+					sprintf(
637
+						esc_html__(
638
+							'If you are not automatically redirected to the payment website within 10 seconds... %1$s %2$s Click Here %3$s',
639
+							'event_espresso'
640
+						),
641
+						EEH_HTML::br(2),
642
+						'<input type="submit" value="',
643
+						'">'
644
+					),
645
+					'',
646
+					'',
647
+					'text-align:center;'
648
+				);
649
+			}
650
+			$method = apply_filters(
651
+				'FHEE__EE_Payment__redirect_form__method',
652
+				$this->redirect_args() ? 'POST' : 'GET',
653
+				$this
654
+			);
655
+			// if it's a GET request, we need to remove all the GET params in the querystring
656
+			// and put them into the form instead
657
+			if ($method === 'GET') {
658
+				$querystring = parse_url($redirect_url, PHP_URL_QUERY);
659
+				$get_params = null;
660
+				parse_str($querystring, $get_params);
661
+				$inside_form_html .= $this->_args_as_inputs($get_params);
662
+				$redirect_url = str_replace('?' . $querystring, '', $redirect_url);
663
+			}
664
+			$form = EEH_HTML::nl(1)
665
+					. '<form method="'
666
+					. $method
667
+					. '" name="gateway_form" action="'
668
+					. $redirect_url
669
+					. '">';
670
+			$form .= EEH_HTML::nl(1) . $this->redirect_args_as_inputs();
671
+			$form .= $inside_form_html;
672
+			$form .= EEH_HTML::nl(-1) . '</form>' . EEH_HTML::nl(-1);
673
+			return $form;
674
+		} else {
675
+			return null;
676
+		}
677
+	}
678
+
679
+
680
+	/**
681
+	 * Changes all the name-value pairs of the redirect args into html inputs
682
+	 * and returns the html as a string
683
+	 *
684
+	 * @return string
685
+	 * @throws \EE_Error
686
+	 */
687
+	public function redirect_args_as_inputs()
688
+	{
689
+		return $this->_args_as_inputs($this->redirect_args());
690
+	}
691
+
692
+
693
+	/**
694
+	 * Converts a 2d array of key-value pairs into html hidden inputs
695
+	 * and returns the string of html
696
+	 *
697
+	 * @param array $args key-value pairs
698
+	 * @return string
699
+	 */
700
+	protected function _args_as_inputs($args)
701
+	{
702
+		$html = '';
703
+		if ($args !== null && is_array($args)) {
704
+			foreach ($args as $name => $value) {
705
+				$html .= $this->generateInput($name, $value);
706
+			}
707
+		}
708
+		return $html;
709
+	}
710
+
711
+	/**
712
+	 * Converts either a single name and value or array of values into html hidden inputs
713
+	 * and returns the string of html
714
+	 *
715
+	 * @param string $name
716
+	 * @param string|array $value
717
+	 * @return string
718
+	 */
719
+	private function generateInput($name, $value)
720
+	{
721
+		if (is_array($value)) {
722
+			$html = '';
723
+			$name = "{$name}[]";
724
+			foreach ($value as $array_value) {
725
+				$html .= $this->generateInput($name, $array_value);
726
+			}
727
+			return $html;
728
+		}
729
+		return EEH_HTML::nl()
730
+			. '<input type="hidden" name="' . $name . '"'
731
+			. ' value="' . esc_attr($value) . '"/>';
732
+	}
733
+
734
+
735
+	/**
736
+	 * Returns the currency of the payment.
737
+	 * (At the time of writing, this will always be the currency in the configuration;
738
+	 * however in the future it is anticipated that this will be stored on the payment
739
+	 * object itself)
740
+	 *
741
+	 * @return string for the currency code
742
+	 */
743
+	public function currency_code()
744
+	{
745
+		return EE_Config::instance()->currency->code;
746
+	}
747
+
748
+
749
+	/**
750
+	 * apply wp_strip_all_tags to all elements within an array
751
+	 *
752
+	 * @access private
753
+	 * @param mixed $item
754
+	 */
755
+	private function _strip_all_tags_within_array(&$item)
756
+	{
757
+		if (is_object($item)) {
758
+			$item = (array) $item;
759
+		}
760
+		if (is_array($item)) {
761
+			array_walk_recursive($item, array($this, '_strip_all_tags_within_array'));
762
+		} else {
763
+			$item = wp_strip_all_tags($item);
764
+		}
765
+	}
766
+
767
+
768
+	/**
769
+	 * Returns TRUE is this payment was set to approved during this request (or
770
+	 * is approved and was created during this request). False otherwise.
771
+	 *
772
+	 * @return boolean
773
+	 * @throws \EE_Error
774
+	 */
775
+	public function just_approved()
776
+	{
777
+		$original_status = EEH_Array::is_set(
778
+			$this->_props_n_values_provided_in_constructor,
779
+			'STS_ID',
780
+			$this->get_model()->field_settings_for('STS_ID')->get_default_value()
781
+		);
782
+		$current_status = $this->status();
783
+		if (
784
+			$original_status !== EEM_Payment::status_id_approved
785
+			&& $current_status === EEM_Payment::status_id_approved
786
+		) {
787
+			return true;
788
+		} else {
789
+			return false;
790
+		}
791
+	}
792
+
793
+
794
+	/**
795
+	 * Overrides parents' get_pretty() function just for legacy reasons
796
+	 * (to allow ticket https://events.codebasehq.com/projects/event-espresso/tickets/7420)
797
+	 *
798
+	 * @param string $field_name
799
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
800
+	 *                                (in cases where the same property may be used for different outputs
801
+	 *                                - i.e. datetime, money etc.)
802
+	 * @return mixed
803
+	 * @throws \EE_Error
804
+	 */
805
+	public function get_pretty($field_name, $extra_cache_ref = null)
806
+	{
807
+		if ($field_name === 'PAY_gateway') {
808
+			return $this->payment_method() ? $this->payment_method()->name() : esc_html__('Unknown', 'event_espresso');
809
+		}
810
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
811
+	}
812
+
813
+
814
+	/**
815
+	 * Gets details regarding which registrations this payment was applied to
816
+	 *
817
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
818
+	 * @return EE_Registration_Payment[]
819
+	 * @throws \EE_Error
820
+	 */
821
+	public function registration_payments($query_params = array())
822
+	{
823
+		return $this->get_many_related('Registration_Payment', $query_params);
824
+	}
825
+
826
+
827
+	/**
828
+	 * Gets the first event for this payment (it's possible that it could be for multiple)
829
+	 *
830
+	 * @return EE_Event|null
831
+	 */
832
+	public function get_first_event()
833
+	{
834
+		$transaction = $this->transaction();
835
+		if ($transaction instanceof EE_Transaction) {
836
+			$primary_registrant = $transaction->primary_registration();
837
+			if ($primary_registrant instanceof EE_Registration) {
838
+				return $primary_registrant->event_obj();
839
+			}
840
+		}
841
+		return null;
842
+	}
843
+
844
+
845
+	/**
846
+	 * Gets the name of the first event for which is being paid
847
+	 *
848
+	 * @return string
849
+	 */
850
+	public function get_first_event_name()
851
+	{
852
+		$event = $this->get_first_event();
853
+		return $event instanceof EE_Event ? $event->name() : esc_html__('Event', 'event_espresso');
854
+	}
855
+
856
+
857
+	/**
858
+	 * Returns the payment's transaction's primary registration
859
+	 *
860
+	 * @return EE_Registration|null
861
+	 */
862
+	public function get_primary_registration()
863
+	{
864
+		if ($this->transaction() instanceof EE_Transaction) {
865
+			return $this->transaction()->primary_registration();
866
+		}
867
+		return null;
868
+	}
869
+
870
+
871
+	/**
872
+	 * Gets the payment's transaction's primary registration's attendee, or null
873
+	 *
874
+	 * @return EE_Attendee|null
875
+	 */
876
+	public function get_primary_attendee()
877
+	{
878
+		$primary_reg = $this->get_primary_registration();
879
+		if ($primary_reg instanceof EE_Registration) {
880
+			return $primary_reg->attendee();
881
+		}
882
+		return null;
883
+	}
884 884
 }
Please login to merge, or discard this patch.