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