PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.1
1.7.1 1.7.0 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 All 51 releases
← All changes | api/StoreSettings.php +315 -35 1.6.5 → 1.7.1 View file →
@@ -8,8 +8,10 @@
8 8 use FluentCart\App\Helpers\AddressHelper;
9 9 use FluentCart\App\Helpers\CurrenciesHelper;
10 10 use FluentCart\App\Services\OrderService;
11 11 use FluentCart\App\Services\Theme\ColorPalette;
12 +use FluentCart\App\Services\Theme\FrontendTheme;
13 +use FluentCart\App\Services\Theme\RadiusPalette;
12 14 use FluentCart\App\Services\Theme\ThemePalette;
13 15 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
14 16 use FluentCart\App\Modules\StoreManagedRenewal\Services\RenewalService;
15 17 use FluentCart\App\Modules\Subscriptions\Services\SubscriptionManagementMode;
@@ -82,9 +84,10 @@
82 84 'show_cart_icon_in_body' => 'yes',
83 85 'additional_address_field' => 'yes',
84 86 'hide_coupon_field' => 'no',
85 87 'user_account_creation_mode' => 'all',
86 - 'auto_login_after_account_creation' => 'no',
88 + 'auto_login_after_account_creation' => 'yes',
89 + 'require_customer_email_verification' => 'no',
87 90 'checkout_page_id' => '',
88 91 'custom_payment_page_id' => '',
89 92 'registration_page_id' => '',
90 93 'login_page_id' => '',
@@ -89,8 +92,9 @@
89 92 'registration_page_id' => '',
90 93 'login_page_id' => '',
91 94 'cart_page_id' => '',
92 95 'receipt_page_id' => '',
96 + 'order_review_page_id' => '',
93 97 'shop_page_id' => '',
94 98 'customer_profile_page_id' => '',
95 99 'customer_profile_page_slug' => '',
96 100 'currency' => 'USD',
@@ -101,8 +105,11 @@
101 105 'store_postcode' => '',
102 106 'store_state' => '',
103 107 'show_relevant_product_in_single_page' => 'yes',
104 108 'show_relevant_product_in_modal' => '',
109 + 'show_reviews_in_single_page' => 'yes',
110 + 'show_rating_in_shop' => 'yes',
111 + 'show_rating_in_relevant' => 'yes',
105 112 'order_mode' => 'test',
106 113 'subscription_mode_guard' => 'yes',
107 114 'variation_view' => 'both',
108 115 'variation_columns' => 'masonry',
@@ -115,8 +122,9 @@
115 122 'weight_unit' => 'kg',
116 123 'dimension_unit' => 'cm',
117 124 'appearance_source' => ColorPalette::SOURCE_DEFAULT,
118 125 'appearance_colors' => [],
126 + 'appearance_radius' => [],
119 127 // 'wordpress', not 'fluent_cart': the FluentCart patterns are
120 128 // literals ('M j, Y'), and a literal renders a half-translated date
121 129 // on a localized store -- a German month in English field order,
122 130 // which a German reader misreads as day-first. Following
@@ -143,9 +151,10 @@
143 151 'shop_page_id' => $this->getShopPage(),
144 152 'customer_profile_page_id' => $this->getCustomerProfilePage(),
145 153 'cart_page_id' => $this->getCartPage(),
146 154 'checkout_page_id' => $this->getCheckoutPage(),
147 - 'receipt_page_id' => $this->getReceiptPage()
155 + 'receipt_page_id' => $this->getReceiptPage(),
156 + 'order_review_page_id' => $this->getOrderReviewPage()
148 157 ];
149 158 $isProActive = App::isProActive();
150 159 $proFeatureIcon = Vite::getAssetUrl('images/crown.svg');
151 160
@@ -892,8 +901,45 @@
892 901 ],
893 902 ]
894 903 ],
895 904
905 + 'hr_order_review' => [
906 + 'type' => 'html',
907 + 'value' => '<hr class="settings-divider">'
908 + ],
909 +
910 + 'order_review_page_grid' => [
911 + 'type' => 'grid',
912 + 'columns' => [
913 + 'default' => 1,
914 + 'md' => 3
915 + ],
916 + 'disable_nesting' => true,
917 + 'schema' => [
918 + 'label' => [
919 + 'type' => 'html',
920 + 'value' => '<span class="setting-label">' . __('Select Order Review Page', 'fluent-cart') . '</span>
921 + <div class="form-note">' . __("Select the page where customers will review the products they bought, from the link in their order email.", 'fluent-cart') . '</div>'
922 + ],
923 + 'order_review_page_id' => [
924 + 'wrapperClass' => 'col-span-2',
925 + 'label' => false,
926 + 'page_title' => __('Review Your Order', 'fluent-cart'),
927 + 'type' => 'component',
928 + 'component' => 'StoreSettings/PageSelector',
929 + 'page_key' => 'order_review_page_id',
930 + 'preview_link' => $previewLinks['order_review_page_id'],
931 + 'hide_note' => true,
932 + 'options' => $pages,
933 + 'value' => '',
934 + 'note' => \FluentCart\App\Helpers\Helper::getShortcodeInstructionString(
935 + '[fluent_cart_order_review]',
936 + __('Review Your Order', 'fluent-cart')
937 + ),
938 + ],
939 + ]
940 + ],
941 +
896 942 'hr4' => [
897 943 'type' => 'html',
898 944 'value' => '<hr class="settings-divider">'
899 945 ],
@@ -951,9 +997,9 @@
951 997 'schema' => [
952 998 'label' => [
953 999 'type' => 'html',
954 1000 'value' => '<span class="setting-label">' . __('Single Product Setup', 'fluent-cart') . '</span>
955 - <div class="form-note">' . __("Control the display of relevant information.", 'fluent-cart') . '</div>'
1001 + <div class="form-note">' . __("Choose which sections to display on the single product page.", 'fluent-cart') . '</div>'
956 1002 ],
957 1003 'fields' => [
958 1004 'type' => 'grid',
959 1005 'columns' => [
@@ -971,8 +1017,17 @@
971 1017 "label" => __('Show Relevant In Product Modal', 'fluent-cart'),
972 1018 "type" => "checkbox",
973 1019 "value" => "no"
974 1020 ],
1021 + "show_reviews_in_single_page" => [
1022 + "label" => __('Show Reviews In Single Page', 'fluent-cart'),
1023 + "type" => "checkbox",
1024 + "value" => "yes"
1025 + ],
1026 + "show_reviews_note" => [
1027 + "type" => "html",
1028 + "value" => '<div class="form-note">' . __('For block themes, add the Product Reviews block to your Single Product template to display reviews on your product page.', 'fluent-cart') . '</div>'
1029 + ],
975 1030 ]
976 1031 ]
977 1032
978 1033 ]
@@ -1134,8 +1189,53 @@
1134 1189 ],
1135 1190
1136 1191 ]
1137 1192 ],
1193 +
1194 + 'hr_product_slug' => [
1195 + 'type' => 'html',
1196 + 'value' => '<hr class="settings-divider">'
1197 + ],
1198 +
1199 + 'product_rating_grid' => [
1200 + 'type' => 'grid',
1201 + 'columns' => [
1202 + 'default' => 1,
1203 + 'md' => 3
1204 + ],
1205 + 'disable_nesting' => true,
1206 + 'schema' => [
1207 + 'label' => [
1208 + 'type' => 'html',
1209 + 'value' => sprintf(
1210 + '<span class="setting-label">%1$s</span><div class="form-note">%2$s</div>',
1211 + __('Product Rating', 'fluent-cart'),
1212 + __('Show star ratings when reviews are enabled.', 'fluent-cart')
1213 + )
1214 + ],
1215 + 'fields' => [
1216 + 'type' => 'grid',
1217 + 'columns' => [
1218 + 'default' => 1,
1219 + 'md' => 1
1220 + ],
1221 + 'disable_nesting' => true,
1222 + 'schema' => [
1223 + "show_rating_in_shop" => [
1224 + "label" => __('Show Rating in Shop', 'fluent-cart'),
1225 + "type" => "checkbox",
1226 + "value" => "yes"
1227 + ],
1228 + "show_rating_in_relevant" => [
1229 + "label" => __('Show Rating in Relevant Products', 'fluent-cart'),
1230 + "type" => "checkbox",
1231 + "value" => "yes"
1232 + ],
1233 + ]
1234 + ]
1235 +
1236 + ]
1237 + ],
1138 1238 ],
1139 1239 ],
1140 1240 'compliance' => [
1141 1241 'title' => __('Compliance', 'fluent-cart'),
@@ -1146,27 +1246,99 @@
1146 1246 'default' => 1,
1147 1247 'md' => 1,
1148 1248 ],
1149 1249 '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'),
1250 + 'email_verification_grid' => [
1251 + 'type' => 'grid',
1252 + 'disable_nesting' => true,
1253 + 'columns' => ['default' => 1, 'md' => 3],
1254 + 'wrapperClass' => 'items-start',
1255 + 'schema' => [
1256 + 'label' => [
1257 + 'type' => 'html',
1258 + 'value' => sprintf(
1259 + '<span class="setting-label">%1$s</span><div class="form-note">%2$s</div>',
1260 + esc_html__('Customer email verification', 'fluent-cart'),
1261 + esc_html__('Confirm email ownership before customers access their portal and saved checkout addresses.', 'fluent-cart')
1262 + ),
1263 + ],
1264 + 'fields' => [
1265 + 'type' => 'grid',
1266 + 'disable_nesting' => true,
1267 + 'columns' => ['default' => 1],
1268 + 'wrapperClass' => 'md:col-span-2',
1269 + 'schema' => [
1270 + 'require_customer_email_verification' => [
1271 + 'wrapperClass' => 'fct-compliance-auto-login',
1272 + 'label' => false,
1273 + 'type' => 'radio',
1274 + 'value' => 'no',
1275 + 'attributes' => [
1276 + 'aria-label' => __('Customer email verification', 'fluent-cart'),
1277 + ],
1278 + 'options' => [
1279 + [
1280 + 'label' => __('Required', 'fluent-cart'),
1281 + 'value' => 'yes',
1282 + ],
1283 + [
1284 + 'label' => __('Not required', 'fluent-cart'),
1285 + 'value' => 'no',
1286 + ],
1287 + ],
1288 + 'note' => __('When not required, new checkout accounts are linked to their purchases immediately.', 'fluent-cart'),
1289 + ],
1290 + ],
1291 + ],
1157 1292 ],
1158 - 'options' => [
1159 - [
1160 - 'label' => __("Don't auto login after account creation", 'fluent-cart'),
1161 - 'value' => 'no',
1293 + ],
1294 + 'compliance_divider' => [
1295 + 'type' => 'html',
1296 + 'value' => '<hr class="settings-divider">',
1297 + ],
1298 + 'account_login_grid' => [
1299 + 'type' => 'grid',
1300 + 'disable_nesting' => true,
1301 + 'columns' => ['default' => 1, 'md' => 3],
1302 + 'wrapperClass' => 'items-start',
1303 + 'schema' => [
1304 + 'label' => [
1305 + 'type' => 'html',
1306 + 'value' => sprintf(
1307 + '<span class="setting-label">%1$s</span><div class="form-note">%2$s</div>',
1308 + esc_html__('Login after account creation', 'fluent-cart'),
1309 + esc_html__('Choose how customers sign in after registering or completing checkout.', 'fluent-cart')
1310 + ),
1162 1311 ],
1163 - [
1164 - 'label' => __('Enable auto login after account creation', 'fluent-cart'),
1165 - 'value' => 'yes',
1312 + 'fields' => [
1313 + 'type' => 'grid',
1314 + 'disable_nesting' => true,
1315 + 'columns' => ['default' => 1],
1316 + 'wrapperClass' => 'md:col-span-2',
1317 + 'schema' => [
1318 + 'auto_login_after_account_creation' => [
1319 + 'wrapperClass' => 'fct-compliance-auto-login',
1320 + 'label' => false,
1321 + 'type' => 'radio',
1322 + 'value' => 'yes',
1323 + 'attributes' => [
1324 + 'aria-label' => __('Login after account creation', 'fluent-cart'),
1325 + ],
1326 + 'options' => [
1327 + [
1328 + 'label' => __('Log in automatically', 'fluent-cart'),
1329 + 'value' => 'yes',
1330 + ],
1331 + [
1332 + 'label' => __('Let customers log in', 'fluent-cart'),
1333 + 'value' => 'no',
1334 + ],
1335 + ],
1336 + 'note' => __('Email verification still applies when required above.', 'fluent-cart'),
1337 + ],
1338 + ],
1166 1339 ],
1167 1340 ],
1168 - 'note' => __('Choose whether customers are logged in automatically when FluentCart creates their account during registration or after checkout.', 'fluent-cart'),
1169 1341 ],
1170 1342 ],
1171 1343 ],
1172 1344 'cart_and_checkout' => [
@@ -1542,8 +1714,25 @@
1542 1714 ]
1543 1715 ];
1544 1716 }
1545 1717
1718 + // Review settings only apply while the reviews module is active —
1719 + // hide them (and their dividers) otherwise: the Product Rating
1720 + // section, the "Show Reviews In Single Page" toggle with its note,
1721 + // and the Order Review page selector. The stored values survive:
1722 + // save() merges the submitted form over what is stored, so a field
1723 + // left off the form keeps its value for when the module returns.
1724 + if (!ModuleSettings::isActive('reviews')) {
1725 + unset(
1726 + $fields['setting_tabs']['schema']['single_product_setup']['schema']['hr_product_slug'],
1727 + $fields['setting_tabs']['schema']['single_product_setup']['schema']['product_rating_grid'],
1728 + $fields['setting_tabs']['schema']['single_product_setup']['schema']['product_settings_grid']['schema']['fields']['schema']['show_reviews_in_single_page'],
1729 + $fields['setting_tabs']['schema']['single_product_setup']['schema']['product_settings_grid']['schema']['fields']['schema']['show_reviews_note'],
1730 + $fields['setting_tabs']['schema']['pages_setup']['schema']['hr_order_review'],
1731 + $fields['setting_tabs']['schema']['pages_setup']['schema']['order_review_page_grid']
1732 + );
1733 + }
1734 +
1546 1735 return apply_filters("fluent_cart/store_settings/fields", $fields, []);
1547 1736 }
1548 1737
1549 1738 public function isModuleTabEnabled(): bool
@@ -1573,8 +1762,28 @@
1573 1762
1574 1763 return empty($data) ? $default : $data;
1575 1764 }
1576 1765
1766 + /**
1767 + * All store settings, as the settings form should edit them.
1768 + *
1769 + * A stored radius the grammar refuses (hand-edited, or written by an older
1770 + * build) never printed on the storefront; dropped here too, so the form
1771 + * never holds a value it would refuse to save.
1772 + *
1773 + * @return array
1774 + */
1775 + public function getForEditing(): array
1776 + {
1777 + $settings = (array)$this->get();
1778 +
1779 + if (array_key_exists('appearance_radius', $settings)) {
1780 + $settings['appearance_radius'] = RadiusPalette::sanitizeOwnerMap($settings['appearance_radius']);
1781 + }
1782 +
1783 + return $settings;
1784 + }
1785 +
1577 1786 public function moduleSettings(): array
1578 1787 {
1579 1788 $settings = $this->get('modules_settings', []);
1580 1789 return Arr::wrap($settings);
@@ -1657,8 +1866,9 @@
1657 1866 'registration_page_id',
1658 1867 'login_page_id',
1659 1868 'cart_page_id',
1660 1869 'receipt_page_id',
1870 + 'order_review_page_id',
1661 1871 'shop_page_id',
1662 1872 'customer_profile_page_id',
1663 1873 'customer_profile_page_slug',
1664 1874 ]
@@ -1734,9 +1944,29 @@
1734 1944 }
1735 1945 return home_url();
1736 1946 }
1737 1947
1948 + public function getOrderReviewPageId()
1949 + {
1950 + return Arr::get($this->storeSettings, 'order_review_page_id');
1951 + }
1738 1952
1953 + /**
1954 + * The page hosting [fluent_cart_order_review], or home_url() when the store
1955 + * has not picked one — Order::getOrderReviewUrl() then falls back to the
1956 + * ?fluent-cart=order-review route, which needs no page at all.
1957 + */
1958 + public function getOrderReviewPage(): string
1959 + {
1960 + if ($pageId = $this->getOrderReviewPageId()) {
1961 + if (Pages::isPage($pageId)) {
1962 + return $this->getPageLink($pageId);
1963 + }
1964 + }
1965 + return home_url();
1966 + }
1967 +
1968 +
1739 1969 public function getBuyButtonText(): string
1740 1970 {
1741 1971 return esc_html(Arr::get($this->storeSettings, 'checkout_button_text', __('Buy now', 'fluent-cart')));
1742 1972 }
@@ -1896,34 +2126,30 @@
1896 2126 // Plain text — the component renders these as text nodes, so no markup
1897 2127 // and no escaping here or the entities would show up verbatim.
1898 2128 $sourceOptions = [
1899 2129 [
1900 - 'label' => __("FluentCart's own colors", 'fluent-cart'),
2130 + 'label' => __("FluentCart's own style", 'fluent-cart'),
1901 2131 'value' => ColorPalette::SOURCE_DEFAULT,
1902 2132 'icon' => 'PaletteLine',
1903 - 'note' => __('The storefront keeps the colors it ships with.', 'fluent-cart'),
2133 + 'note' => __('The storefront keeps the colors and corner rounding it ships with.', 'fluent-cart'),
1904 2134 ],
1905 2135 [
1906 2136 'label' => __('Inherit from the active theme', 'fluent-cart'),
1907 2137 'value' => ColorPalette::SOURCE_THEME,
1908 2138 '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 - ),
2139 + 'note' => __('FluentCart uses your active theme\'s colors, and its button and form field corner rounding, for the storefront. Product cards keep FluentCart\'s corners. If something doesn\'t come through as expected, choose Customize to set it yourself.', 'fluent-cart'),
1914 2140 ],
1915 2141 [
1916 2142 'label' => __('Customize', 'fluent-cart'),
1917 2143 'value' => ColorPalette::SOURCE_CUSTOM,
1918 2144 'icon' => 'PaletteLine',
1919 - 'note' => __('Pick the colors yourself. Only the ones you set are written to the storefront.', 'fluent-cart'),
2145 + 'note' => __('Pick the colors and corner rounding yourself. Only what you set is written to the storefront.', 'fluent-cart'),
1920 2146 ],
1921 2147 ];
1922 2148
1923 2149 $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'),
2150 + 'label' => __('Where colors and corner rounding come from', 'fluent-cart'),
2151 + 'note' => __('Storefront colors and corner rounding cascade from a small set of globals, so changing one here updates every page that uses it.', 'fluent-cart'),
1926 2152 ];
1927 2153
1928 2154 return [
1929 2155 'title' => __('Appearance', 'fluent-cart'),
@@ -1947,8 +2173,9 @@
1947 2173 'heading' => $sourceHeading,
1948 2174 'source_options' => $sourceOptions,
1949 2175 'custom_source' => ColorPalette::SOURCE_CUSTOM,
1950 2176 'color_groups' => $this->getAppearanceColorGroups(),
2177 + 'radius_section' => $this->getAppearanceRadiusSection(),
1951 2178 'preview' => $this->getAppearancePreviewData(),
1952 2179 ],
1953 2180 'appearance_source' => [
1954 2181 'type' => 'hidden',
@@ -1957,8 +2184,12 @@
1957 2184 'appearance_colors' => [
1958 2185 'type' => 'hidden',
1959 2186 'value' => (object)[],
1960 2187 ],
2188 + 'appearance_radius' => [
2189 + 'type' => 'hidden',
2190 + 'value' => (object)[],
2191 + ],
1961 2192 ],
1962 2193 ];
1963 2194 }
1964 2195
@@ -1983,8 +2214,9 @@
1983 2214 return [
1984 2215 'slots' => $this->getAppearancePreviewSlots(),
1985 2216 'theme_source' => ColorPalette::SOURCE_THEME,
1986 2217 'theme_roles' => $this->getAppearanceThemeRoles(),
2218 + 'theme_radii' => $this->getAppearanceThemeRadii(),
1987 2219 'notes' => [
1988 2220 ColorPalette::SOURCE_THEME => $this->getAppearanceThemeNote(),
1989 2221 ColorPalette::SOURCE_CUSTOM => __('Set only the colors you want to change — anything you leave unset keeps FluentCart\'s own default.', 'fluent-cart'),
1990 2222 ],
@@ -2047,13 +2279,16 @@
2047 2279 */
2048 2280 protected function getAppearanceThemeNote(): string
2049 2281 {
2050 2282 if (!ThemePalette::hasUsableSource()) {
2051 - return __('The active theme publishes nothing to inherit, so the storefront keeps FluentCart\'s own colors.', 'fluent-cart');
2283 + return __('The active theme publishes no colors to inherit, so the storefront keeps FluentCart\'s own colors.', 'fluent-cart');
2052 2284 }
2053 2285
2286 + // Only a colour with no hex anywhere is unpreviewable. A live
2287 + // reference with a hex fallback — what the theme-settings readers and
2288 + // Blocksy write — previews as that fallback.
2054 2289 foreach (ThemePalette::resolve() as $value) {
2055 - if (!sanitize_hex_color((string)$value)) {
2290 + if ((string)$value !== '' && ThemePalette::measurable((string)$value) === '') {
2056 2291 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 2292 }
2058 2293 }
2059 2294
@@ -2079,13 +2314,15 @@
2079 2314
2080 2315 $roles = [];
2081 2316
2082 2317 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);
2318 + // A theme that publishes a bare `var(--x)` is written to the
2319 + // storefront verbatim and resolved by the browser there, but that
2320 + // property is not declared in wp-admin. Unpreviewable, so leave it
2321 + // out and let the slot fall back to its own default. A reference
2322 + // with a hex fallback (`var(--x, #hex)`) previews as that hex —
2323 + // the same colour the storefront measures it by.
2324 + $hex = ThemePalette::measurable((string)$value);
2088 2325
2089 2326 if ($hex) {
2090 2327 $roles[$role] = $hex;
2091 2328 }
@@ -2094,8 +2331,21 @@
2094 2331 return $roles;
2095 2332 }
2096 2333
2097 2334 /**
2335 + * The theme's border radii for the preview, role => length.
2336 + *
2337 + * Exactly what inheriting would print, storefront filter included, so the
2338 + * preview never promises a radius the storefront won't write.
2339 + *
2340 + * @return array Role key => length (`8px`, `0.5rem`).
2341 + */
2342 + protected function getAppearanceThemeRadii(): array
2343 + {
2344 + return FrontendTheme::radiiFor(ColorPalette::SOURCE_THEME);
2345 + }
2346 +
2347 + /**
2098 2348 * The colour knobs, grouped the way the registry groups them, as data the
2099 2349 * appearance component renders its pickers from.
2100 2350 *
2101 2351 * @return array
@@ -2129,8 +2379,38 @@
2129 2379 ];
2130 2380 }
2131 2381
2132 2382 return $groups;
2383 + }
2384 +
2385 + /**
2386 + * The border-radius controls, as data the appearance component renders
2387 + * its number inputs from.
2388 + *
2389 + * Driven off RadiusPalette::roles(), the same registry the sanitiser and
2390 + * the storefront CSS read, so a control can never write a role the page
2391 + * would not print.
2392 + *
2393 + * @return array
2394 + */
2395 + protected function getAppearanceRadiusSection(): array
2396 + {
2397 + $fields = [];
2398 +
2399 + foreach (RadiusPalette::roles() as $key => $definition) {
2400 + $fields[] = [
2401 + 'key' => $key,
2402 + 'label' => (string)Arr::get($definition, 'label', $key),
2403 + 'note' => (string)Arr::get($definition, 'note', ''),
2404 + ];
2405 + }
2406 +
2407 + return [
2408 + 'label' => __('Border radius', 'fluent-cart'),
2409 + 'note' => __('A number is read as pixels, or add a unit: 8, 8px, 0.5rem, 1em. Leave a field empty to keep FluentCart\'s default.', 'fluent-cart'),
2410 + 'invalid' => __('Use a number, optionally with px, rem or em.', 'fluent-cart'),
2411 + 'fields' => $fields,
2412 + ];
2133 2413 }
2134 2414
2135 2415 /**
2136 2416 * The hint under one colour picker: what it drives, and the value it