Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .monorepo-source.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
6 changes: 4 additions & 2 deletions assets/storefront-ui-strings/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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}",
Expand Down Expand Up @@ -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.",
Expand Down
6 changes: 4 additions & 2 deletions assets/storefront-ui-strings/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "無料",
Expand Down Expand Up @@ -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": "注文内容の確認",
Expand Down Expand Up @@ -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}を折りたたむ",
Expand Down Expand Up @@ -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": "ここにフルブリードの商品画像や特集画像を配置します。",
Expand Down
6 changes: 4 additions & 2 deletions assets/storefront-ui-strings/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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}",
Expand Down Expand Up @@ -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.",
Expand Down
71 changes: 71 additions & 0 deletions build/netlify-settings.test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
/**
* Focused tests for raw Netlify Control Center settings.
*/

define( 'ABSPATH', __DIR__ . '/' );

$netlify_test_errors = array();

function __( $text, $domain = null ) {
return $text;
}

function add_action( ...$args ) {}
function add_filter( ...$args ) {}
function wp_unslash( $value ) {
return $value;
}
function wp_check_invalid_utf8( $value, $strip = false ) {
return $value;
}
function add_settings_error( $setting, $code, $message ) {
global $netlify_test_errors;
$netlify_test_errors[] = compact( 'setting', 'code', 'message' );
}

require dirname( __DIR__ ) . '/inc/control-center.php';

function netlify_test_assert( $condition, $message ) {
if ( ! $condition ) {
fwrite( STDERR, $message . PHP_EOL );
exit( 1 );
}
}

$valid_redirects = "/old /new 301!\n/shop/* https://shop.example/:splat 302 Country=US";
netlify_test_assert( '' === funkycommerce_validate_netlify_file_setting( $valid_redirects, 'netlify_redirects' ), 'Valid raw redirects should pass.' );
netlify_test_assert( '' !== funkycommerce_validate_netlify_file_setting( 'old /new 301', 'netlify_redirects' ), 'Redirect source must begin with a slash.' );
netlify_test_assert( '' !== funkycommerce_validate_netlify_file_setting( "/old /new 9999", 'netlify_redirects' ), 'Invalid redirect status should fail.' );
netlify_test_assert( '' !== funkycommerce_validate_netlify_file_setting( "/old /new 301\rbroken", 'netlify_redirects' ), 'Bare carriage returns should fail.' );
netlify_test_assert( '' === funkycommerce_validate_netlify_file_setting( "/*\n X-Frame-Options: SAMEORIGIN", 'netlify_headers' ), 'Valid raw headers should pass.' );
netlify_test_assert( '' !== funkycommerce_validate_netlify_file_setting( " X-Frame-Options: DENY", 'netlify_headers' ), 'A header requires a preceding path.' );

$field = array(
'type' => '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";
4 changes: 2 additions & 2 deletions build/tailwind-class-manifest.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});
Expand Down
4 changes: 2 additions & 2 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(),
Expand Down
12 changes: 12 additions & 0 deletions inc/backend-preview.php
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
Expand Down
4 changes: 4 additions & 0 deletions inc/build-webhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' ),
Expand Down
4 changes: 3 additions & 1 deletion inc/control-center-schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ function funkycommerce_control_center_sections() {
'label' => __( 'Theme credit text', 'funkycommerce-headless' ),
'type' => 'textarea',
'tier' => 'pro',
'default' => 'Made with <a href="https://superfunky.pro" target="_blank" rel="noopener noreferrer">superfuky WP theme</a> by <a href="https://codedletter.com" target="_blank" rel="noopener noreferrer">Coded Letter</a>.',
'default' => 'Made with <a href="https://superfunky.pro" target="_blank" rel="noopener noreferrer">FunkyCommerce WordPress theme</a> by <a href="https://codedletter.com" target="_blank" rel="noopener noreferrer">Coded Letter</a>.',
'sanitize' => 'html',
'description' => __( 'PRO sites may customize or hide this credit. Free sites always display the bundled attribution.', 'funkycommerce-headless' ),
),
Expand Down Expand Up @@ -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' ),
Expand Down
Loading
Loading