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
|
|
|
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) |
|
|
|
|
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
|
|
|
|
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.