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.

SettingsMenuPage::print()   C
last analyzed

Complexity

Conditions 13
Paths 121

Size

Total Lines 81
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 66
nc 121
nop 0
dl 0
loc 81
rs 5.8951
c 0
b 0
f 0

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