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 ( 100f27...a4f893 )
by Leonardo
03:21
created

SettingsMenuPage   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 393
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 200
c 1
b 0
f 0
dl 0
loc 393
ccs 0
cts 135
cp 0
rs 6.96
wmc 53

12 Methods

Rating   Name   Duplication   Size   Complexity  
A printWithTabs() 0 3 1
A getSettingsFieldForModule() 0 3 1
A getMenuPageSlug() 0 3 1
A getAllItems() 0 15 2
A printCheckboxField() 0 10 1
A getOptionValue() 0 4 1
A enqueueAssets() 0 6 2
C print() 0 81 13
C normalizeSettings() 0 45 12
B printSelectField() 0 25 8
A printInputField() 0 16 6
B initialize() 0 89 5

How to fix   Complexity   

Complex Class

Complex classes like SettingsMenuPage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SettingsMenuPage, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQLAPI\GraphQLAPI\Admin\MenuPages;
6
7
use GraphQLAPI\GraphQLAPI\Settings\Options;
8
use GraphQLAPI\GraphQLAPI\Facades\ModuleRegistryFacade;
9
use GraphQLAPI\GraphQLAPI\Admin\MenuPages\AbstractMenuPage;
10
use GraphQLAPI\GraphQLAPI\Facades\UserSettingsManagerFacade;
11
use GraphQLAPI\GraphQLAPI\General\RequestParams;
12
use GraphQLAPI\GraphQLAPI\ModuleSettings\Properties;
13
14
/**
15
 * Settings menu page
16
 */
