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 ( 98ec12...634367 )
by Leonardo
03:51
created

Plugin::getMinorReleaseVersion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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