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 ( d64eb9...b11886 )
by Leonardo
12:12
created

Plugin::checkIsPluginUpgraded()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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