| @@ -82,9 +82,10 @@ | ||
| 82 | 82 | 'show_cart_icon_in_body' => 'yes', |
| 83 | 83 | 'additional_address_field' => 'yes', |
| 84 | 84 | 'hide_coupon_field' => 'no', |
| 85 | 85 | 'user_account_creation_mode' => 'all', |
| 86 | - 'auto_login_after_account_creation' => 'no', | |
| 86 | + 'auto_login_after_account_creation' => 'yes', | |
| 87 | + 'require_customer_email_verification' => 'no', | |
| 87 | 88 | 'checkout_page_id' => '', |
| 88 | 89 | 'custom_payment_page_id' => '', |
| 89 | 90 | 'registration_page_id' => '', |
| 90 | 91 | 'login_page_id' => '', |
| @@ -89,8 +90,9 @@ | ||
| 89 | 90 | 'registration_page_id' => '', |
| 90 | 91 | 'login_page_id' => '', |
| 91 | 92 | 'cart_page_id' => '', |
| 92 | 93 | 'receipt_page_id' => '', |
| 94 | + 'order_review_page_id' => '', | |
| 93 | 95 | 'shop_page_id' => '', |
| 94 | 96 | 'customer_profile_page_id' => '', |
| 95 | 97 | 'customer_profile_page_slug' => '', |
| 96 | 98 | 'currency' => 'USD', |
| @@ -101,8 +103,11 @@ | ||
| 101 | 103 | 'store_postcode' => '', |
| 102 | 104 | 'store_state' => '', |
| 103 | 105 | 'show_relevant_product_in_single_page' => 'yes', |
| 104 | 106 | 'show_relevant_product_in_modal' => '', |
| 107 | + 'show_reviews_in_single_page' => 'yes', | |
| 108 | + 'show_rating_in_shop' => 'yes', | |
| 109 | + 'show_rating_in_relevant' => 'yes', | |
| 105 | 110 | 'order_mode' => 'test', |
| 106 | 111 | 'subscription_mode_guard' => 'yes', |
| 107 | 112 | 'variation_view' => 'both', |
| 108 | 113 | 'variation_columns' => 'masonry', |
| @@ -143,9 +148,10 @@ | ||
| 143 | 148 | 'shop_page_id' => $this->getShopPage(), |
| 144 | 149 | 'customer_profile_page_id' => $this->getCustomerProfilePage(), |
| 145 | 150 | 'cart_page_id' => $this->getCartPage(), |
| 146 | 151 | 'checkout_page_id' => $this->getCheckoutPage(), |
| 147 | - 'receipt_page_id' => $this->getReceiptPage() | |
| 152 | + 'receipt_page_id' => $this->getReceiptPage(), | |
| 153 | + 'order_review_page_id' => $this->getOrderReviewPage() | |
| 148 | 154 | ]; |
| 149 | 155 | $isProActive = App::isProActive(); |
| 150 | 156 | $proFeatureIcon = Vite::getAssetUrl('images/crown.svg'); |
| 151 | 157 | |
| @@ -892,8 +898,45 @@ | ||
| 892 | 898 | ], |
| 893 | 899 | ] |
| 894 | 900 | ], |
| 895 | 901 | |
| 902 | + 'hr_order_review' => [ | |
| 903 | + 'type' => 'html', | |
| 904 | + 'value' => '<hr class="settings-divider">' | |
| 905 | + ], | |
| 906 | + | |
| 907 | + 'order_review_page_grid' => [ | |
| 908 | + 'type' => 'grid', | |
| 909 | + 'columns' => [ | |
| 910 | + 'default' => 1, | |
| 911 | + 'md' => 3 | |
| 912 | + ], | |
| 913 | + 'disable_nesting' => true, | |
| 914 | + 'schema' => [ | |
| 915 | + 'label' => [ | |
| 916 | + 'type' => 'html', | |
| 917 | + 'value' => '<span class="setting-label">' . __('Select Order Review Page', 'fluent-cart') . '</span> | |
| 918 | + <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>' | |
| 919 | + ], | |
| 920 | + 'order_review_page_id' => [ | |
| 921 | + 'wrapperClass' => 'col-span-2', | |
| 922 | + 'label' => false, | |
| 923 | + 'page_title' => __('Review Your Order', 'fluent-cart'), | |
| 924 | + 'type' => 'component', | |
| 925 | + 'component' => 'StoreSettings/PageSelector', | |
| 926 | + 'page_key' => 'order_review_page_id', | |
| 927 | + 'preview_link' => $previewLinks['order_review_page_id'], | |
| 928 | + 'hide_note' => true, | |
| 929 | + 'options' => $pages, | |
| 930 | + 'value' => '', | |
| 931 | + 'note' => \FluentCart\App\Helpers\Helper::getShortcodeInstructionString( | |
| 932 | + '[fluent_cart_order_review]', | |
| 933 | + __('Review Your Order', 'fluent-cart') | |
| 934 | + ), | |
| 935 | + ], | |
| 936 | + ] | |
| 937 | + ], | |
| 938 | + | |
| 896 | 939 | 'hr4' => [ |
| 897 | 940 | 'type' => 'html', |
| 898 | 941 | 'value' => '<hr class="settings-divider">' |
| 899 | 942 | ], |
| @@ -951,9 +994,9 @@ | ||
| 951 | 994 | 'schema' => [ |
| 952 | 995 | 'label' => [ |
| 953 | 996 | 'type' => 'html', |
| 954 | 997 | '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>' | |
| 998 | + <div class="form-note">' . __("Choose which sections to display on the single product page.", 'fluent-cart') . '</div>' | |
| 956 | 999 | ], |
| 957 | 1000 | 'fields' => [ |
| 958 | 1001 | 'type' => 'grid', |
| 959 | 1002 | 'columns' => [ |
| @@ -971,8 +1014,17 @@ | ||
| 971 | 1014 | "label" => __('Show Relevant In Product Modal', 'fluent-cart'), |
| 972 | 1015 | "type" => "checkbox", |
| 973 | 1016 | "value" => "no" |
| 974 | 1017 | ], |
| 1018 | + "show_reviews_in_single_page" => [ | |
| 1019 | + "label" => __('Show Reviews In Single Page', 'fluent-cart'), | |
| 1020 | + "type" => "checkbox", | |
| 1021 | + "value" => "yes" | |
| 1022 | + ], | |
| 1023 | + "show_reviews_note" => [ | |
| 1024 | + "type" => "html", | |
| 1025 | + "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>' | |
| 1026 | + ], | |
| 975 | 1027 | ] |
| 976 | 1028 | ] |
| 977 | 1029 | |
| 978 | 1030 | ] |
| @@ -1134,8 +1186,53 @@ | ||
| 1134 | 1186 | ], |
| 1135 | 1187 | |
| 1136 | 1188 | ] |
| 1137 | 1189 | ], |
| 1190 | + | |
| 1191 | + 'hr_product_slug' => [ | |
| 1192 | + 'type' => 'html', | |
| 1193 | + 'value' => '<hr class="settings-divider">' | |
| 1194 | + ], | |
| 1195 | + | |
| 1196 | + 'product_rating_grid' => [ | |
| 1197 | + 'type' => 'grid', | |
| 1198 | + 'columns' => [ | |
| 1199 | + 'default' => 1, | |
| 1200 | + 'md' => 3 | |
| 1201 | + ], | |
| 1202 | + 'disable_nesting' => true, | |
| 1203 | + 'schema' => [ | |
| 1204 | + 'label' => [ | |
| 1205 | + 'type' => 'html', | |
| 1206 | + 'value' => sprintf( | |
| 1207 | + '<span class="setting-label">%1$s</span><div class="form-note">%2$s</div>', | |
| 1208 | + __('Product Rating', 'fluent-cart'), | |
| 1209 | + __('Show star ratings when reviews are enabled.', 'fluent-cart') | |
| 1210 | + ) | |
| 1211 | + ], | |
| 1212 | + 'fields' => [ | |
| 1213 | + 'type' => 'grid', | |
| 1214 | + 'columns' => [ | |
| 1215 | + 'default' => 1, | |
| 1216 | + 'md' => 1 | |
| 1217 | + ], | |
| 1218 | + 'disable_nesting' => true, | |
| 1219 | + 'schema' => [ | |
| 1220 | + "show_rating_in_shop" => [ | |
| 1221 | + "label" => __('Show Rating in Shop', 'fluent-cart'), | |
| 1222 | + "type" => "checkbox", | |
| 1223 | + "value" => "yes" | |
| 1224 | + ], | |
| 1225 | + "show_rating_in_relevant" => [ | |
| 1226 | + "label" => __('Show Rating in Relevant Products', 'fluent-cart'), | |
| 1227 | + "type" => "checkbox", | |
| 1228 | + "value" => "yes" | |
| 1229 | + ], | |
| 1230 | + ] | |
| 1231 | + ] | |
| 1232 | + | |
| 1233 | + ] | |
| 1234 | + ], | |
| 1138 | 1235 | ], |
| 1139 | 1236 | ], |
| 1140 | 1237 | 'compliance' => [ |
| 1141 | 1238 | 'title' => __('Compliance', 'fluent-cart'), |
| @@ -1146,27 +1243,99 @@ | ||
| 1146 | 1243 | 'default' => 1, |
| 1147 | 1244 | 'md' => 1, |
| 1148 | 1245 | ], |
| 1149 | 1246 | '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'), | |
| 1247 | + 'email_verification_grid' => [ | |
| 1248 | + 'type' => 'grid', | |
| 1249 | + 'disable_nesting' => true, | |
| 1250 | + 'columns' => ['default' => 1, 'md' => 3], | |
| 1251 | + 'wrapperClass' => 'items-start', | |
| 1252 | + 'schema' => [ | |
| 1253 | + 'label' => [ | |
| 1254 | + 'type' => 'html', | |
| 1255 | + 'value' => sprintf( | |
| 1256 | + '<span class="setting-label">%1$s</span><div class="form-note">%2$s</div>', | |
| 1257 | + esc_html__('Customer email verification', 'fluent-cart'), | |
| 1258 | + esc_html__('Confirm email ownership before customers access their portal and saved checkout addresses.', 'fluent-cart') | |
| 1259 | + ), | |
| 1260 | + ], | |
| 1261 | + 'fields' => [ | |
| 1262 | + 'type' => 'grid', | |
| 1263 | + 'disable_nesting' => true, | |
| 1264 | + 'columns' => ['default' => 1], | |
| 1265 | + 'wrapperClass' => 'md:col-span-2', | |
| 1266 | + 'schema' => [ | |
| 1267 | + 'require_customer_email_verification' => [ | |
| 1268 | + 'wrapperClass' => 'fct-compliance-auto-login', | |
| 1269 | + 'label' => false, | |
| 1270 | + 'type' => 'radio', | |
| 1271 | + 'value' => 'no', | |
| 1272 | + 'attributes' => [ | |
| 1273 | + 'aria-label' => __('Customer email verification', 'fluent-cart'), | |
| 1274 | + ], | |
| 1275 | + 'options' => [ | |
| 1276 | + [ | |
| 1277 | + 'label' => __('Required', 'fluent-cart'), | |
| 1278 | + 'value' => 'yes', | |
| 1279 | + ], | |
| 1280 | + [ | |
| 1281 | + 'label' => __('Not required', 'fluent-cart'), | |
| 1282 | + 'value' => 'no', | |
| 1283 | + ], | |
| 1284 | + ], | |
| 1285 | + 'note' => __('When not required, new checkout accounts are linked to their purchases immediately.', 'fluent-cart'), | |
| 1286 | + ], | |
| 1287 | + ], | |
| 1288 | + ], | |
| 1157 | 1289 | ], |
| 1158 | - 'options' => [ | |
| 1159 | - [ | |
| 1160 | - 'label' => __("Don't auto login after account creation", 'fluent-cart'), | |
| 1161 | - 'value' => 'no', | |
| 1290 | + ], | |
| 1291 | + 'compliance_divider' => [ | |
| 1292 | + 'type' => 'html', | |
| 1293 | + 'value' => '<hr class="settings-divider">', | |
| 1294 | + ], | |
| 1295 | + 'account_login_grid' => [ | |
| 1296 | + 'type' => 'grid', | |
| 1297 | + 'disable_nesting' => true, | |
| 1298 | + 'columns' => ['default' => 1, 'md' => 3], | |
| 1299 | + 'wrapperClass' => 'items-start', | |
| 1300 | + 'schema' => [ | |
| 1301 | + 'label' => [ | |
| 1302 | + 'type' => 'html', | |
| 1303 | + 'value' => sprintf( | |
| 1304 | + '<span class="setting-label">%1$s</span><div class="form-note">%2$s</div>', | |
| 1305 | + esc_html__('Login after account creation', 'fluent-cart'), | |
| 1306 | + esc_html__('Choose how customers sign in after registering or completing checkout.', 'fluent-cart') | |
| 1307 | + ), | |
| 1162 | 1308 | ], |
| 1163 | - [ | |
| 1164 | - 'label' => __('Enable auto login after account creation', 'fluent-cart'), | |
| 1165 | - 'value' => 'yes', | |
| 1309 | + 'fields' => [ | |
| 1310 | + 'type' => 'grid', | |
| 1311 | + 'disable_nesting' => true, | |
| 1312 | + 'columns' => ['default' => 1], | |
| 1313 | + 'wrapperClass' => 'md:col-span-2', | |
| 1314 | + 'schema' => [ | |
| 1315 | + 'auto_login_after_account_creation' => [ | |
| 1316 | + 'wrapperClass' => 'fct-compliance-auto-login', | |
| 1317 | + 'label' => false, | |
| 1318 | + 'type' => 'radio', | |
| 1319 | + 'value' => 'yes', | |
| 1320 | + 'attributes' => [ | |
| 1321 | + 'aria-label' => __('Login after account creation', 'fluent-cart'), | |
| 1322 | + ], | |
| 1323 | + 'options' => [ | |
| 1324 | + [ | |
| 1325 | + 'label' => __('Log in automatically', 'fluent-cart'), | |
| 1326 | + 'value' => 'yes', | |
| 1327 | + ], | |
| 1328 | + [ | |
| 1329 | + 'label' => __('Let customers log in', 'fluent-cart'), | |
| 1330 | + 'value' => 'no', | |
| 1331 | + ], | |
| 1332 | + ], | |
| 1333 | + 'note' => __('Email verification still applies when required above.', 'fluent-cart'), | |
| 1334 | + ], | |
| 1335 | + ], | |
| 1166 | 1336 | ], |
| 1167 | 1337 | ], |
| 1168 | - 'note' => __('Choose whether customers are logged in automatically when FluentCart creates their account during registration or after checkout.', 'fluent-cart'), | |
| 1169 | 1338 | ], |
| 1170 | 1339 | ], |
| 1171 | 1340 | ], |
| 1172 | 1341 | 'cart_and_checkout' => [ |
| @@ -1542,8 +1711,17 @@ | ||
| 1542 | 1711 | ] |
| 1543 | 1712 | ]; |
| 1544 | 1713 | } |
| 1545 | 1714 | |
| 1715 | + // Product Rating toggles only apply while the reviews module is | |
| 1716 | + // active — hide the section (and its divider) otherwise. | |
| 1717 | + if (!ModuleSettings::isActive('reviews')) { | |
| 1718 | + unset( | |
| 1719 | + $fields['setting_tabs']['schema']['single_product_setup']['schema']['hr_product_slug'], | |
| 1720 | + $fields['setting_tabs']['schema']['single_product_setup']['schema']['product_rating_grid'] | |
| 1721 | + ); | |
| 1722 | + } | |
| 1723 | + | |
| 1546 | 1724 | return apply_filters("fluent_cart/store_settings/fields", $fields, []); |
| 1547 | 1725 | } |
| 1548 | 1726 | |
| 1549 | 1727 | public function isModuleTabEnabled(): bool |
| @@ -1657,8 +1835,9 @@ | ||
| 1657 | 1835 | 'registration_page_id', |
| 1658 | 1836 | 'login_page_id', |
| 1659 | 1837 | 'cart_page_id', |
| 1660 | 1838 | 'receipt_page_id', |
| 1839 | + 'order_review_page_id', | |
| 1661 | 1840 | 'shop_page_id', |
| 1662 | 1841 | 'customer_profile_page_id', |
| 1663 | 1842 | 'customer_profile_page_slug', |
| 1664 | 1843 | ] |
| @@ -1734,9 +1913,29 @@ | ||
| 1734 | 1913 | } |
| 1735 | 1914 | return home_url(); |
| 1736 | 1915 | } |
| 1737 | 1916 | |
| 1917 | + public function getOrderReviewPageId() | |
| 1918 | + { | |
| 1919 | + return Arr::get($this->storeSettings, 'order_review_page_id'); | |
| 1920 | + } | |
| 1738 | 1921 | |
| 1922 | + /** | |
| 1923 | + * The page hosting [fluent_cart_order_review], or home_url() when the store | |
| 1924 | + * has not picked one — Order::getOrderReviewUrl() then falls back to the | |
| 1925 | + * ?fluent-cart=order-review route, which needs no page at all. | |
| 1926 | + */ | |
| 1927 | + public function getOrderReviewPage(): string | |
| 1928 | + { | |
| 1929 | + if ($pageId = $this->getOrderReviewPageId()) { | |
| 1930 | + if (Pages::isPage($pageId)) { | |
| 1931 | + return $this->getPageLink($pageId); | |
| 1932 | + } | |
| 1933 | + } | |
| 1934 | + return home_url(); | |
| 1935 | + } | |
| 1936 | + | |
| 1937 | + | |
| 1739 | 1938 | public function getBuyButtonText(): string |
| 1740 | 1939 | { |
| 1741 | 1940 | return esc_html(Arr::get($this->storeSettings, 'checkout_button_text', __('Buy now', 'fluent-cart'))); |
| 1742 | 1941 | } |
| @@ -1905,13 +2104,9 @@ | ||
| 1905 | 2104 | [ |
| 1906 | 2105 | 'label' => __('Inherit from the active theme', 'fluent-cart'), |
| 1907 | 2106 | 'value' => ColorPalette::SOURCE_THEME, |
| 1908 | 2107 | '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 | - ), | |
| 2108 | + 'note' => __('FluentCart uses your active theme\'s colors for the storefront. If they don\'t come through as expected, choose Customize to set them yourself.', 'fluent-cart'), | |
| 1914 | 2109 | ], |
| 1915 | 2110 | [ |
| 1916 | 2111 | 'label' => __('Customize', 'fluent-cart'), |
| 1917 | 2112 | 'value' => ColorPalette::SOURCE_CUSTOM, |
| @@ -2050,10 +2245,13 @@ | ||
| 2050 | 2245 | if (!ThemePalette::hasUsableSource()) { |
| 2051 | 2246 | return __('The active theme publishes nothing to inherit, so the storefront keeps FluentCart\'s own colors.', 'fluent-cart'); |
| 2052 | 2247 | } |
| 2053 | 2248 | |
| 2249 | + // Only a colour with no hex anywhere is unpreviewable. A live | |
| 2250 | + // reference with a hex fallback — what the theme-settings readers and | |
| 2251 | + // Blocksy write — previews as that fallback. | |
| 2054 | 2252 | foreach (ThemePalette::resolve() as $value) { |
| 2055 | - if (!sanitize_hex_color((string)$value)) { | |
| 2253 | + if ((string)$value !== '' && ThemePalette::measurable((string)$value) === '') { | |
| 2056 | 2254 | 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 | 2255 | } |
| 2058 | 2256 | } |
| 2059 | 2257 | |
| @@ -2079,13 +2277,15 @@ | ||
| 2079 | 2277 | |
| 2080 | 2278 | $roles = []; |
| 2081 | 2279 | |
| 2082 | 2280 | 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); | |
| 2281 | + // A theme that publishes a bare `var(--x)` is written to the | |
| 2282 | + // storefront verbatim and resolved by the browser there, but that | |
| 2283 | + // property is not declared in wp-admin. Unpreviewable, so leave it | |
| 2284 | + // out and let the slot fall back to its own default. A reference | |
| 2285 | + // with a hex fallback (`var(--x, #hex)`) previews as that hex — | |
| 2286 | + // the same colour the storefront measures it by. | |
| 2287 | + $hex = ThemePalette::measurable((string)$value); | |
| 2088 | 2288 | |
| 2089 | 2289 | if ($hex) { |
| 2090 | 2290 | $roles[$role] = $hex; |
| 2091 | 2291 | } |