From b2ea2222a506897deb6c1fb1b86c8f81a54974bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:32:21 +0000 Subject: [PATCH] chore: sync funkycommerce-headless from monorepo Source: coded-letter/coded-letter-monorepo@f8ac2a236416b16346b5cc77699d2776ee504545 --- .monorepo-source.json | 2 +- assets/storefront-ui-strings/en.json | 6 +- assets/storefront-ui-strings/ja.json | 6 +- assets/storefront-ui-strings/pl.json | 6 +- build/netlify-settings.test.php | 71 +++++++++++++++++++++ build/tailwind-class-manifest.test.mjs | 4 +- functions.php | 4 +- inc/backend-preview.php | 12 ++++ inc/build-webhooks.php | 4 ++ inc/control-center-schema.php | 4 +- inc/control-center.php | 85 +++++++++++++++++++++++++- inc/crypto-payments.php | 2 +- inc/frontend-theme.php | 9 ++- package-lock.json | 4 +- package.json | 2 +- readme.txt | 10 ++- style.css | 2 +- 17 files changed, 211 insertions(+), 22 deletions(-) create mode 100644 build/netlify-settings.test.php diff --git a/.monorepo-source.json b/.monorepo-source.json index 07e9508..36f6037 100644 --- a/.monorepo-source.json +++ b/.monorepo-source.json @@ -1,7 +1,7 @@ { "productId": "funkycommerce-headless", "repository": "coded-letter/superfunky-theme", - "source": "coded-letter/coded-letter-monorepo@cdc6b7cfa7b626f79a4910b3b5624de02a64f691", + "source": "coded-letter/coded-letter-monorepo@f8ac2a236416b16346b5cc77699d2776ee504545", "sourcePath": "workspace/backend/wordpress/themes/free/funkycommerce-headless", "installSlug": "funkycommerce-headless", "kind": "theme", diff --git a/assets/storefront-ui-strings/en.json b/assets/storefront-ui-strings/en.json index e5bacc6..c653ec2 100644 --- a/assets/storefront-ui-strings/en.json +++ b/assets/storefront-ui-strings/en.json @@ -23,6 +23,7 @@ "cart.item_plural": "items", "cart.continue_checkout": "Continue to checkout", "cart.shipping": "Shipping", + "cart.free_shipping.notice": "{remaining} left to free shipping — {action}", "cart.tax": "Tax", "cart.digital_delivery": "Digital delivery", "cart.free": "Free", @@ -101,6 +102,7 @@ "checkout.payment.unavailable_digital": "Not available for digital orders", "checkout.payment.unavailable": "Not available at this time", "checkout.payment.ssl": "Transactions secured with SSL encryption", + "checkout.stripe.security_note": "Powered by Stripe — card details are never stored on our servers.", "checkout.payment.unavailable_backend": "Crypto checkout is not currently available on this backend.", "checkout.payment.method_unavailable": "The selected payment method is not currently available.", "checkout.summary.title": "Review order", @@ -406,7 +408,7 @@ "footer.assistant.tab": "AI shopping assistant", "footer.spotify.tab": "Spotify player", "footer.assistant_spotify.aria": "Assistant / Spotify", - "footer.radio.title": "Superfunky Radio", + "footer.radio.title": "{brand} Radio", "footer.radio.description": "Instrumental jazz-hop for browsing — swap for any track, album, or podcast link.", "footer.nav.expand": "Expand {label}", "footer.nav.collapse": "Collapse {label}", @@ -495,7 +497,7 @@ "filters.sort_title": "Title", "filters.tag_aria": "Filter by tag", "image.show_label": "Show {label}", - "newsletter.default_body": "Join our insider list for early access, private offers, and curated stories from the Superfunky world.", + "newsletter.default_body": "Join our insider list for early access, private offers, and curated stories from {brand}.", "newsletter.default_title": "Be the first to know when the next favorite drops.", "newsletter.eyebrow": "Stay in the loop", "newsletter.image_placeholder_body": "Drop in a full-bleed product or editorial image here.", diff --git a/assets/storefront-ui-strings/ja.json b/assets/storefront-ui-strings/ja.json index ca245f8..0ccd785 100644 --- a/assets/storefront-ui-strings/ja.json +++ b/assets/storefront-ui-strings/ja.json @@ -23,6 +23,7 @@ "cart.item_plural": "点", "cart.continue_checkout": "購入手続きに進む", "cart.shipping": "送料", + "cart.free_shipping.notice": "送料無料まであと{remaining} — {action}", "cart.tax": "税金", "cart.digital_delivery": "デジタル配信", "cart.free": "無料", @@ -101,6 +102,7 @@ "checkout.payment.unavailable_digital": "デジタル注文では利用できません", "checkout.payment.unavailable": "現在利用できません", "checkout.payment.ssl": "取引はSSL暗号化で保護されています", + "checkout.stripe.security_note": "Stripeによる決済 — カード情報は当社のサーバーに保存されません。", "checkout.payment.unavailable_backend": "このバックエンドでは現在、暗号資産決済を利用できません。", "checkout.payment.method_unavailable": "選択したお支払い方法は現在利用できません。", "checkout.summary.title": "注文内容の確認", @@ -406,7 +408,7 @@ "footer.assistant.tab": "AIショッピングアシスタント", "footer.spotify.tab": "Spotifyプレーヤー", "footer.assistant_spotify.aria": "アシスタント / Spotify", - "footer.radio.title": "Superfunky Radio", + "footer.radio.title": "{brand} Radio", "footer.radio.description": "ブラウジングに合うインストゥルメンタル・ジャズホップ。お好きな曲、アルバム、ポッドキャストに変更できます。", "footer.nav.expand": "{label}を展開", "footer.nav.collapse": "{label}を折りたたむ", @@ -495,7 +497,7 @@ "filters.sort_title": "タイトル", "filters.tag_aria": "タグで絞り込む", "image.show_label": "{label}を表示", - "newsletter.default_body": "インサイダーリストに登録すると、Superfunkyの世界からの先行アクセス、限定オファー、厳選ストーリーをお届けします。", + "newsletter.default_body": "{brand}から、先行アクセス、限定オファー、厳選ストーリーをお届けします。", "newsletter.default_title": "次のお気に入りが登場したら、いち早くお知らせします。", "newsletter.eyebrow": "最新情報をチェック", "newsletter.image_placeholder_body": "ここにフルブリードの商品画像や特集画像を配置します。", diff --git a/assets/storefront-ui-strings/pl.json b/assets/storefront-ui-strings/pl.json index fc57a4f..7ca81ee 100644 --- a/assets/storefront-ui-strings/pl.json +++ b/assets/storefront-ui-strings/pl.json @@ -23,6 +23,7 @@ "cart.item_plural": "produkty", "cart.continue_checkout": "Przejdź do kasy", "cart.shipping": "Dostawa", + "cart.free_shipping.notice": "Do darmowej dostawy brakuje {remaining} — {action}", "cart.tax": "Podatek", "cart.digital_delivery": "Dostawa cyfrowa", "cart.free": "Gratis", @@ -101,6 +102,7 @@ "checkout.payment.unavailable_digital": "Niedostępne dla zamówień cyfrowych", "checkout.payment.unavailable": "Niedostępne w tej chwili", "checkout.payment.ssl": "Transakcje zabezpieczone szyfrowaniem SSL", + "checkout.stripe.security_note": "Obsługiwane przez Stripe — dane karty nigdy nie są przechowywane na naszych serwerach.", "checkout.payment.unavailable_backend": "Płatności krypto są obecnie niedostępne.", "checkout.payment.method_unavailable": "Wybrana metoda płatności jest niedostępna.", "checkout.summary.title": "Podsumowanie zamówienia", @@ -406,7 +408,7 @@ "footer.assistant.tab": "Asystent zakupowy AI", "footer.spotify.tab": "Odtwarzacz Spotify", "footer.assistant_spotify.aria": "Asystent / Spotify", - "footer.radio.title": "Superfunky Radio", + "footer.radio.title": "{brand} Radio", "footer.radio.description": "Instrumentalny jazz-hop do przeglądania — wybierz dowolny utwór, album lub podcast.", "footer.nav.expand": "Rozwiń {label}", "footer.nav.collapse": "Zwiń {label}", @@ -495,7 +497,7 @@ "filters.sort_title": "Tytuł", "filters.tag_aria": "Filtruj według tagu", "image.show_label": "Pokaż {label}", - "newsletter.default_body": "Dołącz do naszej listy insiderów, aby zyskać wcześniejszy dostęp, ekskluzywne oferty i wyselekcjonowane historie ze świata Superfunky.", + "newsletter.default_body": "Dołącz do naszej listy, aby otrzymywać wcześniejszy dostęp, ekskluzywne oferty i wybrane historie od {brand}.", "newsletter.default_title": "Bądź pierwszy, który dowie się o nowej ulubionej premierze.", "newsletter.eyebrow": "Bądź na bieżąco", "newsletter.image_placeholder_body": "Umieść tutaj pełnowymiarowe zdjęcie produktu lub materiał redakcyjny.", diff --git a/build/netlify-settings.test.php b/build/netlify-settings.test.php new file mode 100644 index 0000000..86a9347 --- /dev/null +++ b/build/netlify-settings.test.php @@ -0,0 +1,71 @@ + 'code', + 'sanitize' => 'netlify_redirects', + 'label' => 'Netlify redirects', +); +netlify_test_assert( + $valid_redirects === funkycommerce_sanitize_control_field( 'netlify_redirects', $field, $valid_redirects, 'previous' ), + 'Valid redirect text should be preserved exactly.' +); +$netlify_test_errors = array(); +netlify_test_assert( + 'previous' === funkycommerce_sanitize_control_field( 'netlify_redirects', $field, 'bad /new 301', 'previous' ), + 'Invalid redirect text should preserve the previous setting.' +); +netlify_test_assert( 1 === count( $netlify_test_errors ), 'Invalid redirect text should produce a visible settings error.' ); + +$schema = file_get_contents( dirname( __DIR__ ) . '/inc/control-center-schema.php' ); +$graphql = file_get_contents( dirname( __DIR__ ) . '/inc/build-webhooks.php' ); +netlify_test_assert( false !== strpos( $schema, "'netlify_redirects'" ) && false !== strpos( $schema, "'netlify_headers'" ), 'The Control Center schema must expose both settings.' ); +$redirect_field = preg_match( "/'netlify_redirects'\\s*=>\\s*array\\([^\\n]*'tier'\\s*=>\\s*'pro'/", $schema ); +$header_field = preg_match( "/'netlify_headers'\\s*=>\\s*array\\([^\\n]*'tier'\\s*=>\\s*'pro'/", $schema ); +netlify_test_assert( 1 === $redirect_field && 1 === $header_field, 'Both raw settings must retain the current Pro tier gate.' ); +netlify_test_assert( false !== strpos( $graphql, "'netlifyRedirects'" ) && false !== strpos( $graphql, "'netlifyHeaders'" ), 'Static-generation GraphQL must allowlist both settings.' ); +$config_function = substr( $graphql, strpos( $graphql, 'function funkycommerce_static_generation_config' ), strpos( $graphql, 'function funkycommerce_static_navigation' ) - strpos( $graphql, 'function funkycommerce_static_generation_config' ) ); +netlify_test_assert( false !== strpos( $config_function, "funkycommerce_field_accessible( 'netlify_redirects'" ), 'The public build payload must enforce the Pro tier gate.' ); +netlify_test_assert( false === strpos( substr( $graphql, strpos( $graphql, 'function funkycommerce_static_generation_config' ), strpos( $graphql, 'function funkycommerce_static_navigation' ) - strpos( $graphql, 'function funkycommerce_static_generation_config' ) ), 'build_webhook_url' ), 'Privileged webhook credentials must stay outside the public payload.' ); + +echo "Netlify Control Center settings tests passed.\n"; diff --git a/build/tailwind-class-manifest.test.mjs b/build/tailwind-class-manifest.test.mjs index 4961af1..8ca8b38 100644 --- a/build/tailwind-class-manifest.test.mjs +++ b/build/tailwind-class-manifest.test.mjs @@ -11,9 +11,9 @@ const style = source("style.css"); const webhooks = source("inc/build-webhooks.php"); const parser = new Engine({ parser: { suppressErrors: false } }); -test("theme 1.2.54 retains the one-time Tailwind worker cleanup", () => { +test("theme 1.2.55 retains the one-time Tailwind worker cleanup", () => { assert.doesNotThrow(() => parser.parseCode(cleanup)); - assert.match(functions, /FUNKYCOMMERCE_HEADLESS_VERSION', '1\.2\.54'/); + assert.match(functions, /FUNKYCOMMERCE_HEADLESS_VERSION', '1\.2\.55'/); assert.match(functions, /require_once .*\/inc\/tailwind-manifest-cleanup\.php'/); assert.doesNotMatch(functions, /tailwind-class-manifest\.php/); }); diff --git a/functions.php b/functions.php index b1c377a..cedb450 100644 --- a/functions.php +++ b/functions.php @@ -9,7 +9,7 @@ exit; } -define( 'FUNKYCOMMERCE_HEADLESS_VERSION', '1.2.54' ); +define( 'FUNKYCOMMERCE_HEADLESS_VERSION', '1.2.55' ); /** * Whether Superfunky Pro is active and licensed. @@ -451,7 +451,7 @@ function funkycommerce_content_shortcode_schemas() { 'content_type' => array( 'default' => 'playlist', 'enum' => array( 'track', 'album', 'playlist', 'artist', 'show', 'episode' ) ), 'height' => array( 'default' => 400, 'type' => 'integer', 'min' => 152, 'max' => 800 ), 'theme' => array( 'default' => 'auto', 'enum' => array( 'auto', 'dark', 'light' ) ), - 'title' => array( 'default' => __( 'Superfunky Radio', 'funkycommerce-headless' ) ), + 'title' => array( 'default' => sprintf( __( '%s Radio', 'funkycommerce-headless' ), get_bloginfo( 'name' ) ) ), 'description' => array( 'default' => '' ), ), 'chat_assistant' => array(), diff --git a/inc/backend-preview.php b/inc/backend-preview.php index 54eee28..684dfa8 100644 --- a/inc/backend-preview.php +++ b/inc/backend-preview.php @@ -367,6 +367,18 @@ function funkycommerce_backend_preview_enqueue_styles() { if ( ! funkycommerce_backend_preview_active() ) { return; } + + $tailwind_css_path = get_theme_file_path( 'assets/dist/theme.css' ); + if ( file_exists( $tailwind_css_path ) ) { + $theme_version = wp_get_theme()->get( 'Version' ); + wp_enqueue_style( + 'funkycommerce-backend-preview-tailwind', + get_theme_file_uri( 'assets/dist/theme.css' ), + array(), + $theme_version ? $theme_version : filemtime( $tailwind_css_path ) + ); + } + wp_register_style( 'funkycommerce-backend-preview', false, array(), FUNKYCOMMERCE_HEADLESS_VERSION ); wp_enqueue_style( 'funkycommerce-backend-preview' ); wp_add_inline_style( 'funkycommerce-backend-preview', funkycommerce_backend_preview_styles_css() ); diff --git a/inc/build-webhooks.php b/inc/build-webhooks.php index 81c899b..2f03cfd 100644 --- a/inc/build-webhooks.php +++ b/inc/build-webhooks.php @@ -22,10 +22,14 @@ function funkycommerce_static_generation_config() { $headers = function_exists( 'funkycommerce_security_header_values' ) ? funkycommerce_security_header_values() : array(); + $netlify_rules_enabled = function_exists( 'funkycommerce_field_accessible' ) + && funkycommerce_field_accessible( 'netlify_redirects', array( 'tier' => 'pro' ) ); return array( 'frontendUrl' => (string) ( $settings['frontend_url'] ?? '' ), 'buildProvider' => (string) ( $settings['build_provider'] ?? 'netlify' ), 'buildBadgeId' => (string) ( $settings['build_badge_id'] ?? '' ), + 'netlifyRedirects' => $netlify_rules_enabled ? (string) ( $settings['netlify_redirects'] ?? '' ) : '', + 'netlifyHeaders' => $netlify_rules_enabled ? (string) ( $settings['netlify_headers'] ?? '' ) : '', 'buildStatusBadgeUrl' => (string) ( $settings['build_status_badge_url'] ?? '' ), 'buildDashboardUrl' => (string) ( $settings['build_dashboard_url'] ?? '' ), 'sitemapEnabled' => 'no' !== ( $settings['sitemap_enabled'] ?? 'yes' ), diff --git a/inc/control-center-schema.php b/inc/control-center-schema.php index f754bbe..cba9b7f 100644 --- a/inc/control-center-schema.php +++ b/inc/control-center-schema.php @@ -460,7 +460,7 @@ function funkycommerce_control_center_sections() { 'label' => __( 'Theme credit text', 'funkycommerce-headless' ), 'type' => 'textarea', 'tier' => 'pro', - 'default' => 'Made with superfuky WP theme by Coded Letter.', + 'default' => 'Made with FunkyCommerce WordPress theme by Coded Letter.', 'sanitize' => 'html', 'description' => __( 'PRO sites may customize or hide this credit. Free sites always display the bundled attribution.', 'funkycommerce-headless' ), ), @@ -661,6 +661,8 @@ function funkycommerce_control_center_sections() { 'artifact_cache_ttl' => array( 'label' => __( 'Artifact shared-cache TTL (seconds)', 'funkycommerce-headless' ), 'type' => 'number', 'tier' => 'free', 'default' => '60', 'min' => '0', 'max' => '3600', 'step' => '1' ), 'artifact_retention_days' => array( 'label' => __( 'Artifact retention (days)', 'funkycommerce-headless' ), 'type' => 'number', 'tier' => 'free', 'default' => '30', 'min' => '1', 'max' => '365', 'step' => '1' ), 'build_provider' => array( 'label' => __( 'Deployment provider', 'funkycommerce-headless' ), 'type' => 'select', 'tier' => 'free', 'default' => 'netlify', 'options' => array( 'netlify' => __( 'Netlify', 'funkycommerce-headless' ), 'cloudflare-pages' => __( 'Cloudflare Pages', 'funkycommerce-headless' ) ) ), + 'netlify_redirects' => array( 'label' => __( 'Netlify _redirects rules', 'funkycommerce-headless' ), 'type' => 'code', 'tier' => 'pro', 'sanitize' => 'netlify_redirects', 'description' => __( 'Raw Netlify _redirects format. One rule per line; maximum 64 KB and 1,000 lines. Invalid rules are not saved.', 'funkycommerce-headless' ) ), + 'netlify_headers' => array( 'label' => __( 'Netlify _headers rules', 'funkycommerce-headless' ), 'type' => 'code', 'tier' => 'pro', 'sanitize' => 'netlify_headers', 'description' => __( 'Raw Netlify _headers format. Use a path followed by indented header lines; maximum 64 KB and 1,000 lines. Invalid rules are not saved.', 'funkycommerce-headless' ) ), 'build_webhook_url' => array( 'label' => __( 'Build webhook URL', 'funkycommerce-headless' ), 'type' => 'url', 'tier' => 'free', 'description' => __( 'Used by the Rebuild storefront action in the top admin bar. Static-first content changes are published only after this hook completes.', 'funkycommerce-headless' ) ), 'periodic_rebuild' => array( 'label' => __( 'Periodic rebuilds', 'funkycommerce-headless' ), 'type' => 'toggle', 'tier' => 'pro', 'default' => 'no', 'description' => __( 'Optional. Leave disabled for explicit, cost-controlled publishing through the admin-bar rebuild action.', 'funkycommerce-headless' ) ), 'rebuild_interval' => array( 'label' => __( 'Rebuild interval (hours)', 'funkycommerce-headless' ), 'type' => 'number', 'tier' => 'pro', 'default' => '12', 'min' => '1', 'max' => '168', 'step' => '1' ), diff --git a/inc/control-center.php b/inc/control-center.php index 9264c75..9c960e6 100644 --- a/inc/control-center.php +++ b/inc/control-center.php @@ -434,6 +434,26 @@ function funkycommerce_sanitize_control_field( $key, $field, $value, $previous ) return wp_check_invalid_utf8( wp_unslash( (string) $value ), true ); } + if ( 'code' === $type && in_array( $field['sanitize'] ?? '', array( 'netlify_redirects', 'netlify_headers' ), true ) ) { + $raw = wp_check_invalid_utf8( (string) $value, true ); + $raw = str_replace( "\r\n", "\n", $raw ); + $reason = funkycommerce_validate_netlify_file_setting( $raw, $field['sanitize'] ); + if ( '' !== $reason ) { + add_settings_error( + 'funkycommerce_control_center', + 'invalid_' . $field['sanitize'], + sprintf( + /* translators: 1: field label, 2: validation reason. */ + __( '%1$s was not saved: %2$s', 'funkycommerce-headless' ), + $field['label'], + $reason + ) + ); + return $previous; + } + return $raw; + } + if ( 'code' === $type || 'textarea' === $type ) { $value = wp_unslash( (string) $value ); if ( 'html' === ( $field['sanitize'] ?? '' ) ) { @@ -448,6 +468,69 @@ function funkycommerce_sanitize_control_field( $key, $field, $value, $previous ) return sanitize_text_field( wp_unslash( (string) $value ) ); } +/** + * Validate bounded raw Netlify-format redirect or header rules. + * + * @param string $contents Raw file contents. + * @param string $kind Either netlify_redirects or netlify_headers. + * @return string Empty when valid, otherwise a user-visible reason. + */ +function funkycommerce_validate_netlify_file_setting( $contents, $kind ) { + if ( strlen( $contents ) > 65536 ) { + return __( 'The content exceeds the 64 KB limit.', 'funkycommerce-headless' ); + } + if ( false !== strpos( $contents, "\r" ) || preg_match( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', $contents ) ) { + return __( 'Control characters are not allowed.', 'funkycommerce-headless' ); + } + $lines = explode( "\n", $contents ); + if ( count( $lines ) > 1000 ) { + return __( 'The content exceeds the 1,000-line limit.', 'funkycommerce-headless' ); + } + + $has_header_path = false; + foreach ( $lines as $line_number => $line ) { + if ( strlen( $line ) > 4096 ) { + return sprintf( + /* translators: %d: line number. */ + __( 'Line %d exceeds the 4 KB line limit.', 'funkycommerce-headless' ), + $line_number + 1 + ); + } + if ( '' === trim( $line ) || preg_match( '/^\s*#/', $line ) ) { + continue; + } + + if ( 'netlify_redirects' === $kind ) { + $parts = preg_split( '/\s+/', trim( $line ) ); + if ( count( $parts ) < 2 || ! str_starts_with( $parts[0], '/' ) || preg_match( '/[\r\n]/', $line ) ) { + return sprintf( __( 'Line %d must contain a source path and destination.', 'funkycommerce-headless' ), $line_number + 1 ); + } + if ( isset( $parts[2] ) && ! preg_match( '/^[2-5][0-9]{2}!?(?:$)/', $parts[2] ) ) { + return sprintf( __( 'Line %d has an invalid redirect status.', 'funkycommerce-headless' ), $line_number + 1 ); + } + foreach ( array_slice( $parts, 3 ) as $option ) { + if ( ! preg_match( '/^[A-Za-z][A-Za-z0-9_-]*=[A-Za-z0-9_.*,:-]+$/', $option ) ) { + return sprintf( __( 'Line %d has an invalid redirect option.', 'funkycommerce-headless' ), $line_number + 1 ); + } + } + continue; + } + + if ( preg_match( '/^[ \t]/', $line ) ) { + if ( ! $has_header_path || ! preg_match( '/^[ \t]+([!#$%&\\x27*+.^_`|~0-9A-Za-z-]+):[ \t]*([^\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]*)$/', $line ) ) { + return sprintf( __( 'Line %d must be a valid indented header following a path.', 'funkycommerce-headless' ), $line_number + 1 ); + } + continue; + } + if ( ! str_starts_with( trim( $line ), '/' ) || preg_match( '/\s/', trim( $line ) ) ) { + return sprintf( __( 'Line %d must be a path beginning with /.', 'funkycommerce-headless' ), $line_number + 1 ); + } + $has_header_path = true; + } + + return ''; +} + /** * Sanitize the complete option while preserving dynamic language values. */ @@ -801,7 +884,7 @@ function funkycommerce_storefront_control_settings( $language = '' ) { $assistant_show_footer = $has_surface_settings ? 'yes' === $settings['ai_assistant_show_footer'] : 'footer' === $legacy_placement; $assistant_show_fixed = $has_surface_settings ? 'yes' === $settings['ai_assistant_show_fixed'] : 'fixed' === $legacy_placement; $assistant_placement = $assistant_show_header ? 'header' : ( $assistant_show_fixed ? 'fixed' : 'footer' ); - $default_theme_credit = 'Made with superfuky WP theme by Coded Letter.'; + $default_theme_credit = 'Made with FunkyCommerce WordPress theme by Coded Letter.'; $theme_credit = funkycommerce_is_pro() ? wp_kses_post( (string) ( $settings['theme_credit_text'] ?? $default_theme_credit ) ) : $default_theme_credit; diff --git a/inc/crypto-payments.php b/inc/crypto-payments.php index 7ef54b1..f982f9a 100644 --- a/inc/crypto-payments.php +++ b/inc/crypto-payments.php @@ -140,7 +140,7 @@ function funkycommerce_register_crypto_gateway() { class WC_Gateway_FunkyCommerce_Crypto extends WC_Payment_Gateway { public function __construct() { $this->id = 'funkycommerce_crypto'; - $this->method_title = __( 'Superfunky Crypto Wallet', 'funkycommerce-headless' ); + $this->method_title = __( 'FunkyCommerce Crypto Wallet', 'funkycommerce-headless' ); $this->method_description = __( 'Accept direct BTC and ETH wallet transfers. Orders remain on hold until the transfer is verified.', 'funkycommerce-headless' ); $this->has_fields = true; $this->supports = array( 'products' ); diff --git a/inc/frontend-theme.php b/inc/frontend-theme.php index 8706647..3d7be34 100644 --- a/inc/frontend-theme.php +++ b/inc/frontend-theme.php @@ -89,9 +89,14 @@ function funkycommerce_frontend_theme_localized_settings() { $language = function_exists( 'pll_current_language' ) ? pll_current_language( 'slug' ) : strtolower( strtok( determine_locale(), '_-' ) ); - $settings['spotify']['title'] = ! empty( $control['footer']['spotifyPlayerTitle'] ) + $store_name = sanitize_text_field( (string) ( $control['branding']['storeName'] ?? get_bloginfo( 'name' ) ) ); + if ( '' === $store_name ) { + $store_name = 'FunkyCommerce'; + } + $radio_title = ! empty( $control['footer']['spotifyPlayerTitle'] ) ? $control['footer']['spotifyPlayerTitle'] - : funkycommerce_frontend_theme_ui_string( 'footer.radio.title', $language, __( 'Superfunky Radio', 'funkycommerce-headless' ) ); + : funkycommerce_frontend_theme_ui_string( 'footer.radio.title', $language, sprintf( __( '%s Radio', 'funkycommerce-headless' ), $store_name ) ); + $settings['spotify']['title'] = str_replace( '{brand}', $store_name, (string) $radio_title ); $settings['spotify']['description'] = ! empty( $control['footer']['spotifyPlayerDescription'] ) ? $control['footer']['spotifyPlayerDescription'] : funkycommerce_frontend_theme_ui_string( 'footer.radio.description', $language, '' ); diff --git a/package-lock.json b/package-lock.json index cd8db88..4a28e9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "funkycommerce-headless-theme", - "version": "1.2.54", + "version": "1.2.55", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "funkycommerce-headless-theme", - "version": "1.2.54", + "version": "1.2.55", "devDependencies": { "autoprefixer": "^10.4.20", "esbuild": "^0.28.2", diff --git a/package.json b/package.json index ef5c682..d596dbf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "funkycommerce-headless-theme", "private": true, - "version": "1.2.54", + "version": "1.2.55", "description": "Tailwind/build tooling for the FunkyCommerce Headless WordPress theme's native frontend assets (block templates, template parts, loader, and Spotify slot).", "scripts": { "build:css": "tailwindcss -i ./assets/css/theme-source.css -o ./assets/dist/theme.css --minify", diff --git a/readme.txt b/readme.txt index ebe70a7..225d5a0 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: headless, woocommerce, wpgraphql, full-site-editing Requires at least: 6.7 Tested up to: 6.8 Requires PHP: 7.4 -Stable tag: 1.2.54 +Stable tag: 1.2.55 License: GPL-2.0-or-later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -36,7 +36,13 @@ With the matching storefront, deferred styles activate on window load or after two seconds, with a no-JavaScript fallback. Rebuild the storefront after saving to publish static pages. Older storefronts apply all CSS immediately. -== 1.2.54 candidate == +== 1.2.55 == + +* Adds the Control Center inputs for Netlify redirects and security headers, and emits them with static storefront builds. +* Styles authenticated backend previews with the theme's compiled Tailwind utilities. +* Improves preview integration, SEO metadata, and editor-facing storefront behavior. + +== 1.2.54 == * Excludes media attachments at the Tailwind inventory query boundary, reducing build traffic before incremental comparison. * Supports safely paced one-time frontend extractor policy upgrades without adding work to WordPress saves. diff --git a/style.css b/style.css index 8249ac6..043a994 100644 --- a/style.css +++ b/style.css @@ -7,7 +7,7 @@ Description: A minimal, content-first block theme for polished WordPress preview Requires at least: 6.7 Tested up to: 6.8 Requires PHP: 7.4 -Version: 1.2.54 +Version: 1.2.55 Update URI: https://github.com/coded-letter/superfunky-theme License: GPL-2.0-or-later License URI: https://www.gnu.org/licenses/gpl-2.0.html