' . esc_html__( 'Settings saved.', 'addon-boss' ) . '

'; } // Save the storefront Colours (free: appearance ships with every build). // Its own form and nonce, so a free install with no uploads engine still // saves colours. addon_boss_sanitize_colours is the boundary: hex per // known token or nothing stores. if ( isset( $_POST['addon_boss_colours_nonce'] ) && current_user_can( 'manage_woocommerce' ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['addon_boss_colours_nonce'] ) ), 'addon_boss_colours_settings' ) ) { $bb_raw_cols = map_deep( wp_unslash( (array) ( $_POST['addon_boss_colours'] ?? array() ) ), 'sanitize_text_field' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- leaf-sanitised here, hex-validated in the sanitiser. update_option( 'addon_boss_colours', addon_boss_sanitize_colours( (array) $bb_raw_cols ) ); echo '

' . esc_html__( 'Settings saved.', 'addon-boss' ) . '

'; } echo '
'; echo '
'; // The real mark, falling back to the plain coral square where the file is // missing. This screen showed a blank coloured tile next to the name while // the product had a logo all along. $bb_mark = function_exists( 'addon_boss_product_icon_url' ) ? addon_boss_product_icon_url() : ''; echo ''; echo '' !== $bb_mark ? '' : ''; echo esc_html__( 'AddOnBoss', 'addon-boss' ) . ''; echo '' . esc_html__( 'Settings', 'addon-boss' ) . ''; echo '
'; // The first-product checklist leads the landing while it is in play: each // step ticks itself off real store state, and one dismiss retires it for // good (see includes/onboarding.php). if ( function_exists( 'addon_boss_onboarding_card' ) ) { addon_boss_onboarding_card(); } if ( ! $has_uploads ) { // Without the uploads engine there are no upload settings to show; the // page is the descriptive line and the Colours card. echo '

' . esc_html__( 'Product options, add-ons and personalisation on any WooCommerce product.', 'addon-boss' ) . '

'; addon_boss_render_colours_card(); echo '
'; return; } echo '

' . esc_html__( 'When customers upload artwork or files on a product, decide how those files are organised, delivered and named for your shop.', 'addon-boss' ) . '

'; $folder = '1' === (string) get_option( 'addon_boss_uploads_folder_per_order', '1' ); $attach = '1' === (string) get_option( 'addon_boss_uploads_attach_email', '1' ); $pattern = (string) get_option( 'addon_boss_uploads_rename_pattern', '' ); $tokens = function_exists( 'addon_boss_upload_rename_tokens' ) ? addon_boss_upload_rename_tokens() : array(); echo '
'; wp_nonce_field( 'addon_boss_uploads_settings', 'addon_boss_uploads_nonce' ); echo '
'; echo '
' . esc_html__( 'Uploaded files', 'addon-boss' ) . '
'; echo '
'; // Organise & deliver: two on/off switches. echo '
'; echo '

' . esc_html__( 'Organise & deliver', 'addon-boss' ) . '

'; echo '
'; echo '' . esc_html__( 'Keep each order\'s files together', 'addon-boss' ) . ''; echo '' . esc_html__( 'Copy every uploaded file for an order into one folder, so an order\'s artwork sits in one place.', 'addon-boss' ) . ''; echo ''; echo '
'; echo '
'; echo '' . esc_html__( 'Email the files to you', 'addon-boss' ) . ''; echo '' . esc_html__( 'Attach the customer\'s uploaded files to your New order email, up to a sensible size.', 'addon-boss' ) . ''; echo ''; echo '
'; echo '
'; // How files are named: the four preset schemes first (D8: picking a // filename token by token is a decision most shops should not have to // make), Build your own behind them revealing the token builder. The // dropdown stores nothing itself: it derives from the one stored // pattern and fills it, so the resolver, the order copy and the email // never learn schemes exist. echo '
'; echo '

' . esc_html__( 'Rename uploaded files', 'addon-boss' ) . '

'; echo '

' . esc_html__( 'Pick how uploaded files are named for you, or build your own naming from the pieces.', 'addon-boss' ) . '

'; $bb_schemes = array( 'keep' => array( 'label' => __( 'Keep the customer\'s filenames', 'addon-boss' ), 'pattern' => '' ), 'order' => array( 'label' => __( 'Order number + original name', 'addon-boss' ), 'pattern' => '{order}-{original}' ), 'order-pr' => array( 'label' => __( 'Order number + product + original name', 'addon-boss' ), 'pattern' => '{order}-{product}-{original}' ), 'order-cf' => array( 'label' => __( 'Order number + customer + field', 'addon-boss' ), 'pattern' => '{order}-{customer}-{field}' ), ); $bb_scheme_now = 'custom'; foreach ( $bb_schemes as $bb_sk => $bb_s ) { if ( $pattern === $bb_s['pattern'] ) { $bb_scheme_now = $bb_sk; break; } } echo ''; // The token builder, behind Build your own. The pattern input stays in // the form while folded, so whatever is shown is exactly what saves. echo '
'; echo '

' . esc_html__( 'Build a filename from the pieces below. Click to add each one. Leave it empty to keep the customer\'s own filenames.', 'addon-boss' ) . '

