Completed
Pull Request — master (#229)
by Doğa
03:56 queued 01:05
created

OptionPages::get()   C

Complexity

Conditions 8
Paths 9

Size

Total Lines 40
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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