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