| @@ -15,8 +15,9 @@ | ||
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | 17 | namespace ThemeAtelier\Darkify\Admin\Rest; |
| 18 | 18 | |
| 19 | +use ThemeAtelier\Darkify\Admin\Schema\SchemaDefaults; | |
| 19 | 20 | use ThemeAtelier\Darkify\Admin\Schema\SchemaRegistry; |
| 20 | 21 | |
| 21 | 22 | if (! defined('ABSPATH')) { |
| 22 | 23 | die; |
| @@ -279,8 +280,25 @@ | ||
| 279 | 280 | return $out; |
| 280 | 281 | } |
| 281 | 282 | |
| 282 | 283 | /** |
| 284 | + * Navigation sources that are stored as posts rather than `nav_menu` terms. | |
| 285 | + * | |
| 286 | + * Keyed by post type, valued by the short source label shown after the menu | |
| 287 | + * name in the picker. `kadence_navigation` is only registered when Kadence | |
| 288 | + * Blocks is active, which is why every caller checks post_type_exists() | |
| 289 | + * before querying — the list itself is deliberately static so it costs | |
| 290 | + * nothing to ask for. | |
| 291 | + */ | |
| 292 | + protected static function darkify_block_menu_sources() | |
| 293 | + { | |
| 294 | + return [ | |
| 295 | + 'wp_navigation' => \__('Block menu', 'darkify'), | |
| 296 | + 'kadence_navigation' => \__('Kadence', 'darkify'), | |
| 297 | + ]; | |
| 298 | + } | |
| 299 | + | |
| 300 | + /** | |
| 283 | 301 | * The read-only shortcode templates for the `switcher_shortcode` / |
| 284 | 302 | * `switcher_shortcode_v2` fields, byte-for-byte as the old framework's field |
| 285 | 303 | * renderers printed them. |
| 286 | 304 | */ |
| @@ -350,8 +368,50 @@ | ||
| 350 | 368 | case 'menus': { |
| 351 | 369 | foreach (\wp_get_nav_menus() as $menu) { |
| 352 | 370 | $out[(string) $menu->term_id] = $menu->name; |
| 353 | 371 | } |
| 372 | + | |
| 373 | + /* | |
| 374 | + * Block-theme and Kadence navigations are POSTS, not `nav_menu` | |
| 375 | + * terms, so wp_get_nav_menus() above cannot see them. On a block | |
| 376 | + * theme — or any site whose menus were built with Kadence | |
| 377 | + * Navigation — this field came back completely empty even though | |
| 378 | + * the site had menus, because there were no classic menus left to | |
| 379 | + * list. | |
| 380 | + * | |
| 381 | + * Their ids are POST ids and would collide with the term ids | |
| 382 | + * above (post 5 and term 5 are different menus), so they are | |
| 383 | + * namespaced `<post_type>:<id>`. Bare numeric keys stay reserved | |
| 384 | + * for classic menus, which is what keeps every already-saved | |
| 385 | + * `switch_in_menu_location` resolving to exactly the menu it | |
| 386 | + * always did. | |
| 387 | + */ | |
| 388 | + foreach (self::darkify_block_menu_sources() as $post_type => $label) { | |
| 389 | + if (! \post_type_exists($post_type)) { | |
| 390 | + continue; | |
| 391 | + } | |
| 392 | + | |
| 393 | + $navs = \get_posts([ | |
| 394 | + 'post_type' => $post_type, | |
| 395 | + 'post_status' => 'publish', | |
| 396 | + 'numberposts' => -1, | |
| 397 | + 'orderby' => 'title', | |
| 398 | + 'order' => 'ASC', | |
| 399 | + 'suppress_filters' => false, | |
| 400 | + ]); | |
| 401 | + | |
| 402 | + foreach (\is_array($navs) ? $navs : [] as $nav) { | |
| 403 | + $title = ($nav->post_title !== '') | |
| 404 | + ? $nav->post_title | |
| 405 | + /* translators: %d: navigation menu post ID. */ | |
| 406 | + : \sprintf(\__('(no title) #%d', 'darkify'), $nav->ID); | |
| 407 | + | |
| 408 | + // The source is shown because a site can legitimately | |
| 409 | + // have a classic menu, a block menu and a Kadence menu | |
| 410 | + // all called "Primary Menu". | |
| 411 | + $out[$post_type . ':' . $nav->ID] = $title . ' — ' . $label; | |
| 412 | + } | |
| 413 | + } | |
| 354 | 414 | break; |
| 355 | 415 | } |
| 356 | 416 | } |
| 357 | 417 | |
| @@ -411,66 +471,12 @@ | ||
| 411 | 471 | * save their fields flat, so those recurse to the top level unchanged. |
| 412 | 472 | */ |
| 413 | 473 | public function collect_defaults(array $sections): array |
| 414 | 474 | { |
| 415 | - $defaults = []; | |
| 416 | - foreach ($sections as $section) { | |
| 417 | - if (! empty($section['fields']) && \is_array($section['fields'])) { | |
| 418 | - $this->collect_field_defaults($section['fields'], $defaults); | |
| 419 | - } | |
| 420 | - } | |
| 421 | - return $defaults; | |
| 422 | - } | |
| 423 | - | |
| 424 | - /** | |
| 425 | - * Recursive worker for collect_defaults(). Writes into $out by reference, | |
| 426 | - * building a fieldset's default as the nested object of its own children. | |
| 427 | - * | |
| 428 | - * @param array $fields The fields list to walk. | |
| 429 | - * @param array $out Accumulator, keyed by field id. | |
| 430 | - */ | |
| 431 | - protected function collect_field_defaults(array $fields, array &$out): void | |
| 432 | - { | |
| 433 | - foreach ($fields as $field) { | |
| 434 | - if (! \is_array($field)) { | |
| 435 | - continue; | |
| 436 | - } | |
| 437 | - $id = $field['id'] ?? ''; | |
| 438 | - $type = $field['type'] ?? ''; | |
| 439 | - | |
| 440 | - // A fieldset's default is the nested object of its children's | |
| 441 | - // defaults, stored under the fieldset's own id (recurses, so a | |
| 442 | - // fieldset nested inside a fieldset nests correctly too). | |
| 443 | - if ( | |
| 444 | - $type === 'fieldset' | |
| 445 | - && $id !== '' | |
| 446 | - && ! empty($field['fields']) | |
| 447 | - && \is_array($field['fields']) | |
| 448 | - ) { | |
| 449 | - $nested = []; | |
| 450 | - $this->collect_field_defaults($field['fields'], $nested); | |
| 451 | - if (! empty($nested)) { | |
| 452 | - $out[$id] = $nested; | |
| 453 | - } | |
| 454 | - continue; | |
| 455 | - } | |
| 456 | - | |
| 457 | - // An ordinary field with a declared default (this also covers | |
| 458 | - // group/repeater, whose own `default` is a list of rows — we do NOT | |
| 459 | - // recurse into their per-row template fields). | |
| 460 | - if ($id !== '' && \array_key_exists('default', $field)) { | |
| 461 | - $out[$id] = $this->decode_labels($field['default']); | |
| 462 | - } | |
| 463 | - | |
| 464 | - // section_tab sub-tabs: their fields save flat at the top level. | |
| 465 | - if (! empty($field['tabs']) && \is_array($field['tabs'])) { | |
| 466 | - foreach ($field['tabs'] as $tab) { | |
| 467 | - if (! empty($tab['fields']) && \is_array($tab['fields'])) { | |
| 468 | - $this->collect_field_defaults($tab['fields'], $out); | |
| 469 | - } | |
| 470 | - } | |
| 471 | - } | |
| 472 | - } | |
| 475 | + // The walk itself lives in SchemaDefaults so the install seeder | |
| 476 | + // (Admin::seed_default_options) resolves defaults through exactly the | |
| 477 | + // same code path this controller does. | |
| 478 | + return SchemaDefaults::collect($sections); | |
| 473 | 479 | } |
| 474 | 480 | |
| 475 | 481 | /** |
| 476 | 482 | * Merge saved values over schema defaults. |