Completed
Push — master ( b4b3ba...7cc19c )
by Doğa
04:27
created

OptionPages::createSubPage()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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