17
class SettingsMenuPage extends AbstractMenuPage
18
{
19
    use GraphQLAPIMenuPageTrait, UseTabpanelMenuPageTrait;
20
21
    public const FORM_ORIGIN = 'form-origin';
22
    public const SETTINGS_FIELD = 'graphql-api-settings';
23
24
    public function getMenuPageSlug(): string
25
    {
26
        return 'settings';
27
    }
28
29
    /**
30
     * Initialize the class instance
31
     *
32
     * @return void
33
     */
34
    public function initialize(): void
35
    {
36
        parent::initialize();
37
38
        /**
39
         * Before saving the settings in the DB,
40
         * transform the values:
41
         *
42
         * - from string to bool/int
43
         * - default value if input is empty
44
         */
45
        $option = self::SETTINGS_FIELD;
46
        // \add_filter(
47
        //     "pre_update_option_{$option}",
48
        //     [$this, 'normalizeSettings']
49
        // );
50
51
        /**
52
         * After saving the settings in the DB:
53
         * - Flush the rewrite rules, so different URL slugs take effect
54
         * - Update the timestamp
55
         */
56
        \add_action(
57
            "update_option_{$option}",
58
            function () {
59
                \flush_rewrite_rules();
60
61
                // Update the timestamp
62
                $userSettingsManager = UserSettingsManagerFacade::getInstance();
63
                $userSettingsManager->storeTimestamp();
64
            }
65
        );
66
67
        /**
68
         * Register the settings
69
         */
70
        \add_action(
71
            'admin_init',
72
            function () {
73
                $items = $this->getAllItems();
74
                foreach ($items as $item) {
75
                    $settingsFieldForModule = $this->getSettingsFieldForModule($item['id']);
76
                    $module = $item['module'];
77
                    \add_settings_section(
78
                        $settingsFieldForModule,
79
                        // The empty string ensures the render function won't output a h2.
80
                        '',
81
                        function () {
82
                        },
83
                        self::SETTINGS_FIELD
84
                    );
85
                    foreach ($item['settings'] as $itemSetting) {
86
                        \add_settings_field(
87
                            $itemSetting[Properties::NAME],
88
                            $itemSetting[Properties::TITLE] ?? '',
89
                            function () use ($module, $itemSetting) {
90
                                $type = $itemSetting[Properties::TYPE] ?? null;
91
                                $possibleValues = $itemSetting[Properties::POSSIBLE_VALUES] ?? [];
92
                                if (!empty($possibleValues)) {
93
                                    $this->printSelectField($module, $itemSetting);
94
                                } elseif ($type == Properties::TYPE_BOOL) {
95
                                    $this->printCheckboxField($module, $itemSetting);
96
                                } else {
97
                                    $this->printInputField($module, $itemSetting);
98
                                }
99
                            },
100
                            self::SETTINGS_FIELD,
101
                            $settingsFieldForModule,
102
                            [
103
                                'label' => $itemSetting[Properties::DESCRIPTION] ?? '',
104
                                'id' => $itemSetting[Properties::NAME],
105
                            ]
106
                        );
107
                    }
108
                }
109
110
                /**
111
                 * Finally register all the settings
112
                 */
113
                \register_setting(
114
                    self::SETTINGS_FIELD,
115
                    Options::SETTINGS,
116
                    [
117
                        'type' => 'array',
118
                        'description' => \__('Settings for the GraphQL API', 'graphql-api'),
119
                        // This call is needed to cast the data
120
                        // before saving to the DB
121
                        'sanitize_callback' => [$this, 'normalizeSettings'],
122
                        'show_in_rest' => false,
123
                    ]
124
                );
125
            }
126
        );
127
    }
128
129
    /**
130
     * Normalize the form values:
131
     *
132
     * - If the input is empty, replace with the default
133
     * - Convert from string to int/bool
134
     *
135
     * @param array<string, string> $value All values submitted, each under its optionName as key
136
     * @return array<string, mixed> Normalized values
137
     */
138
    public function normalizeSettings(array $value): array
139
    {
140
        $moduleRegistry = ModuleRegistryFacade::getInstance();
141
        $items = $this->getAllItems();
142
        foreach ($items as $item) {
143
            $module = $item['module'];
144
            $moduleResolver = $moduleRegistry->getModuleResolver($module);
145
            foreach ($item['settings'] as $itemSetting) {
146
                $type = $itemSetting[Properties::TYPE] ?? null;
147
                /**
148
                 * Cast type so PHPStan doesn't throw error
149
                 */
150
                $name = (string)$itemSetting[Properties::NAME];
151
                $option = $itemSetting[Properties::INPUT];
152
                /**
153
                 * If the input is empty, replace with the default
154
                 * It can't be empty, because that could be equivalent
155
                 * to disabling the module, which is done
156
                 * from the Modules page, not from Settings.
157
                 * Ignore for bool since empty means `false` (tackled below)
158
                 * For int, "0" is valid, it must not be considered empty
159
                 */
160
                if (empty($value[$name])
161
                    && $type != Properties::TYPE_BOOL
162
                    && !($type == Properties::TYPE_INT && $value[$name] == '0')
163
                ) {
164
                    $value[$name] = $moduleResolver->getSettingsDefaultValue($module, $option);
165
                } elseif ($type == Properties::TYPE_BOOL) {
166
                    $value[$name] = !empty($value[$name]);
167
                } elseif ($type == Properties::TYPE_INT) {
168
                    $value[$name] = (int) $value[$name];
169
                    // If the value is below its minimum, reset to the default one
170
                    $minNumber = $itemSetting[Properties::MIN_NUMBER] ?? null;
171
                    if (!is_null($minNumber) && $value[$name] < $minNumber) {
172
                        $value[$name] = $moduleResolver->getSettingsDefaultValue($module, $option);
173
                    }
174
                }
175
176
                // Validate it is a valid value, or reset
177
                if (!$moduleResolver->isValidValue($module, $option, $value[$name])) {
178
                    $value[$name] = $moduleResolver->getSettingsDefaultValue($module, $option);
179
                }
180
            }
181
        }
182
        return $value;
183
    }
184
185
    /**
186
     * Return all the modules with settings
187
     *
188
     * @return array<array> Each item is an array of prop => value
189
     */
190
    protected function getAllItems(): array
191
    {
192
        $items = [];
193
        $moduleRegistry = ModuleRegistryFacade::getInstance();
194
        $modules = $moduleRegistry->getAllModules(true, true, false);
195
        foreach ($modules as $module) {
196
            $moduleResolver = $moduleRegistry->getModuleResolver($module);
197
            $items[] = [
198
                'module' => $module,
199
                'id' => $moduleResolver->getID($module),
200
                'name' => $moduleResolver->getName($module),
201
                'settings' => $moduleResolver->getSettings($module),
202
            ];
203
        }
204
        return $items;
205
    }
206
207
    protected function getSettingsFieldForModule(string $moduleID): string
208
    {
209
        return self::SETTINGS_FIELD . '-' . $moduleID;
210
    }
211
212
    /**
213
     * If `true`, print the sections using tabs
214
     * If `false`, print the sections one below the other
215
     *
216
     * @return boolean
217
     */
218
    protected function printWithTabs(): bool
219
    {
220
        return true;
221
    }
222
223
    /**
224
     * Print the settings form
225
     *
226
     * @return void
227
     */
228
    public function print(): void
229
    {
230
        $items = $this->getAllItems();
231
        if (!$items) {
232
            _e('There are no items to be configured', 'graphql-api');
233
            return;
234
        }
235
236
        $printWithTabs = $this->printWithTabs();
237
        // By default, focus on the first module
238
        $activeModuleID = $items[0]['id'];
239
        // If passing a tab, focus on that one, if the module exists
240
        if (isset($_GET[RequestParams::TAB])) {
241
            $tab = $_GET[RequestParams::TAB];
242
            $moduleIDs = array_map(
243
                fn ($item) => $item['id'],
244
                $items
245
            );
246
            if (in_array($tab, $moduleIDs)) {
247
                $activeModuleID = $tab;
248
            }
249
        }
250
        $class = 'wrap';
251
        if ($printWithTabs) {
252
            $class .= ' graphql-api-tabpanel';
253
        }
254
        ?>
255
        <div
256
            id="graphql-api-settings"
257
            class="<?php echo $class ?>"
258
        >
259
            <h1><?php \_e('GraphQL API — Settings', 'graphql-api'); ?></h1>
260
            <?php \settings_errors(); ?>
261
262
            <?php if ($printWithTabs) : ?>
263
                <!-- Tabs -->
264
                <h2 class="nav-tab-wrapper">
265
                    <?php
266
                    foreach ($items as $item) {
267
                        printf(
268
                            '<a href="#%s" class="nav-tab %s">%s</a>',
269
                            $item['id'],
270
                            $item['id'] == $activeModuleID ? 'nav-tab-active' : '',
271
                            $item['name']
272
                        );
273
                    }
274
                    ?>
275
                </h2>
276
            <?php endif; ?>
277
278
            <form method="post" action="options.php">
279
                <!-- Artificial input as flag that the form belongs to this plugin -->
280
                <input type="hidden" name="<?php echo self::FORM_ORIGIN ?>" value="<?php echo self::SETTINGS_FIELD ?>" />
281
                <!-- Panels -->
282
                <?php
283
                $sectionClass = $printWithTabs ? 'tab-content' : '';
284
                \settings_fields(self::SETTINGS_FIELD);
285
                foreach ($items as $item) {
286
                    $sectionStyle = '';
287
                    $maybeTitle = $printWithTabs ? '' : sprintf(
288
                        '<hr/><h3>%s</h3>',
289
                        $item['name']
290
                    );
291
                    if ($printWithTabs) {
292
                        $sectionStyle = sprintf(
293
                            'display: %s;',
294
                            $item['id'] == $activeModuleID ? 'block' : 'none'
295
                        );
296
                    }
297
                    ?>
298
                    <div id="<?php echo $item['id'] ?>" class="<?php echo $sectionClass ?>" style="<?php echo $sectionStyle ?>">
299
                        <?php echo $maybeTitle ?>
300
                        <table class="form-table">
301
                            <?php \do_settings_fields(self::SETTINGS_FIELD, $this->getSettingsFieldForModule($item['id'])) ?>
302
                        </table>
303
                    </div>
304
                    <?php
305
                }
306
                \submit_button();
307
                ?>
308
            </form>
309
        </div>
310
        <?php
311
    }
312
313
    /**
314
     * Enqueue the required assets and initialize the localized scripts
315
     *
316
     * @return void
317
     */
318
    protected function enqueueAssets(): void
319
    {
320
        parent::enqueueAssets();
321
322
        if ($this->printWithTabs()) {
323
            $this->enqueueTabpanelAssets();
324
        }
325
    }
326
327
    /**
328
     * Get the option value
329
     *
330
     * @return mixed
331
     */
332
    protected function getOptionValue(string $module, string $option)
333
    {
334
        $userSettingsManager = UserSettingsManagerFacade::getInstance();
335
        return $userSettingsManager->getSetting($module, $option);
336
    }
337
338
    /**
339
     * Display a checkbox field.
340
     *
341
     * @param array<string, mixed> $itemSetting
342
     */
343
    protected function printCheckboxField(string $module, array $itemSetting): void
344
    {
345
        $name = $itemSetting[Properties::NAME];
346
        $input = $itemSetting[Properties::INPUT];
347
        $value = $this->getOptionValue($module, $input);
348
        ?>
349
            <label for="<?php echo $name; ?>">
350
                <input type="checkbox" name="<?php echo self::SETTINGS_FIELD . '[' . $name . ']'; ?>" id="<?php echo $name; ?>" value="1" <?php checked(1, $value); ?> />
351
                <?php echo $itemSetting[Properties::DESCRIPTION] ?? ''; ?>
352
            </label>
353
        <?php
354
    }
355
356
    /**
357
     * Display an input field.
358
     *
359
     * @param array<string, mixed> $itemSetting
360
     */
361
    protected function printInputField(string $module, array $itemSetting): void
362
    {
363
        $name = $itemSetting[Properties::NAME];
364
        $input = $itemSetting[Properties::INPUT];
365
        $value = $this->getOptionValue($module, $input);
366
        $label = isset($itemSetting[Properties::DESCRIPTION]) ? '<br/>' . $itemSetting[Properties::DESCRIPTION] : '';
367
        $isNumber = isset($itemSetting[Properties::TYPE]) && $itemSetting[Properties::TYPE] == Properties::TYPE_INT;
368
        $minNumber = null;
369
        if ($isNumber) {
370
            $minNumber = $itemSetting[Properties::MIN_NUMBER] ?? null;
371
        }
372
        ?>
373
            <label for="<?php echo $name; ?>">
374
                <input name="<?php echo self::SETTINGS_FIELD . '[' . $name . ']'; ?>" id="<?php echo $name; ?>" value="<?php echo $value; ?>" <?php echo $isNumber ? ('type="number" step="1"' . (!is_null($minNumber) ? ' min="' . $minNumber .'"' : '')) : 'type="text"' ?>/>
375
                <?php echo $label; ?>
376
            </label>
377
        <?php
378
    }
379
380
    /**
381
     * Display a select field.
382
     *
383
     * @param array<string, mixed> $itemSetting
384
     */
385
    protected function printSelectField(string $module, array $itemSetting): void
386
    {
387
        $name = $itemSetting[Properties::NAME];
388
        $input = $itemSetting[Properties::INPUT];
389
        $value = $this->getOptionValue($module, $input);
390
        // If it is multiple, $value is an array.
391
        // To simplify, deal always with arrays
392
        if (!is_array($value)) {
393
            $value = is_null($value) ? [] : [$value];
394
        }
395
        $label = isset($itemSetting[Properties::DESCRIPTION]) ? '<br/>' . $itemSetting[Properties::DESCRIPTION] : '';
396
        $isMultiple = $itemSetting[Properties::IS_MULTIPLE] ?? false;
397
        $possibleValues = $itemSetting[Properties::POSSIBLE_VALUES] ?? [];
398
        ?>
399
            <label for="<?php echo $name; ?>">
400
                <select name="<?php echo self::SETTINGS_FIELD . '[' . $name . ']' . ($isMultiple ? '[]' : ''); ?>" id="<?php echo $name; ?>" <?php echo $isMultiple ? 'multiple="multiple"' : ''; ?>>
401
                <?php foreach ($possibleValues as $optionValue => $optionLabel) : ?>
402
                    <?php $maybeSelected = in_array($optionValue, $value) ? 'selected="selected"' : ''; ?>
403
                    <option value="<?php echo $optionValue ?>" <?php echo $maybeSelected ?>>
404
                        <?php echo $optionLabel ?>
405
                    </option>
406
                <?php endforeach ?>
407
                </select>
408
                <?php echo $label; ?>
409
            </label>
410
        <?php
411
    }
412
}
413