Completed
Push — baseComponentSliderMedia ( f2e84f...d2aad9 )
by Markus
11:22 queued 08:05
created

OptionPages::get()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 20
nc 7
nop 4
dl 0
loc 32
rs 8.439
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
    /**
91
     * Get option(s) from a sub page.
92
     *
93
     * Returns an option of a sub page. If no field name is provided it will get all option of that sub page.
94
     * Parameters are expected to be camelCase.
95
     *
96
     * @param string $optionType Type of option page. Either globalOptions or translatableOptions.
97
     * @param string $optionCategory Category of option page. One of these three values: component, feature, customPostType.
98
     * @param string $subPageName Name of the sub page.
99
     * @param string $fieldName (optional) Name of the field to get.
100
     * @return mixed The value of the option or array of options. False if subpage doesn't exist or no option was found.
101
     **/
102
    public static function get($optionType, $optionCategory, $subPageName, $fieldName = null)
103
    {
104
        $optionType = lcfirst($optionType);
105
106
        if (!isset(self::$optionTypes[$optionType])) {
107
            return false;
108
        }
109
110
        $optionCategory = ucfirst($optionCategory);
111
        $subPageName = ucfirst($subPageName);
112
113
        $prefix = implode('', [$optionType, $optionCategory, $subPageName, '_']);
114
        $options = self::getOptionFields(self::$optionTypes[$optionType]['translatable']);
115
116
        // find and replace relevant keys, then return an array of all options for this Sub-Page
117
        $optionKeys = is_array($options) ? array_keys($options) : [];
118
        $options = array_reduce($optionKeys, function ($carry, $key) use ($options, $prefix) {
119
            $count = 0;
120
            $option = $options[$key];
121
            $key = str_replace($prefix, '', $key, $count);
122
            if ($count > 0) {
123
                $carry[$key] = $option;
124
            }
125
            return $carry;
126
        }, []);
127
128
        if (isset($fieldName)) {
129
            $fieldName = lcfirst($fieldName);
130
            return array_key_exists($fieldName, $options) ? $options[$fieldName] : false;
131
        }
132
        return $options;
133
    }
134
135
    // ============
136
    // COMPONENTS
137
    // ============
138
139
    protected static function registerOptionCategoryComponent()
140
    {
141
        add_action(
142
            'Flynt/registerComponent',
143
            ['Flynt\Features\Acf\OptionPages', 'addComponentSubPage'],
144
            12
145
        );
146
147
        add_filter('Flynt/addComponentData', function ($data, $parentData, $config) {
148
149
            // get fields for this component
150
            $options = array_reduce(array_keys(self::$optionTypes), function ($carry, $optionType) use ($config) {
151
                return array_merge($carry, self::get($optionType, 'Component', $config['name']));
152
            }, []);
153
154
            // don't overwrite existing data
155
            return array_merge($options, $data);
156
        }, 10, 3);
157
    }
158
159
    public static function addComponentSubPage($componentName)
160
    {
161
        // load fields.json if it exists
162
        $componentManager = ComponentManager::getInstance();
163
        $filePath = $componentManager->getComponentFilePath($componentName, 'fields.json');
164
165
        if (false === $filePath) {
166
            return;
167
        }
168
169
        self::createSubPageFromConfig($filePath, 'component', $componentName);
170
    }
171
172
    // ==================
173
    // CUSTOM POST TYPES
174
    // ==================
175
176
    protected static function registerOptionCategoryCustomPostType()
177
    {
178
        add_action(
179
            'Flynt/Features/CustomPostTypes/Register',
180
            ['Flynt\Features\Acf\OptionPages', 'addCustomPostTypeSubPage'],
181
            10,
182
            2
183
        );
184
    }
185
186
    public static function addCustomPostTypeSubPage($name, $customPostType)
187
    {
188
        // load fields.json file
189
        $filePath = $customPostType['dir'] . '/fields.json';
190
191
        if (is_file($filePath)) {
192
            $name = StringHelpers::kebapCaseToCamelCase($name);
193
            self::createSubPageFromConfig($filePath, 'customPostType', $name);
194
        }
195
    }
