Completed
Branch new-addon-api (b7bde0)
by
unknown
18:29 queued 08:41
created

EE_System::initialize_last()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\domain\Domain;
4
use EventEspresso\core\domain\DomainFactory;
5
use EventEspresso\core\exceptions\ExceptionStackTraceDisplay;
6
use EventEspresso\core\exceptions\InvalidClassException;
7
use EventEspresso\core\exceptions\InvalidDataTypeException;
8
use EventEspresso\core\exceptions\InvalidFilePathException;
9
use EventEspresso\core\exceptions\InvalidInterfaceException;
10
use EventEspresso\core\interfaces\ResettableInterface;
11
use EventEspresso\core\services\addon\AddonManager;
12
use EventEspresso\core\services\loaders\LoaderFactory;
13
use EventEspresso\core\services\loaders\LoaderInterface;
14
use EventEspresso\core\services\request\RequestInterface;
15
use EventEspresso\core\services\routing\Router;
16
17
/**
18
 * EE_System
19
 * The backbone of the core application that the rest of the system builds off of once bootstrapping is complete
20
 *
21
 * @package        Event Espresso
22
 * @subpackage     core/
23
 * @author         Brent Christensen, Michael Nelson
24
 */
25
final class EE_System implements ResettableInterface
26
{
27
28
    /**
29
     * indicates this is a 'normal' request. Ie, not activation, nor upgrade, nor activation.
30
     * So examples of this would be a normal GET request on the frontend or backend, or a POST, etc
31
     */
32
    const req_type_normal = 0;
33
34
    /**
35
     * Indicates this is a brand new installation of EE so we should install
36
     * tables and default data etc
37
     */
38
    const req_type_new_activation = 1;
39
40
    /**
41
     * we've detected that EE has been reactivated (or EE was activated during maintenance mode,
42
     * and we just exited maintenance mode). We MUST check the database is setup properly
43
     * and that default data is setup too
44
     */
45
    const req_type_reactivation = 2;
46
47
    /**
48
     * indicates that EE has been upgraded since its previous request.
49
     * We may have data migration scripts to call and will want to trigger maintenance mode
50
     */
51
    const req_type_upgrade = 3;
52
53
    /**
54
     * TODO  will detect that EE has been DOWNGRADED. We probably don't want to run in this case...
55
     */
56
    const req_type_downgrade = 4;
57
58
    /**
59
     * @deprecated since version 4.6.0.dev.006
60
     * Now whenever a new_activation is detected the request type is still just
61
     * new_activation (same for reactivation, upgrade, downgrade etc), but if we're in maintenance mode
62
     * EE_System::initialize_db_if_no_migrations_required and EE_Addon::initialize_db_if_no_migrations_required
63
     * will instead enqueue that EE plugin's db initialization for when we're taken out of maintenance mode.
64
     * (Specifically, when the migration manager indicates migrations are finished
65
     * EE_Data_Migration_Manager::initialize_db_for_enqueued_ee_plugins() will be called)
66
     */
67
    const req_type_activation_but_not_installed = 5;
68
69
    /**
70
     * option prefix for recording the activation history (like core's "espresso_db_update") of addons
71
     */
72
    const addon_activation_history_option_prefix = 'ee_addon_activation_history_';
73
74
    /**
75
     * @var AddonManager $addon_manager
76
     */
77
    private $addon_manager;
78
79
    /**
80
     * @var EE_System $_instance
81
     */
82
    private static $_instance;
83
84
    /**
85
     * @var EE_Registry $registry
86
     */
87
    private $registry;
88
89
    /**
90
     * @var LoaderInterface $loader
91
     */
92
    private $loader;
93
94
    /**
95
     * @var EE_Capabilities $capabilities
96
     */
97
    private $capabilities;
98
99
    /**
100
     * @var EE_Maintenance_Mode $maintenance_mode
101
     */
102
    private $maintenance_mode;
103
104
    /**
105
     * @var RequestInterface $request
106
     */
107
    private $request;
108
109
    /**
110
     * Stores which type of request this is, options being one of the constants on EE_System starting with req_type_*.
111
     * It can be a brand-new activation, a reactivation, an upgrade, a downgrade, or a normal request.
112
     *
113
     * @var int $_req_type
114
     */
115
    private $_req_type;
116
117
    /**
118
     * Whether or not there was a non-micro version change in EE core version during this request
119
     *
120
     * @var boolean $_major_version_change
121
     */
122
    private $_major_version_change = false;
123
124
    /**
125
     * @var Router $router
126
     */
127
    private $router;
128
129
130
    /**
131
     * @singleton method used to instantiate class object
132
     * @param LoaderInterface|null     $loader
133
     * @param EE_Maintenance_Mode|null $maintenance_mode
134
     * @param EE_Registry|null         $registry
135
     * @param RequestInterface|null    $request
136
     * @param Router|null              $router
137
     * @return EE_System
138
     */
139
    public static function instance(
140
        LoaderInterface $loader = null,
141
        EE_Maintenance_Mode $maintenance_mode = null,
142
        EE_Registry $registry = null,
143
        RequestInterface $request = null,
144
        Router $router = null
145
    ): EE_System {
146
        // check if class object is instantiated
147
        if (! self::$_instance instanceof EE_System) {
148
            self::$_instance = new self($loader, $maintenance_mode, $registry, $request, $router);
0 ignored issues
show
Bug introduced by
It seems like $loader defined by parameter $loader on line 140 can be null; however, EE_System::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
Bug introduced by
It seems like $maintenance_mode defined by parameter $maintenance_mode on line 141 can be null; however, EE_System::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
Bug introduced by
It seems like $registry defined by parameter $registry on line 142 can be null; however, EE_System::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
Bug introduced by
It seems like $request defined by parameter $request on line 143 can be null; however, EE_System::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
Bug introduced by
It seems like $router defined by parameter $router on line 144 can be null; however, EE_System::__construct() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
149
        }
150
        return self::$_instance;
151
    }
152
153
154
    /**
155
     * resets the instance and returns it
156
     *
157
     * @return EE_System
158
     */
159
    public static function reset(): EE_System
160
    {
161
        self::$_instance->_req_type = null;
162
        // make sure none of the old hooks are left hanging around
163
        remove_all_actions('AHEE__EE_System__perform_activations_upgrades_and_migrations');
164
        // we need to reset the migration manager in order for it to detect DMSs properly
165
        EE_Data_Migration_Manager::reset();
166
        self::instance()->detect_activations_or_upgrades();
167
        self::instance()->perform_activations_upgrades_and_migrations();
168
        return self::instance();
169
    }
170
171
172
    /**
173
     * sets hooks for running rest of system
174
     * provides "AHEE__EE_System__construct__complete" hook for EE Addons to use as their starting point
175
     * starting EE Addons from any other point may lead to problems
176
     *
177
     * @param LoaderInterface     $loader
178
     * @param EE_Maintenance_Mode $maintenance_mode
179
     * @param EE_Registry         $registry
180
     * @param RequestInterface    $request
181
     * @param Router              $router
182
     */
183
    private function __construct(
184
        LoaderInterface $loader,
185
        EE_Maintenance_Mode $maintenance_mode,
186
        EE_Registry $registry,
187
        RequestInterface $request,
188
        Router $router
189
    ) {
190
        $this->registry         = $registry;
191
        $this->loader           = $loader;
192
        $this->request          = $request;
193
        $this->router           = $router;
194
        $this->maintenance_mode = $maintenance_mode;
195
        do_action('AHEE__EE_System__construct__begin', $this);
196
        add_action(
197
            'AHEE__EE_Bootstrap__load_espresso_addons',
198
            [$this, 'loadCapabilities'],
199
            5
200
        );
201
        add_action(
202
            'AHEE__EE_Bootstrap__load_espresso_addons',
203
            [$this, 'loadCommandBus'],
204
            7
205
        );
206
        add_action(
207
            'AHEE__EE_Bootstrap__load_espresso_addons',
208
            [$this, 'loadPluginApi'],
209
            9
210
        );
211
        // allow addons to load first so that they can register autoloaders, set hooks for running DMS's, etc
212
        add_action(
213
            'AHEE__EE_Bootstrap__load_espresso_addons',
214
            [$this, 'load_espresso_addons']
215
        );
216
        // when an ee addon is activated, we want to call the core hook(s) again
217
        // because the newly-activated addon didn't get a chance to run at all
218
        add_action('activate_plugin', [$this, 'load_espresso_addons'], 1);
219
        // detect whether install or upgrade
220
        add_action(
221
            'AHEE__EE_Bootstrap__detect_activations_or_upgrades',
222
            [$this, 'detect_activations_or_upgrades'],
223
            3
224
        );
225
        // load EE_Config, EE_Textdomain, etc
226
        add_action(
227
            'AHEE__EE_Bootstrap__load_core_configuration',
228
            [$this, 'load_core_configuration'],
229
            5
230
        );
231
        // load specifications for matching routes to current request
232
        add_action(
233
            'AHEE__EE_Bootstrap__load_core_configuration',
234
            [$this, 'loadRouteMatchSpecifications']
235
        );
236
        // load EE_Config, EE_Textdomain, etc
237
        add_action(
238
            'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets',
239
            [$this, 'register_shortcodes_modules_and_widgets'],
240
            7
241
        );
242
        // you wanna get going? I wanna get going... let's get going!
243
        add_action(
244
            'AHEE__EE_Bootstrap__brew_espresso',
245
            [$this, 'brew_espresso'],
246
            9
247
        );
248
        // other housekeeping
249
        // exclude EE critical pages from wp_list_pages
250
        add_filter(
251
            'wp_list_pages_excludes',
252
            [$this, 'remove_pages_from_wp_list_pages'],
253
            10
254
        );
255
        // ALL EE Addons should use the following hook point to attach their initial setup too
256
        // it's extremely important for EE Addons to register any class autoloaders so that they can be available when the EE_Config loads
257
        do_action('AHEE__EE_System__construct__complete', $this);
258
    }
259
260
261
    /**
262
     * load and setup EE_Capabilities
263
     *
264
     * @return void
265
     */
266
    public function loadCapabilities()
267
    {
268
        $this->capabilities = $this->loader->getShared('EE_Capabilities');
269
        add_action(
270
            'AHEE__EE_Capabilities__init_caps__before_initialization',
271
            function () {
272
                LoaderFactory::getLoader()->getShared('EE_Payment_Method_Manager');
273
            }
274
        );
275
    }
276
277
278
    /**
279
     * create and cache the CommandBus, and also add middleware
280
     * The CapChecker middleware requires the use of EE_Capabilities
281
     * which is why we need to load the CommandBus after Caps are set up
282
     *
283
     * @return void
284
     */
285
    public function loadCommandBus()
286
    {
287
        $this->loader->getShared(
288
            'CommandBusInterface',
289
            [
290
                null,
291
                apply_filters(
292
                    'FHEE__EE_Load_Espresso_Core__handle_request__CommandBus_middleware',
293
                    [
294
                        $this->loader->getShared('EventEspresso\core\services\commands\middleware\CapChecker'),
295
                        $this->loader->getShared('EventEspresso\core\services\commands\middleware\AddActionHook'),
296
                    ]
297
                ),
298
            ]
299
        );
300
    }
301
302
303
    /**
304
     * @return void
305
     * @throws Exception
306
     */
307
    public function loadPluginApi()
308
    {
309
        $this->addon_manager = $this->loader->getShared(AddonManager::class);
310
        $this->addon_manager->initialize();
311
        $this->loader->getShared('EE_Request_Handler');
312
    }
313
314
315
    /**
316
     * load_espresso_addons
317
     * allow addons to load first so that they can set hooks for running DMS's, etc
318
     * this is hooked into both:
319
     *    'AHEE__EE_Bootstrap__load_core_configuration'
320
     *        which runs during the WP 'plugins_loaded' action at priority 5
321
     *    and the WP 'activate_plugin' hook point
322
     *
323
     * @return void
324
     * @throws Exception
325
     */
326
    public function load_espresso_addons()
327
    {
328
        // looking for hooks? they've been moved into the AddonManager to maintain compatibility
329
        $this->addon_manager->loadAddons();
330
    }
331
332
333
    /**
334
     * detect_activations_or_upgrades
335
     * Checks for activation or upgrade of core first;
336
     * then also checks if any registered addons have been activated or upgraded
337
     * This is hooked into 'AHEE__EE_Bootstrap__detect_activations_or_upgrades'
338
     * which runs during the WP 'plugins_loaded' action at priority 3
339
     *
340
     * @access public
341
     * @return void
342
     */
343
    public function detect_activations_or_upgrades()
344
    {
345
        // first off: let's make sure to handle core
346
        $this->detect_if_activation_or_upgrade();
347
        foreach ($this->registry->addons as $addon) {
348
            if ($addon instanceof EE_Addon) {
349
                // detect teh request type for that addon
350
                $addon->detect_activation_or_upgrade();
351
            }
352
        }
353
    }
354
355
356
    /**
357
     * detect_if_activation_or_upgrade
358
     * Takes care of detecting whether this is a brand new install or code upgrade,
359
     * and either setting up the DB or setting up maintenance mode etc.
360
     *
361
     * @access public
362
     * @return void
363
     */
364
    public function detect_if_activation_or_upgrade()
365
    {
366
        do_action('AHEE__EE_System___detect_if_activation_or_upgrade__begin');
367
        // check if db has been updated, or if its a brand-new installation
368
        $espresso_db_update = $this->fix_espresso_db_upgrade_option();
369
        $request_type       = $this->detect_req_type($espresso_db_update);
370
        // EEH_Debug_Tools::printr( $request_type, '$request_type', __FILE__, __LINE__ );
371
        switch ($request_type) {
372
            case EE_System::req_type_new_activation:
373
                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__new_activation');
374
                $this->_handle_core_version_change($espresso_db_update);
375
                break;
376
            case EE_System::req_type_reactivation:
377
                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__reactivation');
378
                $this->_handle_core_version_change($espresso_db_update);
379
                break;
380
            case EE_System::req_type_upgrade:
381
                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__upgrade');
382
                // migrations may be required now that we've upgraded
383
                $this->maintenance_mode->set_maintenance_mode_if_db_old();
384
                $this->_handle_core_version_change($espresso_db_update);
385
                break;
386
            case EE_System::req_type_downgrade:
387
                do_action('AHEE__EE_System__detect_if_activation_or_upgrade__downgrade');
388
                // its possible migrations are no longer required
389
                $this->maintenance_mode->set_maintenance_mode_if_db_old();
390
                $this->_handle_core_version_change($espresso_db_update);
391
                break;
392
            case EE_System::req_type_normal:
393
            default:
394
                break;
395
        }
396
        do_action('AHEE__EE_System__detect_if_activation_or_upgrade__complete');
397
    }
398
399
400
    /**
401
     * Updates the list of installed versions and sets hooks for
402
     * initializing the database later during the request
403
     *
404
     * @param array $espresso_db_update
405
     */
406
    private function _handle_core_version_change(array $espresso_db_update)
407
    {
408
        $this->update_list_of_installed_versions($espresso_db_update);
409
        // get ready to verify the DB is ok (provided we aren't in maintenance mode, of course)
410
        add_action(
411
            'AHEE__EE_System__perform_activations_upgrades_and_migrations',
412
            [$this, 'initialize_db_if_no_migrations_required']
413
        );
414
    }
415
416
417
    /**
418
     * standardizes the wp option 'espresso_db_upgrade' which actually stores
419
     * information about what versions of EE have been installed and activated,
420
     * NOT necessarily the state of the database
421
     *
422
     * @param mixed $espresso_db_update           the value of the WordPress option.
423
     *                                            If not supplied, fetches it from the options table
424
     * @return array the correct value of 'espresso_db_upgrade', after saving it, if it needed correction
425
     */
426
    private function fix_espresso_db_upgrade_option($espresso_db_update = null): array
427
    {
428
        do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__begin', $espresso_db_update);
429
        if (! $espresso_db_update) {
430
            $espresso_db_update = get_option('espresso_db_update');
431
        }
432
        // check that option is an array
433
        if (! is_array($espresso_db_update)) {
434
            // if option is FALSE, then it never existed
435
            if ($espresso_db_update === false) {
436
                // make $espresso_db_update an array and save option with autoload OFF
437
                $espresso_db_update = [];
438
                add_option('espresso_db_update', $espresso_db_update, '', 'no');
439
            } else {
440
                // option is NOT FALSE but also is NOT an array, so make it an array and save it
441
                $espresso_db_update = [$espresso_db_update => []];
442
                update_option('espresso_db_update', $espresso_db_update);
443
            }
444
        } else {
445
            $corrected_db_update = [];
446
            // if IS an array, but is it an array where KEYS are version numbers, and values are arrays?
447
            foreach ($espresso_db_update as $should_be_version_string => $should_be_array) {
448
                if (is_int($should_be_version_string) && ! is_array($should_be_array)) {
449
                    // the key is an int, and the value IS NOT an array
450
                    // so it must be numerically-indexed, where values are versions installed...
451
                    // fix it!
452
                    $version_string                         = $should_be_array;
453
                    $corrected_db_update[ $version_string ] = ['unknown-date'];
454
                } else {
455
                    // ok it checks out
456
                    $corrected_db_update[ $should_be_version_string ] = $should_be_array;
457
                }
458
            }
459
            $espresso_db_update = $corrected_db_update;
460
            update_option('espresso_db_update', $espresso_db_update);
461
        }
462
        do_action('FHEE__EE_System__manage_fix_espresso_db_upgrade_option__complete', $espresso_db_update);
463
        return ! empty($espresso_db_update) ? $espresso_db_update : [];
464
    }
465
466
467
    /**
468
     * Does the traditional work of setting up the plugin's database and adding default data.
469
     * If migration script/process did not exist, this is what would happen on every activation/reactivation/upgrade.
470
     * NOTE: if we're in maintenance mode (which would be the case if we detect there are data
471
     * migration scripts that need to be run and a version change happens), enqueues core for database initialization,
472
     * so that it will be done when migrations are finished
473
     *
474
     * @param boolean $initialize_addons_too if true, we double-check addons' database tables etc too;
475
     * @param boolean $verify_schema         if true will re-check the database tables have the correct schema.
476
     *                                       This is a resource-intensive job
477
     *                                       so we prefer to only do it when necessary
478
     * @return void
479
     * @throws EE_Error
480
     */
481
    public function initialize_db_if_no_migrations_required($initialize_addons_too = false, $verify_schema = true)
482
    {
483
        $request_type = $this->detect_req_type();
484
        // only initialize system if we're not in maintenance mode.
485
        if ($this->maintenance_mode->level() !== EE_Maintenance_Mode::level_2_complete_maintenance) {
486
            /** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
487
            $rewrite_rules = $this->loader->getShared(
488
                'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
489
            );
490
            $rewrite_rules->flush();
491
            if ($verify_schema) {
492
                EEH_Activation::initialize_db_and_folders();
493
            }
494
            EEH_Activation::initialize_db_content();
495
            EEH_Activation::system_initialization();
496
            if ($initialize_addons_too) {
497
                $this->initialize_addons();
498
            }
499
        } else {
500
            EE_Data_Migration_Manager::instance()->enqueue_db_initialization_for('Core');
501
        }
502
        if ($request_type === EE_System::req_type_new_activation
503
            || $request_type === EE_System::req_type_reactivation
504
            || (
505
                $request_type === EE_System::req_type_upgrade
506
                && $this->is_major_version_change()
507
            )
508
        ) {
509
            add_action('AHEE__EE_System__initialize_last', [$this, 'redirect_to_about_ee'], 9);
510
        }
511
    }
512
513
514
    /**
515
     * Initializes the db for all registered addons
516
     *
517
     * @throws EE_Error
518
     */
519
    public function initialize_addons()
520
    {
521
        // foreach registered addon, make sure its db is up-to-date too
522
        foreach ($this->registry->addons as $addon) {
523
            if ($addon instanceof EE_Addon) {
524
                $addon->initialize_db_if_no_migrations_required();
525
            }
526
        }
527
    }
528
529
530
    /**
531
     * Adds the current code version to the saved wp option which stores a list of all ee versions ever installed.
532
     *
533
     * @param array  $version_history
534
     * @param string $current_version_to_add version to be added to the version history
535
     * @return    boolean success as to whether or not this option was changed
536
     */
537
    public function update_list_of_installed_versions($version_history = null, $current_version_to_add = null): bool
538
    {
539
        if (! $version_history) {
540
            $version_history = $this->fix_espresso_db_upgrade_option($version_history);
541
        }
542
        if ($current_version_to_add === null) {
543
            $current_version_to_add = espresso_version();
544
        }
545
        $version_history[ $current_version_to_add ][] = date('Y-m-d H:i:s', time());
546
        // re-save
547
        return update_option('espresso_db_update', $version_history);
548
    }
549
550
551
    /**
552
     * Detects if the current version indicated in the has existed in the list of
553
     * previously-installed versions of EE (espresso_db_update). Does NOT modify it (ie, no side-effect)
554
     *
555
     * @param array $espresso_db_update array from the wp option stored under the name 'espresso_db_update'.
556
     *                                  If not supplied, fetches it from the options table.
557
     *                                  Also, caches its result so later parts of the code can also know whether
558
     *                                  there's been an update or not. This way we can add the current version to
559
     *                                  espresso_db_update, but still know if this is a new install or not
560
     * @return int one of the constants on EE_System::req_type_
561
     */
562
    public function detect_req_type($espresso_db_update = null): int
563
    {
564
        if ($this->_req_type === null) {
565
            $espresso_db_update          = ! empty($espresso_db_update)
566
                ? $espresso_db_update
567
                : $this->fix_espresso_db_upgrade_option();
568
            $this->_req_type             = EE_System::detect_req_type_given_activation_history(
569
                $espresso_db_update,
570
                'ee_espresso_activation',
571
                espresso_version()
572
            );
573
            $this->_major_version_change = $this->_detect_major_version_change($espresso_db_update);
574
            $this->request->setIsActivation($this->_req_type !== EE_System::req_type_normal);
575
        }
576
        return $this->_req_type;
577
    }
578
579
580
    /**
581
     * Returns whether or not there was a non-micro version change (ie, change in either
582
     * the first or second number in the version. Eg 4.9.0.rc.001 to 4.10.0.rc.000,
583
     * but not 4.9.0.rc.0001 to 4.9.1.rc.0001
584
     *
585
     * @param $activation_history
586
     * @return bool
587
     */
588
    private function _detect_major_version_change($activation_history): bool
589
    {
590
        $previous_version       = EE_System::getMostRecentlyActiveVersion($activation_history);
591
        $previous_version_parts = explode('.', $previous_version);
592
        $current_version_parts  = explode('.', espresso_version());
593
        return isset(
594
            $previous_version_parts[0],
595
            $previous_version_parts[1],
596
            $current_version_parts[0],
597
            $current_version_parts[1]
598
        ) && (
599
            $previous_version_parts[0] !== $current_version_parts[0]
600
            || $previous_version_parts[1] !== $current_version_parts[1]
601
        );
602
    }
603
604
605
    /**
606
     * Returns true if either the major or minor version of EE changed during this request.
607
     * 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
608
     *
609
     * @return bool
610
     */
611
    public function is_major_version_change(): bool
612
    {
613
        return $this->_major_version_change;
614
    }
615
616
617
    /**
618
     * Determines the request type for any ee addon, given three piece of info: the current array of activation
619
     * histories (for core that' 'espresso_db_update' wp option); the name of the WordPress option which is temporarily
620
     * set upon activation of the plugin (for core it's 'ee_espresso_activation'); and the version that this plugin was
621
     * just activated to (for core that will always be espresso_version())
622
     *
623
     * @param array|null $activation_history             the option's value which stores the activation history for
624
     *                                                 this
625
     *                                                 ee plugin. for core that's 'espresso_db_update'
626
     * @param string $activation_indicator_option_name the name of the WordPress option that is temporarily set to
627
     *                                                 indicate that this plugin was just activated
628
     * @param string $current_version                  the version that was just upgraded to (for core that will be
629
     *                                                 espresso_version())
630
     * @return int one of the constants on EE_System::req_type_
631
     */
632
    public static function detect_req_type_given_activation_history(
633
        array $activation_history,
634
        string $activation_indicator_option_name,
635
        string $current_version
636
    ): int {
637
        $version_change = self::compareVersionWithPrevious($activation_history, $current_version);
638
        $is_activation  = get_option($activation_indicator_option_name, false);
639
        $req_type       = self::getRequestType($activation_history, $version_change, $is_activation);
640
        if ($is_activation) {
641
            // cleanup in aisle 6
642
            delete_option($activation_indicator_option_name);
643
        }
644
        return $req_type;
645
    }
646
647
648
    /**
649
     * @param array  $activation_history
650
     * @param int    $version_change
651
     * @param bool   $is_activation
652
     * @return int
653
     * @since $VID:$
654
     */
655
    private static function getRequestType(array $activation_history, int $version_change, bool $is_activation): int
656
    {
657
        // if no previous activation history exists, then this is a brand new install
658
        if (empty($activation_history)) {
659
            return EE_System::req_type_new_activation;
660
        }
661
        // current version is higher than previous version, so it's an upgrade
662
        if ($version_change === 1) {
663
            return EE_System::req_type_upgrade;
664
        }
665
        // current version is lower than previous version, so it's a downgrade
666
        if ($version_change === -1) {
667
            return EE_System::req_type_downgrade;
668
        }
669
        // version hasn't changed since last version so check if the activation indicator is set
670
        // to determine if it's a reactivation, or just a normal request
671
        return $is_activation
672
            ? EE_System::req_type_reactivation
673
            : EE_System::req_type_normal;
674
    }
675
676
677
    /**
678
     * Detects if the $version_to_upgrade_to is higher than the most recent version in
679
     * the $activation_history_for_addon
680
     *
681
     * @param array  $activation_history    array where keys are versions,
682
     *                                      values are arrays of times activated (sometimes 'unknown-date')
683
     * @param string $current_version
684
     * @return int results of version_compare( $version_to_upgrade_to, $most_recently_active_version ).
685
     *                                      -1 if $version_to_upgrade_to is LOWER (downgrade);
686
     *                                      0 if $version_to_upgrade_to MATCHES (reactivation or normal request);
687
     *                                      1 if $version_to_upgrade_to is HIGHER (upgrade) ;
688
     */
689
    private static function compareVersionWithPrevious(array $activation_history, string $current_version): int
690
    {
691
        // find the most recently-activated version
692
        $most_recently_active_version = EE_System::getMostRecentlyActiveVersion($activation_history);
693
        return version_compare($current_version, $most_recently_active_version);
694
    }
695
696
697
    /**
698
     * Gets the most recently active version listed in the activation history,
699
     * and if none are found (ie, it's a brand new install) returns '0.0.0.dev.000'.
700
     *
701
     * @param array $activation_history  (keys are versions, values are arrays of times activated,
702
     *                                   sometimes containing 'unknown-date'
703
     * @return string
704
     */
705
    private static function getMostRecentlyActiveVersion(array $activation_history): string
706
    {
707
        $most_recent_activation_date  = '1970-01-01 00:00:00';
708
        $most_recently_active_version = '0.0.0.dev.000';
709
        if (is_array($activation_history)) {
710
            foreach ($activation_history as $version => $activation_dates) {
711
                // check there is a record of when this version was activated.
712
                // Otherwise, mark it as unknown
713
                if (! $activation_dates) {
714
                    $activation_dates = ['unknown-date'];
715
                }
716
                $activation_dates = is_string($activation_dates) ? [$activation_dates] : $activation_dates;
717
                foreach ($activation_dates as $activation_date) {
718
                    if ($activation_date !== 'unknown-date' && $activation_date > $most_recent_activation_date) {
719
                        $most_recently_active_version = $version;
720
                        $most_recent_activation_date  = $activation_date;
721
                    }
722
                }
723
            }
724
        }
725
        return $most_recently_active_version;
726
    }
727
728
729
    /**
730
     * This redirects to the about EE page after activation
731
     *
732
     * @return void
733
     */
734
    public function redirect_to_about_ee()
735
    {
736
        $notices = EE_Error::get_notices(false);
737
        // if current user is an admin and it's not an ajax or rest request
738
        if (! isset($notices['errors'])
739
            && $this->request->isAdmin()
740
            && apply_filters(
741
                'FHEE__EE_System__redirect_to_about_ee__do_redirect',
742
                $this->capabilities->current_user_can('manage_options', 'espresso_about_default')
743
            )
744
        ) {
745
            $query_params = ['page' => 'espresso_about'];
746
            if (EE_System::instance()->detect_req_type() === EE_System::req_type_new_activation) {
747
                $query_params['new_activation'] = true;
748
            }
749
            if (EE_System::instance()->detect_req_type() === EE_System::req_type_reactivation) {
750
                $query_params['reactivation'] = true;
751
            }
752
            $url = add_query_arg($query_params, admin_url('admin.php'));
753
            EEH_URL::safeRedirectAndExit($url);
754
        }
755
    }
756
757
758
    /**
759
     * load_core_configuration
760
     * this is hooked into 'AHEE__EE_Bootstrap__load_core_configuration'
761
     * which runs during the WP 'plugins_loaded' action at priority 5
762
     *
763
     * @return void
764
     * @throws ReflectionException
765
     * @throws Exception
766
     */
767
    public function load_core_configuration()
768
    {
769
        do_action('AHEE__EE_System__load_core_configuration__begin', $this);
770
        $this->loader->getShared('EE_Load_Textdomain');
771
        // load textdomain
772
        EE_Load_Textdomain::load_textdomain();
773
        // load caf stuff a chance to play during the activation process too.
774
        $this->_maybe_brew_regular();
775
        // load and setup EE_Config and EE_Network_Config
776
        $config = $this->loader->getShared('EE_Config');
777
        $this->loader->getShared('EE_Network_Config');
778
        // setup autoloaders
779
        // enable logging?
780
        if ($config->admin->use_remote_logging) {
781
            $this->loader->getShared('EE_Log');
782
        }
783
        // check for activation errors
784
        $activation_errors = get_option('ee_plugin_activation_errors', false);
785
        if ($activation_errors) {
786
            EE_Error::add_error($activation_errors, __FILE__, __FUNCTION__, __LINE__);
787
            update_option('ee_plugin_activation_errors', false);
788
        }
789
        // get model names
790
        $this->_parse_model_names();
791
        // configure custom post type definitions
792
        $this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions');
793
        $this->loader->getShared('EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions');
794
        do_action('AHEE__EE_System__load_core_configuration__complete', $this);
795
    }
796
797
798
    /**
799
     * cycles through all of the models/*.model.php files, and assembles an array of model names
800
     *
801
     * @return void
802
     * @throws ReflectionException
803
     */
804
    private function _parse_model_names()
805
    {
806
        // get all the files in the EE_MODELS folder that end in .model.php
807
        $models                 = glob(EE_MODELS . '*.model.php');
808
        $model_names            = [];
809
        $non_abstract_db_models = [];
810
        foreach ($models as $model) {
811
            // get model classname
812
            $classname       = EEH_File::get_classname_from_filepath_with_standard_filename($model);
813
            $short_name      = str_replace('EEM_', '', $classname);
814
            $reflectionClass = new ReflectionClass($classname);
815
            if ($reflectionClass->isSubclassOf('EEM_Base') && ! $reflectionClass->isAbstract()) {
816
                $non_abstract_db_models[ $short_name ] = $classname;
817
            }
818
            $model_names[ $short_name ] = $classname;
819
        }
820
        $this->registry->models                 = apply_filters('FHEE__EE_System__parse_model_names', $model_names);
821
        $this->registry->non_abstract_db_models = apply_filters(
822
            'FHEE__EE_System__parse_implemented_model_names',
823
            $non_abstract_db_models
824
        );
825
    }
826
827
828
    /**
829
     * The purpose of this method is to simply check for a file named "caffeinated/brewing_regular.php" for any hooks
830
     * that need to be setup before our EE_System launches.
831
     *
832
     * @return void
833
     * @throws DomainException
834
     * @throws InvalidArgumentException
835
     * @throws InvalidDataTypeException
836
     * @throws InvalidInterfaceException
837
     * @throws InvalidClassException
838
     * @throws InvalidFilePathException
839
     */
840
    private function _maybe_brew_regular()
841
    {
842
        /** @var Domain $domain */
843
        $domain = DomainFactory::getEventEspressoCoreDomain();
844
        if ($domain->isCaffeinated()) {
845
            require_once EE_CAFF_PATH . 'brewing_regular.php';
846
        }
847
    }
848
849
850
    /**
851
     * @throws Exception
852
     * @since 4.9.71.p
853
     */
854
    public function loadRouteMatchSpecifications()
855
    {
856
        try {
857
            $this->loader->getShared('EventEspresso\core\services\routing\RouteMatchSpecificationManager');
858
            $this->loader->getShared('EventEspresso\core\services\routing\RouteCollection');
859
            $this->router->loadPrimaryRoutes();
860
        } catch (Exception $exception) {
861
            new ExceptionStackTraceDisplay($exception);
862
        }
863
        do_action('AHEE__EE_System__loadRouteMatchSpecifications');
864
    }
865
866
867
    /**
868
     * register_shortcodes_modules_and_widgets
869
     * generate lists of shortcodes and modules, then verify paths and classes
870
     * This is hooked into 'AHEE__EE_Bootstrap__register_shortcodes_modules_and_widgets'
871
     * which runs during the WP 'plugins_loaded' action at priority 7
872
     *
873
     * @access public
874
     * @return void
875
     * @throws Exception
876
     */
877
    public function register_shortcodes_modules_and_widgets()
878
    {
879
        $this->router->registerShortcodesModulesAndWidgets();
880
        do_action('AHEE__EE_System__register_shortcodes_modules_and_widgets');
881
        // check for addons using old hook point
882
        if (has_action('AHEE__EE_System__register_shortcodes_modules_and_addons')) {
883
            $this->_incompatible_addon_error();
884
        }
885
    }
886
887
888
    /**
889
     * _incompatible_addon_error
890
     *
891
     * @access public
892
     * @return void
893
     */
894
    private function _incompatible_addon_error()
895
    {
896
        // get array of classes hooking into here
897
        $class_names = EEH_Class_Tools::get_class_names_for_all_callbacks_on_hook(
898
            'AHEE__EE_System__register_shortcodes_modules_and_addons'
899
        );
900
        if (! empty($class_names)) {
901
            $msg = __(
902
                'The following plugins, addons, or modules appear to be incompatible with this version of Event Espresso and were automatically deactivated to avoid fatal errors:',
903
                'event_espresso'
904
            );
905
            $msg .= '<ul>';
906
            foreach ($class_names as $class_name) {
907
                $msg .= '<li><b>Event Espresso - '
908
                        . str_replace(
909
                            ['EE_', 'EEM_', 'EED_', 'EES_', 'EEW_'],
910
                            '',
911
                            $class_name
912
                        ) . '</b></li>';
913
            }
914
            $msg .= '</ul>';
915
            $msg .= __(
916
                'Compatibility issues can be avoided and/or resolved by keeping addons and plugins updated to the latest version.',
917
                'event_espresso'
918
            );
919
            // save list of incompatible addons to wp-options for later use
920
            add_option('ee_incompatible_addons', $class_names, '', 'no');
921
            if (is_admin()) {
922
                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
923
            }
924
        }
925
    }
926
927
928
    /**
929
     * brew_espresso
930
     * begins the process of setting hooks for initializing EE in the correct order
931
     * This is happening on the 'AHEE__EE_Bootstrap__brew_espresso' hook point
932
     * which runs during the WP 'plugins_loaded' action at priority 9
933
     *
934
     * @return void
935
     * @throws Exception
936
     */
937
    public function brew_espresso()
938
    {
939
        do_action('AHEE__EE_System__brew_espresso__begin', $this);
940
        // load some final core systems
941
        add_action('init', [$this, 'set_hooks_for_core'], 1);
942
        add_action('init', [$this, 'perform_activations_upgrades_and_migrations'], 3);
943
        add_action('init', [$this, 'load_CPTs_and_session'], 5);
944
        add_action('init', [$this, 'load_controllers'], 7);
945
        add_action('init', [$this, 'core_loaded_and_ready'], 9);
946
        add_action('init', [$this, 'initialize'], 10);
947
        add_action('init', [$this, 'initialize_last'], 100);
948
        $this->router->brewEspresso();
949
        do_action('AHEE__EE_System__brew_espresso__complete', $this);
950
    }
951
952
953
    /**
954
     *    set_hooks_for_core
955
     *
956
     * @access public
957
     * @return    void
958
     * @throws EE_Error
959
     */
960
    public function set_hooks_for_core()
961
    {
962
        $this->_deactivate_incompatible_addons();
963
        do_action('AHEE__EE_System__set_hooks_for_core');
964
        $this->loader->getShared('EventEspresso\core\domain\values\session\SessionLifespan');
965
        // caps need to be initialized on every request so that capability maps are set.
966
        // @see https://events.codebasehq.com/projects/event-espresso/tickets/8674
967
        $this->registry->CAP->init_caps();
968
    }
969
970
971
    /**
972
     * Using the information gathered in EE_System::_incompatible_addon_error,
973
     * deactivates any addons considered incompatible with the current version of EE
974
     */
975
    private function _deactivate_incompatible_addons()
976
    {
977
        $incompatible_addons = get_option('ee_incompatible_addons', []);
978
        if (! empty($incompatible_addons)) {
979
            $active_plugins = get_option('active_plugins', []);
980
            foreach ($active_plugins as $active_plugin) {
981
                foreach ($incompatible_addons as $incompatible_addon) {
982
                    if (strpos($active_plugin, $incompatible_addon) !== false) {
983
                        unset($_GET['activate']);
984
                        espresso_deactivate_plugin($active_plugin);
985
                    }
986
                }
987
            }
988
        }
989
    }
990
991
992
    /**
993
     *    perform_activations_upgrades_and_migrations
994
     *
995
     * @access public
996
     * @return    void
997
     */
998
    public function perform_activations_upgrades_and_migrations()
999
    {
1000
        do_action('AHEE__EE_System__perform_activations_upgrades_and_migrations');
1001
    }
1002
1003
1004
    /**
1005
     * @return void
1006
     * @throws DomainException
1007
     */
1008
    public function load_CPTs_and_session()
1009
    {
1010
        do_action('AHEE__EE_System__load_CPTs_and_session__start');
1011
        /** @var EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies $register_custom_taxonomies */
1012
        $register_custom_taxonomies = $this->loader->getShared(
1013
            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'
1014
        );
1015
        $register_custom_taxonomies->registerCustomTaxonomies();
1016
        /** @var EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes $register_custom_post_types */
1017
        $register_custom_post_types = $this->loader->getShared(
1018
            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'
1019
        );
1020
        $register_custom_post_types->registerCustomPostTypes();
1021
        /** @var EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms $register_custom_taxonomy_terms */
1022
        $register_custom_taxonomy_terms = $this->loader->getShared(
1023
            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomyTerms'
1024
        );
1025
        $register_custom_taxonomy_terms->registerCustomTaxonomyTerms();
1026
        // load legacy Custom Post Types and Taxonomies
1027
        $this->loader->getShared('EE_Register_CPTs');
1028
        do_action('AHEE__EE_System__load_CPTs_and_session__complete');
1029
    }
1030
1031
1032
    /**
1033
     * load_controllers
1034
     * this is the best place to load any additional controllers that needs access to EE core.
1035
     * it is expected that all basic core EE systems, that are not dependant on the current request are loaded at this
1036
     * time
1037
     *
1038
     * @access public
1039
     * @return void
1040
     * @throws Exception
1041
     */
1042
    public function load_controllers()
1043
    {
1044
        do_action('AHEE__EE_System__load_controllers__start');
1045
        $this->router->loadControllers();
1046
        do_action('AHEE__EE_System__load_controllers__complete');
1047
    }
1048
1049
1050
    /**
1051
     * core_loaded_and_ready
1052
     * all of the basic EE core should be loaded at this point and available regardless of M-Mode
1053
     *
1054
     * @access public
1055
     * @return void
1056
     * @throws Exception
1057
     */
1058
    public function core_loaded_and_ready()
1059
    {
1060
        $this->router->coreLoadedAndReady();
1061
        // integrate WP_Query with the EE models
1062
        $this->loader->getShared('EE_CPT_Strategy');
1063
        do_action('AHEE__EE_System__core_loaded_and_ready');
1064
        // always load template tags, because it's faster than checking if it's a front-end request, and many page
1065
        // builders require these even on the front-end
1066
        require_once EE_PUBLIC . 'template_tags.php';
1067
        do_action('AHEE__EE_System__set_hooks_for_shortcodes_modules_and_addons');
1068
    }
1069
1070
1071
    /**
1072
     * initialize
1073
     * this is the best place to begin initializing client code
1074
     *
1075
     * @access public
1076
     * @return void
1077
     */
1078
    public function initialize()
1079
    {
1080
        do_action('AHEE__EE_System__initialize');
1081
    }
1082
1083
1084
    /**
1085
     * initialize_last
1086
     * this is run really late during the WP init hook point, and ensures that mostly everything else that needs to
1087
     * initialize has done so
1088
     *
1089
     * @access public
1090
     * @return void
1091
     * @throws Exception
1092
     */
1093
    public function initialize_last()
1094
    {
1095
        do_action('AHEE__EE_System__initialize_last');
1096
        /** @var EventEspresso\core\domain\services\custom_post_types\RewriteRules $rewrite_rules */
1097
        $rewrite_rules = $this->loader->getShared(
1098
            'EventEspresso\core\domain\services\custom_post_types\RewriteRules'
1099
        );
1100
        $rewrite_rules->flushRewriteRules();
1101
        $this->router->initializeLast();
1102
        add_action('admin_bar_init', [$this, 'addEspressoToolbar']);
1103
    }
1104
1105
1106
    /**
1107
     * @return void
1108
     */
1109
    public function addEspressoToolbar()
1110
    {
1111
        $this->loader->getShared(
1112
            'EventEspresso\core\domain\services\admin\AdminToolBar',
1113
            [$this->registry->CAP]
1114
        );
1115
    }
1116
1117
1118
    /**
1119
     * do_not_cache
1120
     * sets no cache headers and defines no cache constants for WP plugins
1121
     *
1122
     * @access public
1123
     * @return void
1124
     */
1125
    public static function do_not_cache()
1126
    {
1127
        // set no cache constants
1128
        if (! defined('DONOTCACHEPAGE')) {
1129
            define('DONOTCACHEPAGE', true);
1130
        }
1131
        if (! defined('DONOTCACHCEOBJECT')) {
1132
            define('DONOTCACHCEOBJECT', true);
1133
        }
1134
        if (! defined('DONOTCACHEDB')) {
1135
            define('DONOTCACHEDB', true);
1136
        }
1137
        // add no cache headers
1138
        add_action('send_headers', ['EE_System', 'nocache_headers'], 10);
1139
        // plus a little extra for nginx and Google Chrome
1140
        add_filter('nocache_headers', ['EE_System', 'extra_nocache_headers'], 10, 1);
1141
        // prevent browsers from prefetching of the rel='next' link, because it may contain content that interferes with the registration process
1142
        remove_action('wp_head', 'adjacent_posts_rel_link_wp_head');
1143
    }
1144
1145
1146
    /**
1147
     *    extra_nocache_headers
1148
     *
1149
     * @access    public
1150
     * @param $headers
1151
     * @return    array
1152
     */
1153
    public static function extra_nocache_headers($headers): array
1154
    {
1155
        // for NGINX
1156
        $headers['X-Accel-Expires'] = 0;
1157
        // plus extra for Google Chrome since it doesn't seem to respect "no-cache", but WILL respect "no-store"
1158
        $headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0';
1159
        return $headers;
1160
    }
1161
1162
1163
    /**
1164
     *    nocache_headers
1165
     *
1166
     * @access    public
1167
     * @return    void
1168
     */
1169
    public static function nocache_headers()
1170
    {
1171
        nocache_headers();
1172
    }
1173
1174
1175
    /**
1176
     * simply hooks into "wp_list_pages_exclude" filter (for wp_list_pages method) and makes sure EE critical pages are
1177
     * never returned with the function.
1178
     *
1179
     * @param array $exclude_array any existing pages being excluded are in this array.
1180
     * @return array
1181
     */
1182
    public function remove_pages_from_wp_list_pages(array $exclude_array): array
1183
    {
1184
        return array_merge($exclude_array, $this->registry->CFG->core->get_critical_pages_array());
1185
    }
1186
}
1187