Completed
Push — master ( 237036...650d4b )
by Dominik
02:47
created

OptionPages::createSubPageFromConfig()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 3
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
// TODO adjust readme
4
// TODO [minor] Overview Page + setting for redirect
5
// TODO [minor] add custom post type label
6
// TODO [minor] add notice for meta keys that are too long (unsolved ACF / WordPress issue)
7
// TODO [minor] remove / don't create empty (parent) option pages
8
9
namespace Flynt\Features\Acf;
10
11
use ACFComposer;
12
use Flynt\ComponentManager;
13
use Flynt\Features\AdminNotices\AdminNoticeManager;
14
use Flynt\Features\CustomPostTypes\CustomPostTypeRegister;
15
use Flynt\Utils\Feature;
16
use Flynt\Utils\FileLoader;
17
use Flynt\Utils\StringHelpers;
18
19
class OptionPages
20
{
21
    const FIELD_GROUPS_DIR = '/config/fieldGroups';
22
23
    const OPTION_TYPES = [
24
        'translatableOptions' => [
25
            'title' => 'Translatable Options',
26
            'icon' => 'dashicons-translation',
27
            'translatable' => true
28
        ],
29
        'globalOptions' => [
30
            'title' => 'Global Options',
31
            'icon' => 'dashicons-admin-site',
32
            'translatable' => false
33
        ]
34
    ];
35
36
    const OPTION_CATEGORIES = [
37
        'component' => [
38
            'title' => 'Component',
39
            'icon' => 'dashicons-editor-table',
40
            'showType' => true
41
        ],
42
        'customPostType' => [
43
            'title' => 'Custom Post Type',
44
            'icon' => 'dashicons-palmtree',
45
            'showType' => true
46
            // 'label' => [ 'labels', 'menu_item' ], // TODO add this functionality
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
47
        ],
48
        'feature' => [
49
            'title' => 'Feature',
50
            'icon' => 'dashicons-carrot',
51
            'showType' => true
52
        ]
53
    ];
54
55
    protected static $optionPages = [];
56
    protected static $optionTypes = [];
57
    protected static $optionCategories = [];
58
59
    public static function setup()
60
    {
61
62
        self::$optionTypes = self::OPTION_TYPES;
63
        self::$optionCategories = self::OPTION_CATEGORIES;
64
65
        self::createOptionPages();
66
67
        // Register Categories
68
        foreach (self::$optionCategories as $categoryName => $categorySettings) {
69
            $registerFn = 'registerOptionCategory' . ucfirst($categoryName);
70
            self::$registerFn();
71
        }
72
73
        // Setup Flynt Non Persistent Cache
74
        wp_cache_add_non_persistent_groups('flynt');
75
    }
76
77
    public static function init()
78
    {
79
        // show (dismissible) Admin Notice if required feature is missing
80
        if (class_exists('Flynt\Features\AdminNotices\AdminNoticeManager')) {
81
            self::checkFeature('customPostType', 'flynt-custom-post-types');
82
            self::checkFeature('component', 'flynt-components');
83
        }
84
    }
85
86
    // ============
87
    // PUBLIC API
88
    // ============
89
90
    // usage: OptionPages::getOptions('globalOptions', 'customPostType', 'myCustomPostTypeName');
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
91
    // usage: OptionPages::getOptions('globalOptions', 'feature', 'myFeatureName');
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
92
    // usage: OptionPages::getOptions('translatableOptions', 'component', 'myComponentName');
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
93
    // all params expected to be camelCase
94
    public static function getOptions($optionType, $optionCategory, $subPageName)
95
    {
96
        if (!isset(self::$optionTypes[$optionType])) {
97
            return [];
98
        }
99
100
        $prefix = implode('', [$optionType, ucfirst($optionCategory), ucfirst($subPageName), '_']);
101
        $options = self::getOptionFields(self::$optionTypes[$optionType]['translatable']);
102
103
        // find and replace relevant keys, then return an array of all options for this Sub-Page
104
        $optionKeys = is_array($options) ? array_keys($options) : [];
105
        return array_reduce($optionKeys, function ($carry, $key) use ($options, $prefix) {
106
            $count = 0;
107
            $option = $options[$key];
108
            $key = str_replace($prefix, '', $key, $count);
109
            if ($count > 0) {
110
                $carry[$key] = $option;
111
            }
112
            return $carry;
113
        }, []);
114
    }
115
116
    // usage: OptionPages::getOption('globalOptions', 'customPostType', 'myCustomPostTypeName', 'myFieldName');
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
117
    // usage: OptionPages::getOption('globalOptions', 'feature', 'myFeatureName', 'myFieldName');
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
118
    // usage: OptionPages::getOption('translatableOptions', 'component', 'myComponentName', 'myFieldName');
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
119
    // all params expected to be camelCase
120
    public static function getOption($optionType, $optionCategory, $subPageName, $fieldName)
121
    {
122
        $options = self::getOptions($optionType, $optionCategory, $subPageName);
123
        return array_key_exists($fieldName, $options) ? $options[$fieldName] : false;
124
    }
125
126
    // ============
127
    // COMPONENTS
128
    // ============
129
130
    protected static function registerOptionCategoryComponent()
131
    {
132
        add_action(
133
            'Flynt/registerComponent',
134
            ['Flynt\Features\Acf\OptionPages', 'addComponentSubPage'],
135
            12
136
        );
137
138
        add_filter('Flynt/addComponentData', function ($data, $parentData, $config) {
139
140
            // get fields for this component
141
            $options = array_reduce(array_keys(self::$optionTypes), function ($carry, $optionType) use ($config) {
142
                return array_merge($carry, self::getOptions($optionType, 'Component', $config['name']));
143
            }, []);
144
145
            // don't overwrite existing data
146
            return array_merge($options, $data);
147
        }, 10, 3);
148
    }
149
150
    public static function addComponentSubPage($componentName)
151
    {
152
        // load fields.json if it exists
153
        $componentManager = ComponentManager::getInstance();
154
        $filePath = $componentManager->getComponentFilePath($componentName, 'fields.json');
155
156
        if (false === $filePath) {
157
            return;
158
        }
159
160
        self::createSubPageFromConfig($filePath, 'component', $componentName);
161
    }
162
163
    // ==================
164
    // CUSTOM POST TYPES
165
    // ==================
166
167
    protected static function registerOptionCategoryCustomPostType()
168
    {
169
        add_action(
170
            'Flynt/Features/CustomPostTypes/Register',
171
            ['Flynt\Features\Acf\OptionPages', 'addCustomPostTypeSubPage'],
172
            10,
173
            2
174
        );
175
    }
176
177
    public static function addCustomPostTypeSubPage($name, $customPostType)
178
    {
179
        // load fields.json file
180
        $filePath = $customPostType['dir'] . '/fields.json';
181
182
        if (is_file($filePath)) {
183
            // TODO refactor
184
            // $cptName = ucfirst($cptDir->getFilename());
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
185
            // if (isset($cptConfig['label'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
79% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
186
            //   $label = $cptConfig['label'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
187
            // }
188
            // if (isset($cptConfig['labels'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
79% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
189
            //   if (isset($cptConfig['labels']['menu_name'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
83% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
190
            //     $label = $cptConfig['labels']['menu_name'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
191
            //   } else if (isset($cptConfig['labels']['singular_name'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
192
            //     $label = $cptConfig['labels']['singular_name'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
193
            //   }
194
            // }
195
            self::createSubPageFromConfig($filePath, 'customPostType', ucfirst($name));
196
        }
197
    }
198
199
    // ========
200
    // FEATURES
201
    // ========
202
203
    protected static function registerOptionCategoryFeature()
204
    {
205
        add_action(
206
            'Flynt/afterRegisterFeatures',
207
            ['Flynt\Features\Acf\OptionPages', 'addAllFeatureSubPages']
208
        );
209
    }
210
211
    public static function addAllFeatureSubPages()
212
    {
213
214
        foreach (Feature::getFeatures() as $featureName => $feature) {
215
            $filePath = $feature['dir'] . '/fields.json';
216
217
            if (!is_file($filePath)) {
218
                continue;
219
            }
220
221
            $featureName = StringHelpers::removePrefix('flynt', StringHelpers::kebapCaseToCamelCase($featureName));
222
223
            self::createSubPageFromConfig($filePath, 'feature', $featureName);
224
        }
225
    }
226
227
    // ========
228
    // GENERAL
229
    // ========
230
231
    protected static function createOptionPages()
232
    {
233
234
        foreach (self::$optionTypes as $optionType => $option) {
235
            $title = _x($option['title'], 'title', 'flynt-starter-theme');
236
            $slug = ucfirst($optionType);
237
238
            acf_add_options_page([
239
                'page_title'  => $title,
240
                'menu_title'  => $title,
241
                'redirect'    => true,
242
                'menu_slug'   => $slug,
243
                'icon_url'    => $option['icon']
244
            ]);
245
246
            self::$optionPages[$optionType] = [
247
                'menu_slug' => $slug,
248
                'menu_title' => $title
249
            ];
250
        }
251
252
        add_action('current_screen', function ($currentScreen) {
253
            foreach (self::$optionTypes as $optionType => $option) {
254
                $isTranslatable = $option['translatable'];
255
                $toplevelPageId = 'toplevel_page_' . $optionType;
256
                $menuTitle = self::$optionPages[$optionType]['menu_title'];
257
                $subPageId = sanitize_title($menuTitle) . '_page_' . $optionType;
258
                $isCurrentPage = StringHelpers::startsWith($toplevelPageId, $currentScreen->id)
259
                || StringHelpers::startsWith($subPageId, $currentScreen->id);
260
261
                if (!$isTranslatable && $isCurrentPage) {
262
                    // set acf field values to default language
263
                    add_filter('acf/settings/current_language', 'Flynt\Features\Acf\OptionPages::getDefaultAcfLanguage', 101);
264
265
                    // hide language selector in admin bar
266
                    add_action('wp_before_admin_bar_render', function () {
267
                        $adminBar = $GLOBALS['wp_admin_bar'];
268
                        $adminBar->remove_menu('WPML_ALS');
269
                    });
270
                }
271
            }
272
        });
273
    }
274
275
    protected static function createSubPageFromConfig($filePath, $optionCategoryName, $subPageName)
276
    {
277
        $fields = json_decode(file_get_contents($filePath), true);
278
279
        foreach (self::$optionTypes as $optionType => $option) {
280
            if (array_key_exists($optionType, $fields)) {
281
                self::addOptionSubPage(
282
                    $optionCategoryName,
283
                    ucfirst($subPageName),
284
                    $optionType,
285
                    $fields[$optionType]
286
                );
287
            }
288
        }
289
    }
290
291
    protected static function addOptionSubPage($optionCategoryName, $subPageName, $optionType, $fields)
292
    {
293
        $prettySubPageName = StringHelpers::splitCamelCase($subPageName);
294
        $optionCategorySettings = self::$optionCategories[$optionCategoryName];
295
        $iconClasses = 'flynt-submenu-item dashicons-before ' . $optionCategorySettings['icon'];
296
297
        $appendCategory = '';
298
        if (isset($optionCategorySettings['showType']) && true === $optionCategorySettings['showType']) {
299
            $appendCategory = ' (' . $optionCategorySettings['title'] . ')';
300
        }
301
302
        $subPageConfig = [
303
            'page_title'  => $prettySubPageName . $appendCategory,
304
            'menu_title'  => "<span class='{$iconClasses}'>{$prettySubPageName}</span>",
305
            'parent_slug' => self::$optionPages[$optionType]['menu_slug'],
306
            'menu_slug'   => $optionType . ucfirst($optionCategoryName) . $subPageName
307
        ];
308
309
        acf_add_options_sub_page($subPageConfig);
310
311
        self::addFieldGroupToSubPage(
312
            $subPageConfig['parent_slug'],
313
            $subPageConfig['menu_slug'],
314
            $subPageConfig['menu_title'],
315
            $fields
316
        );
317
    }
318
319
    protected static function addFieldGroupToSubPage($parentMenuSlug, $menuSlug, $prettySubPageName, $fields)
0 ignored issues
show
Unused Code introduced by
The parameter $parentMenuSlug is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
320
    {
321
        $fieldGroup = ACFComposer\ResolveConfig::forFieldGroup(
322
            [
323
                'name' => $menuSlug,
324
                'title' => $prettySubPageName,
325
                'fields' => $fields,
326
                'style' => 'seamless',
327
                'location' => [
328
                    [
329
                        [
330
                            'param' => 'options_page',
331
                            'operator' => '==',
332
                            'value' => $menuSlug
333
                        ]
334
                    ]
335
                ]
336
            ]
337
        );
338
        $fieldGroup['fields'] = self::prefixFields($fieldGroup['fields'], $menuSlug);
339
        acf_add_local_field_group($fieldGroup);
340
    }
341
342
    protected static function checkFeature($optionCategory, $feature)
343
    {
344
        if (array_key_exists($optionCategory, self::$optionCategories) && !Feature::isRegistered($feature)) {
345
            $title = self::$optionCategories[$optionCategory]['title'];
346
            $noticeManager = AdminNoticeManager::getInstance();
347
            $noticeManager->addNotice(
348
                [
349
                    "Could not add Option Pages for {$title} because the \"{$feature}\" feature is missing."
350
                ],
351
                [
352
                    'title' => 'Acf Option Pages Feature',
353
                    'dismissible' => true,
354
                    'type' => 'info'
355
                ]
356
            );
357
        }
358
    }
359
360
    protected static function prefixFields($fields, $prefix)
361
    {
362
        return array_map(function ($field) use ($prefix) {
363
            $field['name'] = $prefix . '_' . $field['name'];
364
            return $field;
365
        }, $fields);
366
    }
367
368
    protected static function getOptionFields($translatable)
369
    {
370
        global $sitepress;
371
372
        if (!isset($sitepress)) {
373
            $options = self::getCachedOptionFields();
374
        } else if ($translatable) {
375
            // get options from cache with language namespace
376
            $options = self::getCachedOptionFields(ICL_LANGUAGE_CODE);
377
        } else {
378
            // switch to default language to get global options
379
            $sitepress->switch_lang(acf_get_setting('default_language'));
380
381
            add_filter('acf/settings/current_language', 'Flynt\Features\Acf\OptionPages::getDefaultAcfLanguage', 100);
382
383
            // get optios from cache with global namespace
384
            $options = self::getCachedOptionFields('global');
385
386
            remove_filter('acf/settings/current_language', 'Flynt\Features\Acf\OptionPages::getDefaultAcfLanguage', 100);
387
388
            $sitepress->switch_lang(ICL_LANGUAGE_CODE);
389
        }
390
391
        return $options;
392
    }
393
394
    protected static function getCachedOptionFields($namespace = '')
395
    {
396
        // get cached options
397
        $found = false;
398
        $suffix = !empty($namespace) ? "_${namespace}" : '';
399
        $cacheKey = "Features/Acf/OptionPages/ID=options${suffix}";
400
401
        $options = wp_cache_get($cacheKey, 'flynt', null, $found);
402
403
        if (!$found) {
404
            $options = get_fields('options');
405
            wp_cache_set($cacheKey, $options, 'flynt');
406
        }
407
408
        return $options;
409
    }
410
411
    protected static function combineArrayDefaults(array $array, array $defaults)
412
    {
413
        return array_map(function ($value) use ($defaults) {
414
            return is_array($value) ? array_merge($defaults, $value) : [];
415
        }, $array);
416
    }
417
418
    public static function getDefaultAcfLanguage()
419
    {
420
        return acf_get_setting('default_language');
421
    }
422
}
423