196
197
    // ========
198
    // FEATURES
199
    // ========
200
201
    protected static function registerOptionCategoryFeature()
202
    {
203
        add_action(
204
            'Flynt/afterRegisterFeatures',
205
            ['Flynt\Features\Acf\OptionPages', 'addAllFeatureSubPages']
206
        );
207
    }
208
209
    public static function addAllFeatureSubPages()
210
    {
211
212
        foreach (Feature::getFeatures() as $featureName => $feature) {
213
            $filePath = $feature['dir'] . '/fields.json';
214
215
            if (!is_file($filePath)) {
216
                continue;
217
            }
218
219
            $featureName = StringHelpers::removePrefix('flynt', StringHelpers::kebapCaseToCamelCase($featureName));
220
221
            self::createSubPageFromConfig($filePath, 'feature', $featureName);
222
        }
223
    }
224
225
    // ========
226
    // GENERAL
227
    // ========
228
229
    protected static function createOptionPages()
230
    {
231
232
        foreach (self::$optionTypes as $optionType => $option) {
233
            $title = _x($option['title'], 'title', 'flynt-starter-theme');
234
            $slug = ucfirst($optionType);
235
236
            acf_add_options_page([
237
                'page_title'  => $title,
238
                'menu_title'  => $title,
239
                'redirect'    => true,
240
                'menu_slug'   => $slug,
241
                'icon_url'    => $option['icon']
242
            ]);
243
244
            self::$optionPages[$optionType] = [
245
                'menu_slug' => $slug,
246
                'menu_title' => $title
247
            ];
248
        }
249
250
        add_action('current_screen', function ($currentScreen) {
251
            foreach (self::$optionTypes as $optionType => $option) {
252
                $isTranslatable = $option['translatable'];
253
                $toplevelPageId = 'toplevel_page_' . $optionType;
254
                $menuTitle = self::$optionPages[$optionType]['menu_title'];
255
                $subPageId = sanitize_title($menuTitle) . '_page_' . $optionType;
256
                $isCurrentPage = StringHelpers::startsWith($toplevelPageId, $currentScreen->id)
257
                || StringHelpers::startsWith($subPageId, $currentScreen->id);
258
259
                if (!$isTranslatable && $isCurrentPage) {
260
                    // set acf field values to default language
261
                    add_filter('acf/settings/current_language', 'Flynt\Features\Acf\OptionPages::getDefaultAcfLanguage', 101);
262
263
                    // hide language selector in admin bar
264
                    add_action('wp_before_admin_bar_render', function () {
265
                        $adminBar = $GLOBALS['wp_admin_bar'];
266
                        $adminBar->remove_menu('WPML_ALS');
267
                    });
268
                }
269
            }
270
        });
271
    }
272
273
    protected static function createSubPageFromConfig($filePath, $optionCategoryName, $subPageName)
274
    {
275
        $fields = json_decode(file_get_contents($filePath), true);
276
277
        foreach (self::$optionTypes as $optionType => $option) {
278
            if (array_key_exists($optionType, $fields)) {
279
                self::addOptionSubPage(
280
                    $optionCategoryName,
281
                    ucfirst($subPageName),
282
                    $optionType,
283
                    $fields[$optionType]
284
                );
285
            }
286
        }
287
    }
288
289
    protected static function addOptionSubPage($optionCategoryName, $subPageName, $optionType, $fields)
290
    {
291
        $prettySubPageName = StringHelpers::splitCamelCase($subPageName);
292
        $optionCategorySettings = self::$optionCategories[$optionCategoryName];
293
        $iconClasses = 'flynt-submenu-item dashicons-before ' . $optionCategorySettings['icon'];
294
295
        $appendCategory = '';
296
        if (isset($optionCategorySettings['showType']) && true === $optionCategorySettings['showType']) {
297
            $appendCategory = ' (' . $optionCategorySettings['title'] . ')';
298
        }
299
300
        $subPageConfig = [
301
            'page_title'  => $prettySubPageName . $appendCategory,
302
            'menu_title'  => "<span class='{$iconClasses}'>{$prettySubPageName}</span>",
303
            'parent_slug' => self::$optionPages[$optionType]['menu_slug'],
304
            'menu_slug'   => $optionType . ucfirst($optionCategoryName) . $subPageName
305
        ];
306
307
        acf_add_options_sub_page($subPageConfig);
308
309
        self::addFieldGroupToSubPage(
310
            $subPageConfig['parent_slug'],
311
            $subPageConfig['menu_slug'],
312
            $subPageConfig['menu_title'],
313
            $fields
314
        );
315
    }
