| @@ -7,8 +7,10 @@ | ||
| 7 | 7 | use FluentCart\App\CPT\Pages; |
| 8 | 8 | use FluentCart\App\Helpers\AddressHelper; |
| 9 | 9 | use FluentCart\App\Helpers\CurrenciesHelper; |
| 10 | 10 | use FluentCart\App\Services\OrderService; |
| 11 | +use FluentCart\App\Services\Theme\ColorPalette; | |
| 12 | +use FluentCart\App\Services\Theme\ThemePalette; | |
| 11 | 13 | use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager; |
| 12 | 14 | use FluentCart\App\Modules\StoreManagedRenewal\Services\RenewalService; |
| 13 | 15 | use FluentCart\App\Modules\Subscriptions\Services\SubscriptionManagementMode; |
| 14 | 16 | use FluentCart\Framework\Support\Arr; |
| @@ -80,8 +82,9 @@ | ||
| 80 | 82 | 'show_cart_icon_in_body' => 'yes', |
| 81 | 83 | 'additional_address_field' => 'yes', |
| 82 | 84 | 'hide_coupon_field' => 'no', |
| 83 | 85 | 'user_account_creation_mode' => 'all', |
| 86 | + 'auto_login_after_account_creation' => 'no', | |
| 84 | 87 | 'checkout_page_id' => '', |
| 85 | 88 | 'custom_payment_page_id' => '', |
| 86 | 89 | 'registration_page_id' => '', |
| 87 | 90 | 'login_page_id' => '', |
| @@ -109,9 +112,19 @@ | ||
| 109 | 112 | 'modules_settings' => [], |
| 110 | 113 | 'min_receipt_number' => '1', |
| 111 | 114 | 'inv_prefix' => 'INV-', |
| 112 | 115 | 'weight_unit' => 'kg', |
| 113 | - 'dimension_unit' => 'cm' | |
| 116 | + 'dimension_unit' => 'cm', | |
| 117 | + 'appearance_source' => ColorPalette::SOURCE_DEFAULT, | |
| 118 | + 'appearance_colors' => [], | |
| 119 | + // 'wordpress', not 'fluent_cart': the FluentCart patterns are | |
| 120 | + // literals ('M j, Y'), and a literal renders a half-translated date | |
| 121 | + // on a localized store -- a German month in English field order, | |
| 122 | + // which a German reader misreads as day-first. Following | |
| 123 | + // Settings > General is the only source that is correct in every | |
| 124 | + // locale, so it is what a store gets until it chooses otherwise. | |
| 125 | + 'date_time_format_source' => 'wordpress', | |
| 126 | + 'timezone_source' => 'fluent_cart' | |
| 114 | 127 | ]; |
| 115 | 128 | |
| 116 | 129 | return apply_filters('fluent_cart/store_settings/values', $defaultSettings, []); |
| 117 | 130 | } |
| @@ -298,9 +311,9 @@ | ||
| 298 | 311 | ], |
| 299 | 312 | ] |
| 300 | 313 | ], |
| 301 | 314 | |
| 302 | - 'settings_hr' => [ | |
| 315 | + 'date_time_hr' => [ | |
| 303 | 316 | 'type' => 'html', |
| 304 | 317 | 'value' => '<hr class="settings-divider">' |
| 305 | 318 | ], |
| 306 | 319 | |
| @@ -566,8 +579,79 @@ | ||
| 566 | 579 | ], |
| 567 | 580 | ] |
| 568 | 581 | ], |
| 569 | 582 | |
| 583 | + 'settings_hr' => [ | |
| 584 | + 'type' => 'html', | |
| 585 | + 'value' => '<hr class="settings-divider">' | |
| 586 | + ], | |
| 587 | + | |
| 588 | + 'date_time_format_grid' => [ | |
| 589 | + 'type' => 'grid', | |
| 590 | + 'columns' => [ | |
| 591 | + 'default' => 1, | |
| 592 | + 'md' => 3 | |
| 593 | + ], | |
| 594 | + 'disable_nesting' => true, | |
| 595 | + 'schema' => [ | |
| 596 | + 'label' => [ | |
| 597 | + 'type' => 'html', | |
| 598 | + 'value' => '<span class="setting-label">' . __('Date & Time Format', 'fluent-cart') . '</span> | |
| 599 | + <div class="form-note">' . __("Which date and time format to display. `Smart` keeps FluentCart's own format; `WordPress` follows Settings → General.", 'fluent-cart') . '</div>' | |
| 600 | + ], | |
| 601 | + 'date_time_format_source' => [ | |
| 602 | + 'wrapperClass' => 'col-span-2 flex items-center', | |
| 603 | + 'label' => '', | |
| 604 | + 'type' => 'radio', | |
| 605 | + 'options' => [ | |
| 606 | + [ | |
| 607 | + // Label only -- the stored value stays 'fluent_cart'. | |
| 608 | + 'label' => __('Smart', 'fluent-cart'), | |
| 609 | + 'value' => 'fluent_cart', | |
| 610 | + ], | |
| 611 | + [ | |
| 612 | + 'label' => __('WordPress', 'fluent-cart'), | |
| 613 | + 'value' => 'wordpress', | |
| 614 | + ], | |
| 615 | + ], | |
| 616 | + 'value' => 'fluent_cart' | |
| 617 | + ], | |
| 618 | + ] | |
| 619 | + ], | |
| 620 | + | |
| 621 | + 'timezone_source_grid' => [ | |
| 622 | + 'type' => 'grid', | |
| 623 | + 'columns' => [ | |
| 624 | + 'default' => 1, | |
| 625 | + 'md' => 3 | |
| 626 | + ], | |
| 627 | + 'disable_nesting' => true, | |
| 628 | + 'schema' => [ | |
| 629 | + 'label' => [ | |
| 630 | + 'type' => 'html', | |
| 631 | + 'value' => '<span class="setting-label">' . __('Timezone', 'fluent-cart') . '</span> | |
| 632 | + <div class="form-note">' . __("Which timezone to display dates in. `Browser` shows admin and dashboard dates in the viewer's own timezone, and renders emails and invoices in the timezone captured at checkout; `WordPress` uses the site timezone from Settings → General.", 'fluent-cart') . '</div>' | |
| 633 | + ], | |
| 634 | + 'timezone_source' => [ | |
| 635 | + 'wrapperClass' => 'col-span-2 flex items-center', | |
| 636 | + 'label' => '', | |
| 637 | + 'type' => 'radio', | |
| 638 | + 'options' => [ | |
| 639 | + [ | |
| 640 | + // Label only -- the stored value stays 'fluent_cart'. | |
| 641 | + 'label' => __('Browser', 'fluent-cart'), | |
| 642 | + 'value' => 'fluent_cart', | |
| 643 | + ], | |
| 644 | + [ | |
| 645 | + 'label' => __('WordPress', 'fluent-cart'), | |
| 646 | + 'value' => 'wordpress', | |
| 647 | + ], | |
| 648 | + ], | |
| 649 | + 'value' => 'fluent_cart' | |
| 650 | + ], | |
| 651 | + ] | |
| 652 | + ], | |
| 653 | + | |
| 570 | 654 | // 'settings_hr_modal' => [ |
| 571 | 655 | // 'type' => 'html', |
| 572 | 656 | // 'value' => '<hr class="settings-divider">' |
| 573 | 657 | // ], |
| @@ -1052,8 +1136,40 @@ | ||
| 1052 | 1136 | ] |
| 1053 | 1137 | ], |
| 1054 | 1138 | ], |
| 1055 | 1139 | ], |
| 1140 | + 'compliance' => [ | |
| 1141 | + 'title' => __('Compliance', 'fluent-cart'), | |
| 1142 | + 'show_title' => false, | |
| 1143 | + 'type' => 'section', | |
| 1144 | + 'disable_nesting' => true, | |
| 1145 | + 'columns' => [ | |
| 1146 | + 'default' => 1, | |
| 1147 | + 'md' => 1, | |
| 1148 | + ], | |
| 1149 | + 'schema' => [ | |
| 1150 | + 'auto_login_after_account_creation' => [ | |
| 1151 | + 'wrapperClass' => 'fct-compliance-auto-login', | |
| 1152 | + 'label' => __('Login after account creation', 'fluent-cart'), | |
| 1153 | + 'type' => 'radio', | |
| 1154 | + 'value' => 'no', | |
| 1155 | + 'attributes' => [ | |
| 1156 | + 'aria-label' => __('Login after account creation', 'fluent-cart'), | |
| 1157 | + ], | |
| 1158 | + 'options' => [ | |
| 1159 | + [ | |
| 1160 | + 'label' => __("Don't auto login after account creation", 'fluent-cart'), | |
| 1161 | + 'value' => 'no', | |
| 1162 | + ], | |
| 1163 | + [ | |
| 1164 | + 'label' => __('Enable auto login after account creation', 'fluent-cart'), | |
| 1165 | + 'value' => 'yes', | |
| 1166 | + ], | |
| 1167 | + ], | |
| 1168 | + 'note' => __('Choose whether customers are logged in automatically when FluentCart creates their account during registration or after checkout.', 'fluent-cart'), | |
| 1169 | + ], | |
| 1170 | + ], | |
| 1171 | + ], | |
| 1056 | 1172 | 'cart_and_checkout' => [ |
| 1057 | 1173 | 'title' => __('Cart & checkout', 'fluent-cart'), |
| 1058 | 1174 | 'show_title' => false, |
| 1059 | 1175 | 'type' => 'section', |
| @@ -1361,8 +1477,9 @@ | ||
| 1361 | 1477 | ] |
| 1362 | 1478 | ], |
| 1363 | 1479 | ] |
| 1364 | 1480 | ], |
| 1481 | + 'appearance' => $this->getAppearanceSchema(), | |
| 1365 | 1482 | ] |
| 1366 | 1483 | ], |
| 1367 | 1484 | ]; |
| 1368 | 1485 | |
| @@ -1369,8 +1486,14 @@ | ||
| 1369 | 1486 | |
| 1370 | 1487 | // Only show weight/dimension unit fields to users with shipping-sensitive permission |
| 1371 | 1488 | if (PermissionManager::hasPermission(['store/sensitive'])) { |
| 1372 | 1489 | $storeSchema = &$fields['setting_tabs']['schema']['store_setup']['schema']; |
| 1490 | + | |
| 1491 | + $storeSchema['weight_unit_grid_divider'] = [ | |
| 1492 | + 'type' => 'html', | |
| 1493 | + 'value' => '<hr class="settings-divider">' | |
| 1494 | + ]; | |
| 1495 | + | |
| 1373 | 1496 | $storeSchema['weight_unit_grid'] = [ |
| 1374 | 1497 | 'type' => 'grid', |
| 1375 | 1498 | 'columns' => ['default' => 1, 'md' => 3], |
| 1376 | 1499 | 'disable_nesting' => true, |
| @@ -1511,8 +1634,11 @@ | ||
| 1511 | 1634 | |
| 1512 | 1635 | public function isCheckoutPage(): bool |
| 1513 | 1636 | { |
| 1514 | 1637 | global $post; |
| 1638 | + if (!$post instanceof \WP_Post) { | |
| 1639 | + return false; | |
| 1640 | + } | |
| 1515 | 1641 | $pageId = $this->getCheckoutPageId(); |
| 1516 | 1642 | return intval($pageId) === intval($post->ID); |
| 1517 | 1643 | } |
| 1518 | 1644 | |
| @@ -1753,8 +1879,283 @@ | ||
| 1753 | 1879 | return $slug; |
| 1754 | 1880 | } |
| 1755 | 1881 | |
| 1756 | 1882 | return trim(str_replace(home_url('/'), '', $url), '/'); |
| 1883 | + } | |
| 1884 | + | |
| 1885 | + /** | |
| 1886 | + * The Appearance settings tab. | |
| 1887 | + * | |
| 1888 | + * Built from the ColorPalette registry rather than written out by hand, so | |
| 1889 | + * a colour added to the registry becomes settable here, sanitised on save | |
| 1890 | + * and written to the storefront without three separate edits. | |
| 1891 | + * | |
| 1892 | + * @return array | |
| 1893 | + */ | |
| 1894 | + protected function getAppearanceSchema(): array | |
| 1895 | + { | |
| 1896 | + // Plain text — the component renders these as text nodes, so no markup | |
| 1897 | + // and no escaping here or the entities would show up verbatim. | |
| 1898 | + $sourceOptions = [ | |
| 1899 | + [ | |
| 1900 | + 'label' => __("FluentCart's own colors", 'fluent-cart'), | |
| 1901 | + 'value' => ColorPalette::SOURCE_DEFAULT, | |
| 1902 | + 'icon' => 'PaletteLine', | |
| 1903 | + 'note' => __('The storefront keeps the colors it ships with.', 'fluent-cart'), | |
| 1904 | + ], | |
| 1905 | + [ | |
| 1906 | + 'label' => __('Inherit from the active theme', 'fluent-cart'), | |
| 1907 | + 'value' => ColorPalette::SOURCE_THEME, | |
| 1908 | + 'icon' => 'PaintLine', | |
| 1909 | + 'note' => sprintf( | |
| 1910 | + '%1$s %2$s', | |
| 1911 | + __('The storefront palette is rebuilt from your theme, and follows it when the theme changes.', 'fluent-cart'), | |
| 1912 | + ThemePalette::sourceLabel() | |
| 1913 | + ), | |
| 1914 | + ], | |
| 1915 | + [ | |
| 1916 | + 'label' => __('Customize', 'fluent-cart'), | |
| 1917 | + 'value' => ColorPalette::SOURCE_CUSTOM, | |
| 1918 | + 'icon' => 'PaletteLine', | |
| 1919 | + 'note' => __('Pick the colors yourself. Only the ones you set are written to the storefront.', 'fluent-cart'), | |
| 1920 | + ], | |
| 1921 | + ]; | |
| 1922 | + | |
| 1923 | + $sourceHeading = [ | |
| 1924 | + 'label' => __('Where colors come from', 'fluent-cart'), | |
| 1925 | + 'note' => __('Storefront colors cascade from a small set of globals, so changing one here updates every page that uses it.', 'fluent-cart'), | |
| 1926 | + ]; | |
| 1927 | + | |
| 1928 | + return [ | |
| 1929 | + 'title' => __('Appearance', 'fluent-cart'), | |
| 1930 | + 'show_title' => false, | |
| 1931 | + 'type' => 'section', | |
| 1932 | + 'wrapperClass' => 'fct-appearance-component-section', | |
| 1933 | + 'disable_nesting' => true, | |
| 1934 | + 'columns' => [ | |
| 1935 | + 'default' => 1, | |
| 1936 | + 'md' => 1, | |
| 1937 | + ], | |
| 1938 | + 'schema' => [ | |
| 1939 | + 'appearance_component' => [ | |
| 1940 | + 'type' => 'component', | |
| 1941 | + 'component' => 'StoreSettings/AppearanceComponent', | |
| 1942 | + 'wrapperClass' => 'col-span-full', | |
| 1943 | + 'label' => false, | |
| 1944 | + // Rendered by the component itself, so the heading sits | |
| 1945 | + // inside .fct-appearance-component with the controls it | |
| 1946 | + // describes rather than as a sibling html field. | |
| 1947 | + 'heading' => $sourceHeading, | |
| 1948 | + 'source_options' => $sourceOptions, | |
| 1949 | + 'custom_source' => ColorPalette::SOURCE_CUSTOM, | |
| 1950 | + 'color_groups' => $this->getAppearanceColorGroups(), | |
| 1951 | + 'preview' => $this->getAppearancePreviewData(), | |
| 1952 | + ], | |
| 1953 | + 'appearance_source' => [ | |
| 1954 | + 'type' => 'hidden', | |
| 1955 | + 'value' => ColorPalette::SOURCE_DEFAULT, | |
| 1956 | + ], | |
| 1957 | + 'appearance_colors' => [ | |
| 1958 | + 'type' => 'hidden', | |
| 1959 | + 'value' => (object)[], | |
| 1960 | + ], | |
| 1961 | + ], | |
| 1962 | + ]; | |
| 1963 | + } | |
| 1964 | + | |
| 1965 | + /** | |
| 1966 | + * What the storefront preview needs to paint itself. | |
| 1967 | + * | |
| 1968 | + * The preview is drawn from semantic roles rather than settings keys, so | |
| 1969 | + * one mock card covers all three sources: `customize` reads the pickers the | |
| 1970 | + * owner has filled in, and the other two are resolved here because their | |
| 1971 | + * colors only exist server side — `inherit_from_theme` is mixed out of | |
| 1972 | + * theme.json by ThemePalette and nothing about it reaches the browser. | |
| 1973 | + * | |
| 1974 | + * @return array | |
| 1975 | + */ | |
| 1976 | + protected function getAppearancePreviewData(): array | |
| 1977 | + { | |
| 1978 | + // No colour is sent with a slot. appearance.scss already writes every | |
| 1979 | + // preview surface as `var(--fct-pv-x, <fallback>)`, so the mock has the | |
| 1980 | + // same single source of truth the storefront does: a slot the component | |
| 1981 | + // cannot resolve simply goes undeclared and the stylesheet's fallback | |
| 1982 | + // applies. | |
| 1983 | + return [ | |
| 1984 | + 'slots' => $this->getAppearancePreviewSlots(), | |
| 1985 | + 'theme_source' => ColorPalette::SOURCE_THEME, | |
| 1986 | + 'theme_roles' => $this->getAppearanceThemeRoles(), | |
| 1987 | + 'notes' => [ | |
| 1988 | + ColorPalette::SOURCE_THEME => $this->getAppearanceThemeNote(), | |
| 1989 | + ColorPalette::SOURCE_CUSTOM => __('Set only the colors you want to change — anything you leave unset keeps FluentCart\'s own default.', 'fluent-cart'), | |
| 1990 | + ], | |
| 1991 | + ]; | |
| 1992 | + } | |
| 1993 | + | |
| 1994 | + /** | |
| 1995 | + * Which custom property the preview paints each surface with. | |
| 1996 | + * | |
| 1997 | + * Keyed by settings key, not by role. Four separate globals claim the | |
| 1998 | + * `accent` role — active text, the brand background and both active | |
| 1999 | + * borders — so collapsing the preview to roles would let one picker drive | |
| 2000 | + * surfaces the storefront keeps apart, and leave the others with no visible | |
| 2001 | + * effect at all. `role` is carried only for theme inheritance, which | |
| 2002 | + * genuinely is role-based: FrontendTheme::getThemeColors() gives every key | |
| 2003 | + * sharing a role the same colour, so the preview collapsing there is the | |
| 2004 | + * storefront's own behaviour rather than a shortcut. | |
| 2005 | + * | |
| 2006 | + * A slot with no `key` is a surface no global controls; it follows the | |
| 2007 | + * role's default and cannot be edited. | |
| 2008 | + * | |
| 2009 | + * @return array | |
| 2010 | + */ | |
| 2011 | + protected function getAppearancePreviewSlots(): array | |
| 2012 | + { | |
| 2013 | + return [ | |
| 2014 | + ['var' => 'surface', 'role' => 'surface', 'key' => 'card_bg_color'], | |
| 2015 | + ['var' => 'surface-mute', 'role' => 'surface_mute'], | |
| 2016 | + ['var' => 'surface-alt', 'role' => 'surface_alt', 'key' => 'secondary_bg_color'], | |
| 2017 | + ['var' => 'text', 'role' => 'text', 'key' => 'primary_text_color'], | |
| 2018 | + ['var' => 'text-muted', 'role' => 'text_muted', 'key' => 'secondary_text_color'], | |
| 2019 | + ['var' => 'text-placeholder', 'role' => 'text_placeholder', 'key' => 'input_placeholder_text_color'], | |
| 2020 | + ['var' => 'input-bg', 'role' => 'surface', 'key' => 'input_bg_color'], | |
| 2021 | + ['var' => 'input-text', 'role' => 'text', 'key' => 'input_text_color'], | |
| 2022 | + ['var' => 'input-disabled-bg', 'role' => 'surface_mute', 'key' => 'input_disabled_bg_color'], | |
| 2023 | + ['var' => 'accent-text', 'role' => 'accent', 'key' => 'primary_active_text_color'], | |
| 2024 | + ['var' => 'active-border', 'role' => 'accent', 'key' => 'active_border_color'], | |
| 2025 | + ['var' => 'border', 'role' => 'border', 'key' => 'border_color'], | |
| 2026 | + ['var' => 'divider', 'role' => 'divider', 'key' => 'divider_color'], | |
| 2027 | + ['var' => 'button-bg', 'role' => 'button_bg', 'key' => 'btn_bg_color'], | |
| 2028 | + ['var' => 'button-text', 'role' => 'button_text', 'key' => 'btn_text_color'], | |
| 2029 | + ['var' => 'secondary-button-bg', 'role' => 'surface', 'key' => 'secondary_btn_bg_color'], | |
| 2030 | + ['var' => 'secondary-button-text', 'role' => 'text', 'key' => 'secondary_btn_text_color'], | |
| 2031 | + ['var' => 'secondary-button-border', 'role' => 'border', 'key' => 'secondary_btn_border_color'], | |
| 2032 | + ['var' => 'secondary-button-hover-bg', 'role' => 'surface_mute', 'key' => 'secondary_btn_hover_bg_color'], | |
| 2033 | + ]; | |
| 2034 | + } | |
| 2035 | + | |
| 2036 | + /** | |
| 2037 | + * Why the theme preview may not be the whole truth. | |
| 2038 | + * | |
| 2039 | + * Themes such as Astra publish their palette as `var(--ast-global-color-0)` | |
| 2040 | + * rather than as colors. FrontendTheme writes those references straight | |
| 2041 | + * through and the browser resolves them on the storefront, but the property | |
| 2042 | + * is not declared in wp-admin, so the preview cannot show them. Saying so | |
| 2043 | + * beats showing FluentCart's colors and letting the owner believe that is | |
| 2044 | + * what inheriting will look like. | |
| 2045 | + * | |
| 2046 | + * @return string Empty when the preview is faithful. | |
| 2047 | + */ | |
| 2048 | + protected function getAppearanceThemeNote(): string | |
| 2049 | + { | |
| 2050 | + if (!ThemePalette::hasUsableSource()) { | |
| 2051 | + return __('The active theme publishes nothing to inherit, so the storefront keeps FluentCart\'s own colors.', 'fluent-cart'); | |
| 2052 | + } | |
| 2053 | + | |
| 2054 | + foreach (ThemePalette::resolve() as $value) { | |
| 2055 | + if (!sanitize_hex_color((string)$value)) { | |
| 2056 | + return __('This theme publishes its palette as CSS variables. The storefront reads them, but they cannot be resolved here — the preview shows FluentCart\'s colors in their place.', 'fluent-cart'); | |
| 2057 | + } | |
| 2058 | + } | |
| 2059 | + | |
| 2060 | + return ''; | |
| 2061 | + } | |
| 2062 | + | |
| 2063 | + /** | |
| 2064 | + * The theme's colors for the preview, role by role. | |
| 2065 | + * | |
| 2066 | + * Only roles that resolved to a real color are returned — the preview | |
| 2067 | + * falls back to each slot's own default for the rest. | |
| 2068 | + * | |
| 2069 | + * @return array Role => hex. | |
| 2070 | + */ | |
| 2071 | + protected function getAppearanceThemeRoles(): array | |
| 2072 | + { | |
| 2073 | + // With nothing to inherit, FrontendTheme writes no declarations at all, | |
| 2074 | + // so the storefront keeps FluentCart's colors — and so must the | |
| 2075 | + // preview, or it would promise a change that never happens. | |
| 2076 | + if (!ThemePalette::hasUsableSource()) { | |
| 2077 | + return []; | |
| 2078 | + } | |
| 2079 | + | |
| 2080 | + $roles = []; | |
| 2081 | + | |
| 2082 | + foreach (ThemePalette::resolve() as $role => $value) { | |
| 2083 | + // A theme that publishes `var(--x)` is written to the storefront | |
| 2084 | + // verbatim and resolved by the browser there, but that property is | |
| 2085 | + // not declared in wp-admin. Unpreviewable, so leave it out and let | |
| 2086 | + // the slot fall back to its own default. | |
| 2087 | + $hex = sanitize_hex_color((string)$value); | |
| 2088 | + | |
| 2089 | + if ($hex) { | |
| 2090 | + $roles[$role] = $hex; | |
| 2091 | + } | |
| 2092 | + } | |
| 2093 | + | |
| 2094 | + return $roles; | |
| 2095 | + } | |
| 2096 | + | |
| 2097 | + /** | |
| 2098 | + * The colour knobs, grouped the way the registry groups them, as data the | |
| 2099 | + * appearance component renders its pickers from. | |
| 2100 | + * | |
| 2101 | + * @return array | |
| 2102 | + */ | |
| 2103 | + protected function getAppearanceColorGroups(): array | |
| 2104 | + { | |
| 2105 | + $groups = []; | |
| 2106 | + | |
| 2107 | + foreach (ColorPalette::groups() as $groupKey => $groupLabel) { | |
| 2108 | + $globals = ColorPalette::globalsFor($groupKey); | |
| 2109 | + | |
| 2110 | + if (!$globals) { | |
| 2111 | + continue; | |
| 2112 | + } | |
| 2113 | + | |
| 2114 | + $fields = []; | |
| 2115 | + | |
| 2116 | + foreach ($globals as $key => $definition) { | |
| 2117 | + $fields[] = [ | |
| 2118 | + 'key' => $key, | |
| 2119 | + 'label' => Arr::get($definition, 'label', $key), | |
| 2120 | + 'note' => $this->getAppearanceFieldNote($definition), | |
| 2121 | + 'default' => (string)Arr::get($definition, 'default', ''), | |
| 2122 | + ]; | |
| 2123 | + } | |
| 2124 | + | |
| 2125 | + $groups[] = [ | |
| 2126 | + 'key' => $groupKey, | |
| 2127 | + 'label' => $groupLabel, | |
| 2128 | + 'fields' => $fields, | |
| 2129 | + ]; | |
| 2130 | + } | |
| 2131 | + | |
| 2132 | + return $groups; | |
| 2133 | + } | |
| 2134 | + | |
| 2135 | + /** | |
| 2136 | + * The hint under one colour picker: what it drives, and the value it | |
| 2137 | + * falls back to when left empty. | |
| 2138 | + * | |
| 2139 | + * @param array $definition | |
| 2140 | + * @return string | |
| 2141 | + */ | |
| 2142 | + protected function getAppearanceFieldNote(array $definition): string | |
| 2143 | + { | |
| 2144 | + $usage = (string)Arr::get($definition, 'note', ''); | |
| 2145 | + $default = (string)Arr::get($definition, 'default', ''); | |
| 2146 | + | |
| 2147 | + if ($default === '') { | |
| 2148 | + return $usage; | |
| 2149 | + } | |
| 2150 | + | |
| 2151 | + if ($usage === '') { | |
| 2152 | + /* translators: %1$s: the hex colour FluentCart falls back to */ | |
| 2153 | + return sprintf(__('Default: %1$s', 'fluent-cart'), $default); | |
| 2154 | + } | |
| 2155 | + | |
| 2156 | + /* translators: 1: what the colour is used for, 2: the hex colour FluentCart falls back to */ | |
| 2157 | + return sprintf(__('%1$s Default: %2$s', 'fluent-cart'), $usage, $default); | |
| 1757 | 2158 | } |
| 1758 | 2159 | |
| 1759 | 2160 | /** |
| 1760 | 2161 | * Get theme colors as CSS variable string. |