'; echo '
'; foreach ( $tokens as $tk => $t ) { echo ''; } echo '
'; echo '
'; echo ''; echo ''; echo '
'; echo '
'; echo '
' . esc_html__( 'Example', 'addon-boss' ) . '
'; echo '
'; echo '
'; // bb-edbody echo '
'; // bb-editor echo '
'; echo '
'; ?> ( function () { var input = document.getElementById( 'bb-up-rename' ); var prev = document.getElementById( 'bb-up-rename-preview' ); if ( ! input || ! prev ) { return; } var egs = {}; document.querySelectorAll( '.bb-up-chip' ).forEach( function ( b ) { egs[ b.getAttribute( 'data-token' ) ] = b.getAttribute( 'data-eg' ); } ); function render() { var p = ( input.value || '' ).trim(); if ( '' === p ) { prev.textContent = 'logo.png'; return; } var name = p.replace( /\{[a-z]+\}/g, function ( m ) { return undefined !== egs[ m ] ? egs[ m ] : ''; } ); name = name.replace( /[\/\\:*?"<>|]+/g, '-' ).replace( /[\s_-]{2,}/g, '-' ).replace( /^[-\s_.]+|[-\s_.]+$/g, '' ); prev.textContent = ( name || 'logo' ) + '.png'; } document.querySelectorAll( '.bb-up-chip' ).forEach( function ( b ) { b.addEventListener( 'click', function () { var t = b.getAttribute( 'data-token' ); input.value = ( input.value && ! /[-_ ]$/.test( input.value ) ) ? input.value + '-' + t : input.value + t; render(); input.focus(); } ); } ); var clr = document.getElementById( 'bb-up-rename-clear' ); if ( clr ) { clr.addEventListener( 'click', function () { input.value = ''; render(); } ); } input.addEventListener( 'input', render ); // The scheme dropdown fills the one stored pattern; Build your own // opens the token builder over whatever is currently stored. var scheme = document.getElementById( 'bb-up-scheme' ); var builder = document.getElementById( 'bb-up-builder' ); if ( scheme && builder ) { scheme.addEventListener( 'change', function () { var opt = scheme.selectedOptions && scheme.selectedOptions[ 0 ]; if ( ! opt ) { return; } if ( 'custom' === opt.value ) { builder.hidden = false; input.focus(); render(); return; } builder.hidden = true; input.value = opt.getAttribute( 'data-pattern' ) || ''; render(); } ); } render(); } )(); '; } /** * The storefront Colours card: four overrides over the skin tokens, each a * picker + hex box + Clear. Free (appearance ships with every build), its * own form and nonce. The skin's three layers are stated in the lede so * the merchant knows what leaving a colour empty means: neutral defaults * that work anywhere, a block theme's palette flowing in where one is * published, and these choices over both. */ function addon_boss_render_colours_card() { $bb_rows = array( 'accent' => array( 'title' => __( 'Highlights and prompts', 'addon-boss' ), 'desc' => __( 'Paints the Choose prompts, the add-another button and the panel\'s other highlights. Out of the box it follows a block theme\'s own accent, with the house coral everywhere else.', 'addon-boss' ), 'default' => '#fd4730', 'placeholder' => __( 'Follows your theme', 'addon-boss' ), ), 'sel' => array( 'title' => __( 'Selected choice', 'addon-boss' ), 'desc' => __( 'The fill behind a picked button and the ring around a picked swatch. Out of the box it uses the theme\'s own text colour, which reads clearly on any store.', 'addon-boss' ), 'default' => '#16181d', 'placeholder' => __( 'Follows your theme', 'addon-boss' ), ), 'receipt' => array( 'title' => __( 'Receipt line', 'addon-boss' ), 'desc' => __( 'The rule across the top of the price breakdown, where the options hand over to the total.', 'addon-boss' ), 'default' => '#16181d', 'placeholder' => __( 'Follows your theme', 'addon-boss' ), ), 'wrap' => array( 'title' => __( 'Options background', 'addon-boss' ), 'desc' => __( 'A soft tint behind the whole options area, drawn as its own panel. Out of the box it is transparent, so your page shows through.', 'addon-boss' ), 'default' => '#f4f3f0', 'placeholder' => __( 'Transparent', 'addon-boss' ), ), ); $stored = addon_boss_sanitize_colours( (array) get_option( 'addon_boss_colours', array() ) ); echo '
'; wp_nonce_field( 'addon_boss_colours_settings', 'addon_boss_colours_nonce' ); echo '
'; echo '
' . esc_html__( 'Storefront colours', 'addon-boss' ) . '
'; echo '
'; echo '

' . esc_html__( 'The options panel follows your theme out of the box: a block theme lends it its own colours, everywhere else it keeps calm neutral defaults. Set a colour here only where you want to overrule that, and clear it to hand the choice back.', 'addon-boss' ) . '

'; foreach ( $bb_rows as $bb_key => $bb_row ) { $bb_val = $stored[ $bb_key ] ?? ''; echo '
'; echo '' . esc_html( $bb_row['title'] ) . ''; echo '' . esc_html( $bb_row['desc'] ) . ''; echo ''; echo ''; echo ''; echo ''; echo '
'; } echo '
'; echo '
'; echo '
'; ?> document.querySelectorAll( '.bb-col-row' ).forEach( function ( row ) { var pick = row.querySelector( '.bb-col-pick' ); var hex = row.querySelector( '.bb-col-hex' ); var clr = row.querySelector( '.bb-col-clear' ); if ( ! pick || ! hex ) { return; } pick.addEventListener( 'input', function () { hex.value = pick.value; } ); hex.addEventListener( 'input', function () { var v = hex.value.trim(); if ( /^#[0-9a-fA-F]{6}$/.test( v ) ) { pick.value = v; } } ); if ( clr ) { clr.addEventListener( 'click', function () { hex.value = ''; pick.value = pick.getAttribute( 'data-default' ) || '#ffffff'; } ); } } );