316
317
    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...
318
    {
319
        $fieldGroup = ACFComposer\ResolveConfig::forFieldGroup(
320
            [
321
                'name' => $menuSlug,
322
                'title' => $prettySubPageName,
323
                'fields' => $fields,
324
                'style' => 'seamless',
325
                'location' => [
326
                    [
327
                        [
328
                            'param' => 'options_page',
329
                            'operator' => '==',
330
                            'value' => $menuSlug
331
                        ]
332
                    ]
333
                ]
334
            ]
335
        );
336
        $fieldGroup['fields'] = self::prefixFields($fieldGroup['fields'], $menuSlug);
337
        acf_add_local_field_group($fieldGroup);
338
    }
339
340
    protected static function checkFeature($optionCategory, $feature)
341
    {
342
        if (array_key_exists($optionCategory, self::$optionCategories) && !Feature::isRegistered($feature)) {
343
            $title = self::$optionCategories[$optionCategory]['title'];
344
            $noticeManager = AdminNoticeManager::getInstance();
345
            $noticeManager->addNotice(
346
                [
347
                    "Could not add Option Pages for {$title} because the \"{$feature}\" feature is missing."
348
                ],
349
                [
350
                    'title' => 'Acf Option Pages Feature',
351
                    'dismissible' => true,
352
                    'type' => 'info'
353
                ]
354
            );
355
        }
356
    }
357
358
    protected static function prefixFields($fields, $prefix)
359
    {
360
        return array_map(function ($field) use ($prefix) {
361
            $field['name'] = $prefix . '_' . $field['name'];
362
            return $field;
363
        }, $fields);
364
    }
365
366
    protected static function getOptionFields($translatable)
367
    {
368
        global $sitepress;
369
370
        if (!isset($sitepress)) {
371
            $options = self::getCachedOptionFields();
372
        } else if ($translatable) {
373
            // get options from cache with language namespace
374
            $options = self::getCachedOptionFields(ICL_LANGUAGE_CODE);
375
        } else {
376
            // switch to default language to get global options
377
            $sitepress->switch_lang(acf_get_setting('default_language'));
378
379
            add_filter('acf/settings/current_language', 'Flynt\Features\Acf\OptionPages::getDefaultAcfLanguage', 100);
380
381
            // get optios from cache with global namespace
382
            $options = self::getCachedOptionFields('global');
383
384
            remove_filter('acf/settings/current_language', 'Flynt\Features\Acf\OptionPages::getDefaultAcfLanguage', 100);
385
386
            $sitepress->switch_lang(ICL_LANGUAGE_CODE);
387
        }
388
389
        return $options;
390
    }
391
392
    protected static function getCachedOptionFields($namespace = '')
393
    {
394
        // get cached options
395
        $found = false;
396
        $suffix = !empty($namespace) ? "_${namespace}" : '';
397
        $cacheKey = "Features/Acf/OptionPages/ID=options${suffix}";
398
399
        $options = wp_cache_get($cacheKey, 'flynt', null, $found);
400
401
        if (!$found) {
402
            $options = get_fields('options');
403
            wp_cache_set($cacheKey, $options, 'flynt');
404
        }
405
406
        return $options;
407
    }
408
409
    protected static function combineArrayDefaults(array $array, array $defaults)
410
    {
411
        return array_map(function ($value) use ($defaults) {
412
            return is_array($value) ? array_merge($defaults, $value) : [];
413
        }, $array);
414
    }
415
416
    public static function getDefaultAcfLanguage()
417
    {
418
        return acf_get_setting('default_language');
419
    }
420
}
421