Completed
Push — fieldLoaderTest ( 57c9f6 )
by Wahiba
03:02
created

OptionPages::setup()   B

Complexity

Conditions 2
Paths 1

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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