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 ( ae7274...bdf558 )
by Leonardo
11:34
created

Plugin   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 428
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 18
Bugs 0 Features 0
Metric Value
eloc 154
c 18
b 0
f 0
dl 0
loc 428
ccs 0
cts 104
cp 0
rs 10
wmc 28

8 Methods

Rating   Name   Duplication   Size   Complexity  
A showReleaseNotesInAdminNotice() 0 57 1
A getMinorReleaseVersion() 0 4 1
B boot() 0 92 10
A deactivate() 0 24 2
B setup() 0 96 7
A initialize() 0 50 5
A bootApplication() 0 4 1
A activate() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQLAPI\GraphQLAPI;
6
7
use GraphQLAPI\GraphQLAPI\Admin\TableActions\ModuleListTableAction;
8
use GraphQLAPI\GraphQLAPI\BlockCategories\AbstractBlockCategory;
9
use GraphQLAPI\GraphQLAPI\Blocks\AbstractBlock;
10
use GraphQLAPI\GraphQLAPI\Blocks\AccessControlRuleBlocks\AccessControlDisableAccessBlock;
11
use GraphQLAPI\GraphQLAPI\Blocks\AccessControlRuleBlocks\AccessControlUserCapabilitiesBlock;
12
use GraphQLAPI\GraphQLAPI\Blocks\AccessControlRuleBlocks\AccessControlUserRolesBlock;
13
use GraphQLAPI\GraphQLAPI\Blocks\AccessControlRuleBlocks\AccessControlUserStateBlock;
14
use GraphQLAPI\GraphQLAPI\ConditionalOnEnvironment\Admin\Services\Helpers\MenuPageHelper;
15
use GraphQLAPI\GraphQLAPI\ConditionalOnEnvironment\Admin\Services\MenuPages\AboutMenuPage;
16
use GraphQLAPI\GraphQLAPI\ConditionalOnEnvironment\Admin\Services\MenuPages\ModulesMenuPage;
17
use GraphQLAPI\GraphQLAPI\ConditionalOnEnvironment\Admin\Services\MenuPages\SettingsMenuPage;
18
use GraphQLAPI\GraphQLAPI\EditorScripts\AbstractEditorScript;
19
use GraphQLAPI\GraphQLAPI\Facades\ModuleRegistryFacade;
20
use GraphQLAPI\GraphQLAPI\Facades\UserSettingsManagerFacade;
21
use GraphQLAPI\GraphQLAPI\General\RequestParams;
22
use GraphQLAPI\GraphQLAPI\ModuleResolvers\AccessControlFunctionalityModuleResolver;
23
use GraphQLAPI\GraphQLAPI\ModuleResolvers\EndpointFunctionalityModuleResolver;
24
use GraphQLAPI\GraphQLAPI\ModuleResolvers\PerformanceFunctionalityModuleResolver;
25
use GraphQLAPI\GraphQLAPI\ModuleResolvers\PluginManagementFunctionalityModuleResolver;
26
use GraphQLAPI\GraphQLAPI\ModuleResolvers\SchemaConfigurationFunctionalityModuleResolver;
27
use GraphQLAPI\GraphQLAPI\ModuleResolvers\UserInterfaceFunctionalityModuleResolver;
28
use GraphQLAPI\GraphQLAPI\ModuleResolvers\VersioningFunctionalityModuleResolver;
29
use GraphQLAPI\GraphQLAPI\PluginConfiguration;
30
use GraphQLAPI\GraphQLAPI\PostTypes\AbstractPostType;
31
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLAccessControlListPostType;
32
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLCacheControlListPostType;
33
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLEndpointPostType;
34
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLFieldDeprecationListPostType;
35
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLPersistedQueryPostType;
36
use GraphQLAPI\GraphQLAPI\PostTypes\GraphQLSchemaConfigurationPostType;
37
use GraphQLAPI\GraphQLAPI\Taxonomies\AbstractTaxonomy;
38
use PoP\ComponentModel\Facades\Instances\InstanceManagerFacade;
39
use PoP\Engine\AppLoader;
40
use PoP\Root\Container\ContainerBuilderUtils;
41
42
class Plugin
43
{
44
    /**
45
     * Plugin's namespace
46
     */
47
    public const NAMESPACE = __NAMESPACE__;
48
49
    /**
50
     * Store the plugin version in the Options table, to track when
51
     * the plugin is installed/updated
52
     */
53
    public const OPTION_PLUGIN_VERSION = 'graphql-api-plugin-version';
54
55
    /**
56
     * Hook to initalize extension plugins
57
     */
58
    public const HOOK_INITIALIZE_EXTENSION_PLUGIN = __CLASS__ . ':initializeExtensionPlugin';
59
    /**
60
     * Hook to boot extension plugins
61
     */
62
    public const HOOK_BOOT_EXTENSION_PLUGIN = __CLASS__ . ':bootExtensionPlugin';
63
64
    /**
65
     * Plugin set-up, executed immediately when loading the plugin.
66
     * There are three stages for this plugin, and for each extension plugin:
67
     * `setup`, `initialize` and `boot`.
68
     *
69
     * This is because:
70
     *
71
     * - The plugin must execute its logic before the extensions
72
     * - The services can't be booted before all services have been initialized
73
     *
74
     * To attain the needed order, we execute them using hook "plugins_loaded":
75
     *
76
     * 1. GraphQL API => setup(): immediately
77
     * 2. GraphQL API extensions => setup(): priority 0
78
     * 3. GraphQL API => initialize(): priority 10
79
     * 4. GraphQL API extensions => initialize(): priority 20
80
     * 5. GraphQL API => bootApplication(): priority 30
81
     * 6. GraphQL API => boot(): priority 40
82
     * 7. GraphQL API extensions => boot(): priority 50
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'], 10);
174
        \add_action('plugins_loaded', function () {
175
            \do_action(self::HOOK_INITIALIZE_EXTENSION_PLUGIN);
176
        }, 20);
177
        \add_action('plugins_loaded', [$this, 'bootApplication'], 30);
178
        \add_action('plugins_loaded', [$this, 'boot'], 40);
179
        \add_action('plugins_loaded', function () {
180
            \do_action(self::HOOK_BOOT_EXTENSION_PLUGIN);
181
        }, 50);
182
    }
183
184
    /**
185
     * Add a notice with a link to the latest release note,
186
     * to open in a modal window
187
     */
188
    protected function showReleaseNotesInAdminNotice(): void
189
    {
190
        // Load the assets to open in a modal
191
        \add_action('admin_enqueue_scripts', function () {
192
            /**
193
             * Hack to open the modal thickbox iframe with the documentation
194
             */
195
            \wp_enqueue_style(
196
                'thickbox'
197
            );
198
            \wp_enqueue_script(
199
                'plugin-install'
200
            );
201
        });
202
        // Add the admin notice
203
        \add_action('admin_notices', function () {
204
            $instanceManager = InstanceManagerFacade::getInstance();
205
            /**
206
             * @var AboutMenuPage
207
             */
208
            $aboutMenuPage = $instanceManager->getInstance(AboutMenuPage::class);
209
            // Calculate the minor release version.
210
            // Eg: if current version is 0.6.3, minor version is 0.6
211
            $minorReleaseVersion = $this->getMinorReleaseVersion(\GRAPHQL_API_VERSION);
212
            $releaseNotesURL = \admin_url(sprintf(
213
                'admin.php?page=%s&%s=%s&%s=%s&TB_iframe=true',
214
                $aboutMenuPage->getScreenID(),
215
                RequestParams::TAB,
216
                RequestParams::TAB_DOCS,
217
                RequestParams::DOC,
218
                sprintf(
219
                    'release-notes/%s',
220
                    $minorReleaseVersion
221
                )
222
            ));
223
            /**
224
             * @var SettingsMenuPage
225
             */
226
            $settingsMenuPage = $instanceManager->getInstance(SettingsMenuPage::class);
227
            $moduleRegistry = ModuleRegistryFacade::getInstance();
228
            $generalSettingsURL = \admin_url(sprintf(
229
                'admin.php?page=%s&tab=%s',
230
                $settingsMenuPage->getScreenID(),
231
                $moduleRegistry
232
                    ->getModuleResolver(PluginManagementFunctionalityModuleResolver::GENERAL)
233
                    ->getID(PluginManagementFunctionalityModuleResolver::GENERAL)
234
            ));
235
            _e(sprintf(
236
                '<div class="notice notice-success is-dismissible">' .
237
                '<p>%s</p>' .
238
                '</div>',
239
                sprintf(
240
                    __('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'),
241
                    \GRAPHQL_API_VERSION,
242
                    $releaseNotesURL,
243
                    'thickbox open-plugin-details-modal',
244
                    $generalSettingsURL
245
                )
246
            ));
247
        });
248
    }
249
250
    /**
251
     * Given a version in semver (MAJOR.MINOR.PATCH),
252
     * return the minor version (MAJOR.MINOR)
253
     */
254
    protected function getMinorReleaseVersion(string $version): string
255
    {
256
        $versionParts = explode('.', $version);
257
        return $versionParts[0] . '.' . $versionParts[1];
258
    }
259
260
    /**
261
     * Plugin initialization, executed on hook "plugins_loaded"
262
     * to wait for all extensions to be loaded
263
     *
264
     * @return void
265
     */
266
    public function initialize(): void
267
    {
268
        /**
269
         * Watch out! If we are in the Modules page and enabling/disabling
270
         * a module, then already take that new state!
271
         * This is because `maybeProcessAction`, which is where modules are
272
         * enabled/disabled, must be executed before PluginConfiguration::initialize(),
273
         * which is where the plugin reads if a module is enabled/disabled as to
274
         * set the environment constants.
275
         *
276
         * This is mandatory, because only when it is enabled, can a module
277
         * have its state persisted when calling `flush_rewrite`
278
         */
279
        if (\is_admin()) {
280
            // We can't use the InstanceManager, since at this stage it hasn't
281
            // been initialized yet
282
            // We can create a new instance of MenuPageHelper and ModulesMenuPage
283
            // because their instantiation produces no side-effects
284
            // (maybe that happens under `initialize`)
285
            $menuPageHelper = new MenuPageHelper();
286
            $modulesMenuPage = new ModulesMenuPage($menuPageHelper);
287
            if (
288
                (isset($_GET['page']) && $_GET['page'] == $modulesMenuPage->getScreenID())
289
                && !$menuPageHelper->isDocumentationScreen()
290
            ) {
291
                // Instantiating ModuleListTableAction DOES have side-effects,
292
                // but they are needed, and won't be repeated when instantiating
293
                // the class through the Container Builder
294
                $moduleListTable = new ModuleListTableAction();
295
                $moduleListTable->maybeProcessAction();
296
            }
297
        }
298
299
        // Configure the plugin. This defines hooks to set environment variables,
300
        // so must be executed
301
        // before those hooks are triggered for first time
302
        // (in ComponentConfiguration classes)
303
        PluginConfiguration::initialize();
304
305
        // Component configuration
306
        $componentClassConfiguration = PluginConfiguration::getComponentClassConfiguration();
307
        $skipSchemaComponentClasses = PluginConfiguration::getSkippingSchemaComponentClasses();
308
309
        // Initialize the containers
310
        AppLoader::addComponentClassesToInitialize(
311
            [
312
                Component::class,
313
            ],
314
            $componentClassConfiguration,
315
            $skipSchemaComponentClasses
316
        );
317
    }
318
319
    /**
320
     * Boot the application
321
     */
322
    public function bootApplication(): void
323
    {
324
        // Boot all PoP components, from this plugin and all extensions
325
        AppLoader::bootApplication(...PluginConfiguration::getContainerCacheConfiguration());
326
    }
327
328
    /**
329
     * Plugin initialization, executed on hook "plugins_loaded"
330
     * to wait for all extensions to be loaded
331
     */
332
    public function boot(): void
333
    {
334
        $instanceManager = InstanceManagerFacade::getInstance();
335
        $moduleRegistry = ModuleRegistryFacade::getInstance();
336
337
        /**
338
         * Taxonomies must be initialized before Post Types
339
         */
340
        $taxonomyServiceClasses = ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\Taxonomies');
341
        foreach ($taxonomyServiceClasses as $serviceClass) {
342
            /**
343
             * @var AbstractTaxonomy
344
             */
345
            $service = $instanceManager->getInstance($serviceClass);
346
            $service->initialize();
347
        }
348
        /**
349
         * Initialize Post Types manually to control in what order they are added to the menu
350
         */
351
        $postTypeServiceClassModules = [
352
            GraphQLEndpointPostType::class => EndpointFunctionalityModuleResolver::CUSTOM_ENDPOINTS,
353
            GraphQLPersistedQueryPostType::class => EndpointFunctionalityModuleResolver::PERSISTED_QUERIES,
354
            GraphQLSchemaConfigurationPostType::class => SchemaConfigurationFunctionalityModuleResolver::SCHEMA_CONFIGURATION,
355
            GraphQLAccessControlListPostType::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL,
356
            GraphQLCacheControlListPostType::class => PerformanceFunctionalityModuleResolver::CACHE_CONTROL,
357
            GraphQLFieldDeprecationListPostType::class => VersioningFunctionalityModuleResolver::FIELD_DEPRECATION,
358
        ];
359
        foreach ($postTypeServiceClassModules as $serviceClass => $module) {
360
            // Check that the corresponding module is enabled
361
            if ($moduleRegistry->isModuleEnabled($module)) {
362
                /**
363
                 * @var AbstractPostType
364
                 */
365
                $service = $instanceManager->getInstance($serviceClass);
366
                $service->initialize();
367
            }
368
        }
369
        /**
370
         * Editor Scripts
371
         * They are all used to show the Welcome Guide
372
         */
373
        if ($moduleRegistry->isModuleEnabled(UserInterfaceFunctionalityModuleResolver::WELCOME_GUIDES)) {
374
            $editorScriptServiceClasses = ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\EditorScripts');
375
            foreach ($editorScriptServiceClasses as $serviceClass) {
376
                /**
377
                 * @var AbstractEditorScript
378
                 */
379
                $service = $instanceManager->getInstance($serviceClass);
380
                $service->initialize();
381
            }
382
        }
383
        /**
384
         * Blocks
385
         * The GraphiQL Block may be overriden to GraphiQLWithExplorerBlock
386
         */
387
        $blockServiceClasses = ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\Blocks', false);
388
        foreach ($blockServiceClasses as $serviceClass) {
389
            /**
390
             * @var AbstractBlock
391
             */
392
            $service = $instanceManager->getInstance($serviceClass);
393
            $service->initialize();
394
        }
395
        /**
396
         * Access Control Nested Blocks
397
         * Register them one by one, as to disable them if module is disabled
398
         */
399
        $accessControlRuleBlockServiceClassModules = [
400
            AccessControlDisableAccessBlock::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL_RULE_DISABLE_ACCESS,
401
            AccessControlUserStateBlock::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL_RULE_USER_STATE,
402
            AccessControlUserRolesBlock::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL_RULE_USER_ROLES,
403
            AccessControlUserCapabilitiesBlock::class => AccessControlFunctionalityModuleResolver::ACCESS_CONTROL_RULE_USER_CAPABILITIES,
404
        ];
405
        foreach ($accessControlRuleBlockServiceClassModules as $serviceClass => $module) {
406
            if ($moduleRegistry->isModuleEnabled($module)) {
407
                /**
408
                 * @var AbstractBlock
409
                 */
410
                $service = $instanceManager->getInstance($serviceClass);
411
                $service->initialize();
412
            }
413
        }
414
        /**
415
         * Block categories
416
         */
417
        $blockCategoryServiceClasses = ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\BlockCategories');
418
        foreach ($blockCategoryServiceClasses as $serviceClass) {
419
            /**
420
             * @var AbstractBlockCategory
421
             */
422
            $service = $instanceManager->getInstance($serviceClass);
423
            $service->initialize();
424
        }
425
    }
426
427
    /**
428
     * Get permalinks to work when activating the plugin
429
     *
430
     * @see    https://codex.wordpress.org/Function_Reference/register_post_type#Flushing_Rewrite_on_Activation
431
     * @return void
432
     */
433
    public function activate(): void
434
    {
435
        // Initialize the timestamp
436
        $userSettingsManager = UserSettingsManagerFacade::getInstance();
437
        $userSettingsManager->storeTimestamp();
438
    }
439
440
    /**
441
     * Remove permalinks when deactivating the plugin
442
     *
443
     * @see    https://developer.wordpress.org/plugins/plugin-basics/activation-deactivation-hooks/
444
     * @return void
445
     */
446
    public function deactivate(): void
447
    {
448
        // First, unregister the post type, so the rules are no longer in memory.
449
        $instanceManager = InstanceManagerFacade::getInstance();
450
        $postTypeObjects = array_map(
451
            function ($serviceClass) use ($instanceManager): AbstractPostType {
452
                /**
453
                 * @var AbstractPostType
454
                 */
455
                $postTypeObject = $instanceManager->getInstance($serviceClass);
456
                return $postTypeObject;
457
            },
458
            ContainerBuilderUtils::getServiceClassesUnderNamespace(__NAMESPACE__ . '\\PostTypes')
459
        );
460
        foreach ($postTypeObjects as $postTypeObject) {
461
            $postTypeObject->unregisterPostType();
462
        }
463
464
        // Then, clear the permalinks to remove the post type's rules from the database.
465
        \flush_rewrite_rules();
466
467
        // Remove the timestamp
468
        $userSettingsManager = UserSettingsManagerFacade::getInstance();
469
        $userSettingsManager->removeTimestamp();
470
    }
471
}
472