GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 44a53f...aac044 )
by Leonardo
10:22
created

Plugin::boot()   B

Complexity

Conditions 10
Paths 144

Size

Total Lines 95
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 40
c 2
b 0
f 0
nc 144
nop 0
dl 0
loc 95
ccs 0
cts 31
cp 0
crap 110
rs 7.2999

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQLAPI\GraphQLAPI;
6
7
use PoP\Engine\ComponentLoader;
8
use GraphQLAPI\GraphQLAPI\PluginConfiguration;
9
use GraphQLAPI\GraphQLAPI\Blocks\AbstractBlock;
10
use GraphQLAPI\GraphQLAPI\General\RequestParams;
11
use GraphQLAPI\GraphQLAPI\ConditionalOnEnvironment\Admin\Services\Menus\AbstractMenu;
12
use GraphQLAPI\GraphQLAPI\PostTypes\AbstractPostType;
13
use GraphQLAPI\GraphQLAPI\Taxonomies\AbstractTaxonomy;
14
use GraphQLAPI\GraphQLAPI\Facades\ModuleRegistryFacade;
15
use PoP\Root\Container\ContainerBuilderUtils;
16
use GraphQLAPI\GraphQLAPI\Admin\MenuPages\AboutMenuPage;
17
use GraphQLAPI\GraphQLAPI\Admin\MenuPages\ModulesMenuPage;
18
use GraphQLAPI\GraphQLAPI\Admin\MenuPages\SettingsMenuPage;
19
use GraphQLAPI\GraphQLAPI\Facades\UserSettingsManagerFacade;
20
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLEndpointPostType;
21
use GraphQLAPI\GraphQLAPI\EditorScripts\AbstractEditorScript;
22
use PoP\ComponentModel\Facades\Instances\InstanceManagerFacade;
23
use GraphQLAPI\GraphQLAPI\BlockCategories\AbstractBlockCategory;
24
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLPersistedQueryPostType;
25
use GraphQLAPI\GraphQLAPI\Admin\TableActions\ModuleListTableAction;
26
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLCacheControlListPostType;
27
use GraphQLAPI\GraphQLAPI\EndpointResolvers\AbstractEndpointResolver;
28
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLAccessControlListPostType;
29
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLSchemaConfigurationPostType;
30
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLFieldDeprecationListPostType;
31
use GraphQLAPI\GraphQLAPI\ModuleResolvers\EndpointFunctionalityModuleResolver;
32
use GraphQLAPI\GraphQLAPI\ModuleResolvers\VersioningFunctionalityModuleResolver;
33
use GraphQLAPI\GraphQLAPI\ModuleResolvers\PerformanceFunctionalityModuleResolver;
34
use GraphQLAPI\GraphQLAPI\ModuleResolvers\AccessControlFunctionalityModuleResolver;
35
use GraphQLAPI\GraphQLAPI\ModuleResolvers\UserInterfaceFunctionalityModuleResolver;
36
use GraphQLAPI\GraphQLAPI\Blocks\AccessControlRuleBlocks\AccessControlUserRolesBlock;
37
use GraphQLAPI\GraphQLAPI\Blocks\AccessControlRuleBlocks\AccessControlUserStateBlock;
38
use GraphQLAPI\GraphQLAPI\ModuleResolvers\PluginManagementFunctionalityModuleResolver;
39
use GraphQLAPI\GraphQLAPI\Blocks\AccessControlRuleBlocks\AccessControlDisableAccessBlock;
40
use GraphQLAPI\GraphQLAPI\ModuleResolvers\SchemaConfigurationFunctionalityModuleResolver;
41
use GraphQLAPI\GraphQLAPI\Blocks\AccessControlRuleBlocks\AccessControlUserCapabilitiesBlock;
42
43
class Plugin
44
{
45
    /**
46
     * Plugin's namespace
47
     */
48
    public const NAMESPACE = __NAMESPACE__;
49
50
    /**
51
     * Store the plugin version in the Options table, to track when
52
     * the plugin is installed/updated
53
     */
54
    public const OPTION_PLUGIN_VERSION = 'graphql-api-plugin-version';
55
56
    /**
57
     * Hook to initalize extension plugins
58
     */
59
    public const HOOK_INITIALIZE_EXTENSION_PLUGIN = __CLASS__ . ':initializeExtensionPlugin';
60
    /**
61
     * Hook to boot extension plugins
62
     */
63
    public const HOOK_BOOT_EXTENSION_PLUGIN = __CLASS__ . ':bootExtensionPlugin';
64
65
    /**
66
     * Plugin set-up, executed immediately when loading the plugin.
67
     * There are three stages for this plugin, and for each extension plugin:
68
     * `setup`, `initialize` and `boot`.
69
     *
70
     * This is because:
71
     *
72
     * - The plugin must execute its logic before the extensions
73
     * - The services can't be booted before all services have been initialized
74
     *
75
     * To attain the needed order, we execute them using hook "plugins_loaded":
76
     *
77
     * 1. GraphQL API => setup(): immediately
78
     * 2. GraphQL API extensions => setup(): priority 0
79
     * 3. GraphQL API => initialize(): priority 5
80
     * 4. GraphQL API extensions => initialize(): priority 10
81
     * 5. GraphQL API => boot(): priority 15
82
     * 6. GraphQL API extensions => boot(): priority 20
83
     *
84
     * @return void
85
     */
86
    public function setup(): void
87
    {
88
        // Functions to execute when activating/deactivating the plugin
89
        \register_deactivation_hook(\GRAPHQL_API_PLUGIN_FILE, [$this, 'deactivate']);
90
        /**
91
         * PoP depends on hook "init" to set-up the endpoint rewrite,
92
         * as in function `addRewriteEndpoints` in `AbstractEndpointHandler`
93
         * However, activating the plugin takes place AFTER hooks "plugins_loaded"
94
         * and "init". Hence, the code cannot flush the rewrite_rules when the plugin
95
         * is activated, and any non-default GraphQL endpoint is not set.
96
         *
97
         * The solution (hack) is to check if the plugin has just been installed,
98
         * and then apply the logic, on every request in the admin!
99
         *
100
         * @see https://developer.wordpress.org/reference/functions/register_activation_hook/#process-flow
101
         */
102
        \register_activation_hook(
103
            \GRAPHQL_API_PLUGIN_FILE,
104
            function (): void {
105
                // By removing the option (in case it already exists from a previously-installed version),
106
                // the next request will know the plugin was just installed
107
                \update_option(self::OPTION_PLUGIN_VERSION, false);
108
                // This is the proper activation logic
109
                $this->activate();
110
            }
111
        );
112
        \add_action(
113
            'admin_init',
114
            function (): void {
115
                // If there is no version stored, it's the first screen after activating the plugin
116
                $isPluginJustActivated = \get_option(self::OPTION_PLUGIN_VERSION) === false;
117
                if (!$isPluginJustActivated) {
118
                    return;
119
                }
120
                // Update to the current version
121
                \update_option(self::OPTION_PLUGIN_VERSION, \GRAPHQL_API_VERSION);
122
                // Required logic after plugin is activated
123
                \flush_rewrite_rules();
124
            }
125
        );
126
        /**
127
         * Show an admin notice with a link to the latest release notes
128
         */
129
        \add_action(
130
            'admin_init',
131
            function (): void {
132
                // Do not execute when doing Ajax, since we can't show the one-time
133
                // admin notice to the user then
134
                if (\wp_doing_ajax()) {
135
                    return;
136
                }
137
                // Check if the plugin has been updated: if the stored version in the DB
138
                // and the current plugin's version are different
139
                // It could also be false from the first time we install the plugin
140
                $storedVersion = \get_option(self::OPTION_PLUGIN_VERSION, \GRAPHQL_API_VERSION);
141
                if (!$storedVersion || $storedVersion == \GRAPHQL_API_VERSION) {
142
                    return;
143
                }
144
                // Update to the current version
145
                \update_option(self::OPTION_PLUGIN_VERSION, \GRAPHQL_API_VERSION);
146
                // Admin notice: Check if it is enabled
147
                $userSettingsManager = UserSettingsManagerFacade::getInstance();
148
                if (
149
                    !$userSettingsManager->getSetting(
150
                        PluginManagementFunctionalityModuleResolver::GENERAL,
151
                        PluginManagementFunctionalityModuleResolver::OPTION_ADD_RELEASE_NOTES_ADMIN_NOTICE
152
                    )
153
                ) {
154
                    return;
155
                }
156
                // Show admin notice only when updating MAJOR or MINOR versions. No need for PATCH versions
157
                $currentMinorReleaseVersion = $this->getMinorReleaseVersion(\GRAPHQL_API_VERSION);
158
                $previousMinorReleaseVersion = $this->getMinorReleaseVersion($storedVersion);
159
                if ($currentMinorReleaseVersion == $previousMinorReleaseVersion) {
160
                    return;
161
                }
162
                // All checks passed, show the release notes
163
                $this->showReleaseNotesInAdminNotice();
164
            }
165
        );
166
167
        /**
168
         * Wait until "plugins_loaded" to initialize the plugin, because:
169
         *
170
         * - ModuleListTableAction requires `wp_verify_nonce`, loaded in pluggable.php
171
         * - Allow other plugins to inject their own functionality
172
         */
173
        \add_action('plugins_loaded', [$this, 'initialize'], 5);
174
        \add_action('plugins_loaded', [$this, 'boot'], 15);
175
        /**
176
         * Hooks to initialize/boot extension plugins, triggered
177
         * after this main plugin is initialized/booted
178
         */
179
        \add_action('plugins_loaded', function () {
180
            \do_action(self::HOOK_INITIALIZE_EXTENSION_PLUGIN);
181
        }, 10);
182
        \add_action('plugins_loaded', function () {
183
            \do_action(self::HOOK_BOOT_EXTENSION_PLUGIN);
184
        }, 20);
185
    }
186
187
    /**
188
     * Add a notice with a link to the latest release note,
189
     * to open in a modal window
190
     */
191
    protected function showReleaseNotesInAdminNotice(): void
192
    {
193
        // Load the assets to open in a modal
194
        \add_action('admin_enqueue_scripts', function () {
195
            /**
196
             * Hack to open the modal thickbox iframe with the documentation
197
             */
198
            \wp_enqueue_style(
199
                'thickbox'
200
            );
201
            \wp_enqueue_script(
202
                'plugin-install'
203
            );
204
        });
205
        // Add the admin notice
206
        \add_action('admin_notices', function () {
207
            $instanceManager = InstanceManagerFacade::getInstance();
208
            /**
209
             * @var AboutMenuPage
210
             */
211
            $aboutMenuPage = $instanceManager->getInstance(AboutMenuPage::class);
212
            // Calculate the minor release version.
213
            // Eg: if current version is 0.6.3, minor version is 0.6
214
            $minorReleaseVersion = $this->getMinorReleaseVersion(\GRAPHQL_API_VERSION);
215
            $releaseNotesURL = \admin_url(sprintf(
216
                'admin.php?page=%s&%s=%s&%s=%s&TB_iframe=true',
217
                $aboutMenuPage->getScreenID(),
218
                RequestParams::TAB,
219
                RequestParams::TAB_DOCS,
220
                RequestParams::DOC,
221
                sprintf(
222
                    'release-notes/%s',
223
                    $minorReleaseVersion
224
                )
225
            ));
226
            /**
227
             * @var SettingsMenuPage
228
             */
229
            $settingsMenuPage = $instanceManager->getInstance(SettingsMenuPage::class);
230
            $moduleRegistry = ModuleRegistryFacade::getInstance();
231
            $generalSettingsURL = \admin_url(sprintf(
232
                'admin.php?page=%s&tab=%s',
233
                $settingsMenuPage->getScreenID(),
234
                $moduleRegistry
235
                    ->getModuleResolver(PluginManagementFunctionalityModuleResolver::GENERAL)
236
                    ->getID(PluginManagementFunctionalityModuleResolver::GENERAL)
237
            ));
238
            _e(sprintf(
239
                '<div class="notice notice-success is-dismissible">' .
240
                '<p>%s</p>' .
241
                '</div>',
242
                sprintf(
243
                    __('Plugin <strong>GraphQL API for WordPress</strong> has been updated to version <code>%s</code>. <strong><a href="%s" class="%s">Check out what\'s new</a></strong> | <a href="%s">Disable this admin notice in the Settings</a>', 'graphql-api'),
244
                    \GRAPHQL_API_VERSION,
245
                    $releaseNotesURL,
246
                    'thickbox open-plugin-details-modal',
247
                    $generalSettingsURL
248
                )
249
            ));
250
        });
251
    }
252
253
    /**
254
     * Given a version in semver (MAJOR.MINOR.PATCH),
255
     * return the minor version (MAJOR.MINOR)
256
     */
257
    protected function getMinorReleaseVersion(string $version): string
258
    {
259
        $versionParts = explode('.', $version);
260
        return $versionParts[0] . '.' . $versionParts[1];
261
    }
262
263
    /**
264
     * Plugin initialization, executed on hook "plugins_loaded"
265
     * to wait for all extensions to be loaded
266
     *
267
     * @return void
268
     */
269
    public function initialize(): void
270
    {
271
        /**
272
         * Watch out! If we are in the Modules page and enabling/disabling
273
         * a module, then already take that new state!
274
         * This is because `maybeProcessAction`, which is where modules are
275
         * enabled/disabled, must be executed before PluginConfiguration::initialize(),
276
         * which is where the plugin reads if a module is enabled/disabled as to
277
         * set the environment constants.
278
         *
279
         * This is mandatory, because only when it is enabled, can a module
280
         * have its state persisted when calling `flush_rewrite`
281
         */
282
        if (\is_admin()) {
283
            // We can't use the InstanceManager, since at this stage it hasn't
284
            // been initialized yet
285
            // We can create a new instances of ModulesMenuPage because
286
            // its instantiation produces no side-effects
287
            $modulesMenuPage = new ModulesMenuPage();
288
            if (isset($_GET['page']) && $_GET['page'] == $modulesMenuPage->getScreenID()) {
289
                // Instantiating ModuleListTableAction DOES have side-effects,
290
                // but they are needed, and won't be repeated when instantiating
291
                // the class through the Container Builder
292
                $moduleListTable = new ModuleListTableAction();
293
                $moduleListTable->maybeProcessAction();
294
            }
295
        }
296
297
        // Configure the plugin. This defines hooks to set environment variables,
298
        // so must be executed
299
        // before those hooks are triggered for first time
300
        // (in ComponentConfiguration classes)
301
        PluginConfiguration::initialize();
302
303
        // Component configuration
304
        $componentClassConfiguration = PluginConfiguration::getComponentClassConfiguration();
305
        $skipSchemaComponentClasses = PluginConfiguration::getSkippingSchemaComponentClasses();
306
307
        // Initialize the plugin's Component and, with it,
308
        // all its dependencies from PoP
309
        ComponentLoader::initializeComponents(
310
            [
311
                Component::class,
312
            ],
313
            $componentClassConfiguration,
314
            $skipSchemaComponentClasses
315
        );
316
    }
317
318
    /**
319
     * Plugin initialization, executed on hook "plugins_loaded"
320
     * to wait for all extensions to be loaded
321
     */
322
    public function boot(): void
323
    {
324
        // Boot all PoP components, from this plugin and all extensions
325
        ComponentLoader::bootComponents();
326
327
        $instanceManager = InstanceManagerFacade::getInstance();
328
        $moduleRegistry = ModuleRegistryFacade::getInstance();
329
330
        /**
331
         * Taxonomies must be initialized before Post Types
332
         */
333
        $taxonomyServiceClasses = ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\Taxonomies');
334
        foreach ($taxonomyServiceClasses as $serviceClass) {
335
            /**
336
             * @var AbstractTaxonomy
337
             */
338
            $service = $instanceManager->getInstance($serviceClass);
339
            $service->initialize();
340
        }
341
        /**
342
         * Initialize Post Types manually to control in what order they are added to the menu
343
         */
344
        $postTypeServiceClassModules = [
345
            GraphQLEndpointPostType::class => EndpointFunctionalityModuleResolver::CUSTOM_ENDPOINTS,
346
            GraphQLPersistedQueryPostType::class => EndpointFunctionalityModuleResolver::PERSISTED_QUERIES,
347
            GraphQLSchemaConfigurationPostType::class => SchemaConfigurationFunctionalityModuleResolver::SCHEMA_CONFIGURATION,
348
            GraphQLAccessControlListPostType::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL,
349
            GraphQLCacheControlListPostType::class => PerformanceFunctionalityModuleResolver::CACHE_CONTROL,
350
            GraphQLFieldDeprecationListPostType::class => VersioningFunctionalityModuleResolver::FIELD_DEPRECATION,
351
        ];
352
        foreach ($postTypeServiceClassModules as $serviceClass => $module) {
353
            // Check that the corresponding module is enabled
354
            if ($moduleRegistry->isModuleEnabled($module)) {
355
                /**
356
                 * @var AbstractPostType
357
                 */
358
                $service = $instanceManager->getInstance($serviceClass);
359
                $service->initialize();
360
            }
361
        }
362
        /**
363
         * Editor Scripts
364
         * They are all used to show the Welcome Guide
365
         */
366
        if ($moduleRegistry->isModuleEnabled(UserInterfaceFunctionalityModuleResolver::WELCOME_GUIDES)) {
367
            $editorScriptServiceClasses = ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\EditorScripts');
368
            foreach ($editorScriptServiceClasses as $serviceClass) {
369
                /**
370
                 * @var AbstractEditorScript
371
                 */
372
                $service = $instanceManager->getInstance($serviceClass);
373
                $service->initialize();
374
            }
375
        }
376
        /**
377
         * Blocks
378
         * The GraphiQL Block may be overriden to GraphiQLWithExplorerBlock
379
         */
380
        $blockServiceClasses = ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\Blocks', false);
381
        foreach ($blockServiceClasses as $serviceClass) {
382
            /**
383
             * @var AbstractBlock
384
             */
385
            $service = $instanceManager->getInstance($serviceClass);
386
            $service->initialize();
387
        }
388
        /**
389
         * Access Control Nested Blocks
390
         * Register them one by one, as to disable them if module is disabled
391
         */
392
        $accessControlRuleBlockServiceClassModules = [
393
            AccessControlDisableAccessBlock::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL_RULE_DISABLE_ACCESS,
394
            AccessControlUserStateBlock::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL_RULE_USER_STATE,
395
            AccessControlUserRolesBlock::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL_RULE_USER_ROLES,
396
            AccessControlUserCapabilitiesBlock::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL_RULE_USER_CAPABILITIES,
397
        ];
398
        foreach ($accessControlRuleBlockServiceClassModules as $serviceClass => $module) {
399
            if ($moduleRegistry->isModuleEnabled($module)) {
400
                /**
401
                 * @var AbstractBlock
402
                 */
403
                $service = $instanceManager->getInstance($serviceClass);
404
                $service->initialize();
405
            }
406
        }
407
        /**
408
         * Block categories
409
         */
410
        $blockCategoryServiceClasses = ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\BlockCategories');
411
        foreach ($blockCategoryServiceClasses as $serviceClass) {
412
            /**
413
             * @var AbstractBlockCategory
414
             */
415
            $service = $instanceManager->getInstance($serviceClass);
416
            $service->initialize();
417
        }
418
    }
419
420
    /**
421
     * Get permalinks to work when activating the plugin
422
     *
423
     * @see    https://codex.wordpress.org/Function_Reference/register_post_type#Flushing_Rewrite_on_Activation
424
     * @return void
425
     */
426
    public function activate(): void
427
    {
428
        // Initialize the timestamp
429
        $userSettingsManager = UserSettingsManagerFacade::getInstance();
430
        $userSettingsManager->storeTimestamp();
431
    }
432
433
    /**
434
     * Remove permalinks when deactivating the plugin
435
     *
436
     * @see    https://developer.wordpress.org/plugins/plugin-basics/activation-deactivation-hooks/
437
     * @return void
438
     */
439
    public function deactivate(): void
440
    {
441
        // First, unregister the post type, so the rules are no longer in memory.
442
        $instanceManager = InstanceManagerFacade::getInstance();
443
        $postTypeObjects = array_map(
444
            function ($serviceClass) use ($instanceManager): AbstractPostType {
445
                /**
446
                 * @var AbstractPostType
447
                 */
448
                $postTypeObject = $instanceManager->getInstance($serviceClass);
449
                return $postTypeObject;
450
            },
451
            ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\PostTypes')
452
        );
453
        foreach ($postTypeObjects as $postTypeObject) {
454
            $postTypeObject->unregisterPostType();
455
        }
456
457
        // Then, clear the permalinks to remove the post type's rules from the database.
458
        \flush_rewrite_rules();
459
460
        // Remove the timestamp
461
        $userSettingsManager = UserSettingsManagerFacade::getInstance();
462
        $userSettingsManager->removeTimestamp();
463
    }
464
}
465