Completed
Push — BaseComponent-BlockCookieNotif... ( 4a8b09...828b6e )
by
unknown
07:23 queued 05:07
created

OptionPages::checkRequiredHooks()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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