dispatch_notices();
if ( ! empty( Admin\Notices::get_notices() ) ) {
wp_add_inline_style( 'storeengine-admin', $this->get_dynamic_css() );
}
}
public function frontend_scripts() {
// Icon Library.
wp_enqueue_style(
'storeengine-frontend-icon',
STOREENGINE_ASSETS_URI . 'library/icons/storeengine-icons.css',
[ 'wp-components' ],
filemtime(
STOREENGINE_ASSETS_DIR_PATH .
'library/icons/storeengine-icons.css'
),
'all'
);
// Main Stylesheet.
wp_enqueue_style(
'storeengine-frontend-style',
STOREENGINE_ASSETS_URI . 'build/frontend.css',
[],
filemtime( STOREENGINE_ASSETS_DIR_PATH . 'build/frontend.css' ),
'all'
);
// Add dynamic CSS variables.
wp_add_inline_style( 'storeengine-frontend-style', $this->get_dynamic_css() );
// Add Flickity CSS.
if ( Helper::is_product() ) {
wp_enqueue_style(
'flickity',
STOREENGINE_ASSETS_URI . 'library/flickity/flickity.min.css',
[],
STOREENGINE_VERSION,
null
);
}
// JS — use `include` (not `include_once`) so a second hook invocation in
// the same request still gets the array; `*_once` returns int(1) after
// the first include and breaks the array_merge below.
$asset_file = STOREENGINE_ASSETS_DIR_PATH . sprintf( 'build/frontend.%s.asset.php', STOREENGINE_VERSION );
$dependencies = is_file( $asset_file ) ? include $asset_file : null;
if ( ! is_array( $dependencies ) ) {
$dependencies = [ 'dependencies' => [], 'version' => STOREENGINE_VERSION ];
}
do_action( 'storeengine/enqueue_frontend_scripts' );
// Note: the storefront bundle is small enough that the standard string
// extractor parses it fine, so w.org generates its language pack normally
// and it needs no i18n companion — we deliberately don't add that
// multi-KB manifest to every storefront page.
wp_enqueue_script(
'storeengine-frontend-scripts',
STOREENGINE_ASSETS_URI .
sprintf( 'build/frontend.%s.js', STOREENGINE_VERSION ),
// The storefront bundle is jQuery-free (vanilla JS via StoreEngineDQ);
// `wp-util` was unused and only pulled jQuery in transitively.
(array) ( $dependencies['dependencies'] ?? [] ),
$dependencies['version'] ?? STOREENGINE_VERSION,
true
);
if ( Helper::is_product() ) {
global $product;
if ( $product instanceof VariableProduct && 'variable' === $product->get_type() ) {
wp_localize_script(
'storeengine-frontend-scripts',
'StoreEngineProductVariations',
self::get_product_variations( $product )
);
}
}
$analytics = (array) Helper::get_settings( 'analytics', [] );
if ( ArrayUtil::any( $analytics, fn( $stat ) => true === $stat ) ) {
$data = [];
if ( Helper::is_product() ) {
$data = Pixel::get_single_product_data();
}
if ( Helper::is_shop() ) {
$data = Pixel::get_product_archive_data();
}
if ( Helper::is_cart() || Helper::is_checkout() ) {
$data = [ 'cart_info' => Pixel::get_cart_info() ];
}
if ( Helper::is_thank_you() ) {
// Provide the completed order so the GA4 `purchase` event can
// fire on the thank-you page (see google.ts), attributing the
// conversion + revenue to this URL instead of the checkout page.
$data = $this->get_thankyou_pixel_data();
}
wp_localize_script(
'storeengine-frontend-scripts',
'SEPixelData',
array_merge(
[
'page_title' => wp_get_document_title(),
'currency' => Formatting::get_currency(),
],
$data
)
);
}
wp_localize_script(
'storeengine-frontend-scripts',
'StoreEngineGlobal',
$this->get_frontend_script_data()
);
if ( Helper::get_settings( 'enable_wishlist', false ) ) {
wp_localize_script(
'storeengine-frontend-scripts',
'StoreEngineWishlist',
[
'enabled' => true,
'isLoggedIn' => is_user_logged_in(),
'ids' => is_user_logged_in() ? array_map( 'strval', storeengine_get_user_wishlist() ) : [],
'restBase' => esc_url_raw( rest_url( 'storeengine/v1/wishlist/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'i18n' => [
'add' => __( 'Add to wishlist', 'storeengine' ),
'remove' => __( 'Remove from wishlist', 'storeengine' ),
],
]
);
}
if ( Helper::get_settings( 'enable_product_compare', false ) ) {
wp_localize_script(
'storeengine-frontend-scripts',
'StoreEngineCompare',
[
'enabled' => true,
'isLoggedIn' => is_user_logged_in(),
'ids' => is_user_logged_in() ? array_map( 'strval', storeengine_get_user_compare() ) : [],
'max' => \StoreEngine\Classes\ProductCompare::max(),
'restBase' => esc_url_raw( rest_url( 'storeengine/v1/compare/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'i18n' => [
'add' => __( 'Compare', 'storeengine' ),
'added' => __( 'Comparing', 'storeengine' ),
'remove' => __( 'Remove from comparison', 'storeengine' ),
'title' => __( 'Compare products', 'storeengine' ),
'close' => __( 'Close', 'storeengine' ),
],
]
);
}
wp_set_script_translations(
'storeengine-frontend-scripts',
'storeengine',
STOREENGINE_ROOT_DIR_PATH . 'i18n/languages'
);
// Add Flickity JS
if ( Helper::is_product() ) {
wp_enqueue_script(
'flickity',
STOREENGINE_ASSETS_URI . 'library/flickity/flickity.pkgd.min.js',
[],
STOREENGINE_VERSION,
true
);
}
do_action( 'storeengine/assets/after_frontend_scripts' );
}
/**
* Completed-order data for the thank-you page GA4 `purchase` event.
*
* Returned under an `order` key inside SEPixelData and consumed by
* dev_storeengine/pixel-integrations/google.ts. The shape mirrors the
* checkout REST response's `order`, limited to the fields the analytics
* integration reads. Building it here (rather than re-running the checkout
* response pipeline) avoids re-firing payment-success side effects.
*
* @return array Either [ 'order' => [...] ] or [] when no valid order.
*/
private function get_thankyou_pixel_data(): array {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$order_hash = isset( $_GET['order_hash'] ) ? sanitize_text_field( wp_unslash( $_GET['order_hash'] ) ) : '';
if ( '' === $order_hash ) {
return [];
}
try {
$order = Helper::get_order_by_key( $order_hash );
} catch ( \Throwable $e ) {
return [];
}
if ( ! $order instanceof Order || is_wp_error( $order ) || ! $order->get_id() ) {
return [];
}
$line_items = [];
foreach ( $order->get_items( 'line_item' ) as $item ) {
$meta = [];
foreach ( (array) $item->get_meta_data() as $m ) {
if ( is_object( $m ) && isset( $m->key ) ) {
$meta[] = [
'key' => $m->key,
'value' => $m->value ?? '',
];
}
}
$line_items[] = [
'product_id' => $item->get_product_id(),
'price_id' => $item->get_price_id(),
'name' => $item->get_name(),
'price' => $item->get_price(),
'quantity' => $item->get_quantity(),
'subtotal' => $item->get_subtotal(),
'total' => $item->get_total(),
'variation_id' => $item->get_variation_id(),
'price_type' => $item->get_price_type(),
'meta' => $meta,
];
}
$coupon_lines = array_map(
static function ( $code ) {
return [ 'code' => $code ];
},
array_values( array_filter( (array) $order->get_coupon_codes() ) )
);
return [
'order' => [
'transaction_id' => $order->get_transaction_id(),
'order_key' => $order->get_order_key(),
'currency' => $order->get_currency(),
'total' => $order->get_total(),
'total_tax' => $order->get_total_tax(),
'shipping_total_amount' => $order->get_shipping_total(),
'discount_total_amount' => $order->get_discount_total(),
'coupon_lines' => $coupon_lines,
'line_items' => $line_items,
],
];
}
/**
* Enqueue Block Editor Assets
*
* @since 1.3.3
*/
public function block_editor_assets() {
// Check if the aBlocks plugin is active before enqueuing styles.
if ( ! Helper::is_plugin_active( 'ablocks/ablocks.php' ) ) {
return;
}
// Icon Library.
wp_enqueue_style(
'storeengine-frontend-icon',
STOREENGINE_ASSETS_URI . 'library/icons/storeengine-icons.css',
[ 'wp-components' ],
filemtime(
STOREENGINE_ASSETS_DIR_PATH .
'library/icons/storeengine-icons.css'
),
'all'
);
// Main Stylesheet.
wp_enqueue_style(
'storeengine-frontend-style',
STOREENGINE_ASSETS_URI . 'build/frontend.css',
[],
filemtime( STOREENGINE_ASSETS_DIR_PATH . 'build/frontend.css' ),
'all'
);
// Add dynamic CSS variables.
wp_add_inline_style( 'storeengine-frontend-style', $this->get_dynamic_css() );
// Add Flickity CSS.
wp_enqueue_style(
'flickity',
STOREENGINE_ASSETS_URI . 'library/flickity/flickity.min.css',
[],
STOREENGINE_VERSION,
null
);
}
public static function get_product_variations( VariableProduct $product ): array {
$pricing = [];
$taxonomies = [];
$variations = array_map( function ( $variant ) use ( &$taxonomies ) {
$attributes = [];
foreach ( $variant->get_attributes() as $attribute ) {
$attributes[ $attribute->taxonomy ] = $attribute->slug;
$taxonomies[] = $attribute->taxonomy;
}
return [
'id' => $variant->get_id(),
'name' => $variant->get_name(),
'price' => $variant->get_price(),
'sku' => $variant->get_sku(),
'pricing_id' => (int) $variant->get_pricing_id(),
'featured_image_url' => wp_get_attachment_image_url( $variant->get_featured_image() ),
'attributes' => $attributes,
// Per-variation stock so the picker can disable Add to Cart /
// relabel "Out of stock" for the selected variation.
'is_in_stock' => method_exists( $variant, 'is_in_stock' ) ? $variant->is_in_stock() : true,
'stock_status' => method_exists( $variant, 'get_stock_status' ) ? $variant->get_stock_status() : 'instock',
];
}, $product->get_available_variants() );
foreach ( $product->get_prices() as $price ) {
$pricing[ $price->get_id() ] = $price->get_price();
}
return [
'taxonomies' => array_values( array_unique( $taxonomies ) ),
'pricing' => $pricing,
'variations' => $variations,
];
}
public function get_frontend_script_data() {
$data = [];
if ( Helper::is_checkout() || Helper::is_edit_address_page() ) {
$state_labels = array_filter( array_map( fn( $data ) => [
'label' => $data['state']['label'] ?? '',
'required' => $data['state']['required'] ?? false,
], Countries::init()->get_country_locale() ) );
$states = array_merge( Countries::init()->get_allowed_country_states(), Countries::init()->get_shipping_country_states() );
$data = [
'states' => $states,
'state_labels' => $state_labels,
'state_label' => __( 'State / County', 'storeengine' ),
'is_required' => ' *',
'i18n_select_state_text' => esc_attr__( 'Select an option…', 'storeengine' ),
'i18n_no_matches' => _x( 'No matches found', 'enhanced select', 'storeengine' ),
'i18n_ajax_error' => _x( 'Loading failed', 'enhanced select', 'storeengine' ),
'i18n_input_too_short_1' => _x( 'Please enter 1 or more characters', 'enhanced select', 'storeengine' ),
'i18n_input_too_short_n' => _x( 'Please enter %qty% or more characters', 'enhanced select', 'storeengine' ),
'i18n_input_too_long_1' => _x( 'Please delete 1 character', 'enhanced select', 'storeengine' ),
'i18n_input_too_long_n' => _x( 'Please delete %qty% characters', 'enhanced select', 'storeengine' ),
'i18n_selection_too_long_1' => _x( 'You can only select 1 item', 'enhanced select', 'storeengine' ),
'i18n_selection_too_long_n' => _x( 'You can only select %qty% items', 'enhanced select', 'storeengine' ),
'i18n_load_more' => _x( 'Loading more results…', 'enhanced select', 'storeengine' ),
'i18n_searching' => _x( 'Searching…', 'enhanced select', 'storeengine' ),
];
}
$analytics = (array) Helper::get_settings( 'analytics', [] );
$hasAnalytics = ArrayUtil::any( $analytics, fn( $stat ) => true === $stat );
return apply_filters(
'storeengine/frontend_scripts_data',
array_merge(
$this->_get_script_data(),
[
'checkout_page_url' => esc_url( Helper::get_checkout_url() ),
'thankyou_page_url' => esc_url( Helper::get_thankyou_page_url() ),
'payment_gateways' => $this->get_payment_method_script_data(),
'payment_data' => [],
'checkout_with_order_pay' => Formatting::string_to_bool( get_query_var( 'order_pay' ) ),
'page' => [
'dashboard' => esc_url( Helper::get_dashboard_url() ),
'checkout' => esc_url( Helper::get_checkout_url() ),
'thankyou' => esc_url( Helper::get_thankyou_page_url() ),
'terms' => esc_url( Helper::get_terms_page_url() ),
'privacy' => esc_url( Helper::get_privacy_page_url() ),
],
'is_page' => [
'is_product' => Helper::is_product(),
'is_shop' => Helper::is_shop(),
'is_cart' => Helper::is_cart(),
'is_dashboard' => Helper::is_dashboard_index(),
'is_address_edit' => Helper::is_edit_address_page(),
'is_checkout' => Helper::is_checkout(),
'is_order_pay' => Helper::is_checkout() && PaymentUtil::is_valid_order_pay_page(),
'is_add_payment_method' => Helper::is_add_payment_method_page(),
],
'settings' => [
'analytics' => $hasAnalytics ? $analytics : false,
'auto_open_cart_drawer' => (bool) Helper::get_settings( 'auto_open_cart_drawer', false ),
]
],
$data
)
);
}
public function _get_script_data(): array {
global $storeengine_addons;
$current_user = wp_get_current_user();
$user_name = ! is_email( $current_user->display_name ) ? $current_user->display_name : trim( $current_user->first_name . ' ' . $current_user->last_name );
$user_name = $user_name ?: $current_user->user_login;
// Brand logo for barcode labels etc. — StoreEngine store logo, then the
// theme custom logo, then the site icon.
$store_logo = Helper::get_settings( 'store_logo' );
if ( is_numeric( $store_logo ) && (int) $store_logo > 0 ) {
$store_logo = wp_get_attachment_image_url( (int) $store_logo, 'medium' );
}
if ( empty( $store_logo ) && get_theme_mod( 'custom_logo' ) ) {
$store_logo = wp_get_attachment_image_url( (int) get_theme_mod( 'custom_logo' ), 'medium' );
}
if ( empty( $store_logo ) ) {
$store_logo = get_site_icon_url( 128 );
}
$data = [
'is_admin' => is_admin(),
'nonce' => wp_create_nonce( 'wp_rest' ),
'storeengine_nonce' => wp_create_nonce( 'storeengine_nonce' ),
'rest_url' => esc_url_raw( rest_url() ),
'locale' => get_locale(),
'user_locale' => function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(),
'namespace' => STOREENGINE_PLUGIN_SLUG . '/v1/',
'plugin_root_url' => STOREENGINE_PLUGIN_ROOT_URI,
'plugin_root_path' => STOREENGINE_ROOT_DIR_PATH,
'root_path' => STOREENGINE_ROOT_DIR_PATH,
'root_url' => STOREENGINE_PLUGIN_ROOT_URI,
'assets_url' => STOREENGINE_ASSETS_URI,
'ajaxurl' => esc_url( admin_url( 'admin-ajax.php' ) ),
'admin_url' => admin_url(),
'site_url' => site_url(),
'route_path' => wp_parse_url( admin_url(), PHP_URL_PATH ),
'admin_menu_lists' => wp_json_encode( Admin\Menu::get_menu_tree() ),
'current_user_id' => $current_user->ID,
'current_user_name' => $user_name,
'store_email' => Helper::get_settings( 'store_email' ),
'store_name' => get_bloginfo( 'name' ),
'store_logo' => $store_logo ? $store_logo : '',
'is_rtl' => is_rtl(),
'addons' => $storeengine_addons,
// Addon cards injected by other plugins (e.g. storeengine-payments
// gateways) for the Add-ons screen. Each entry mirrors the card shape
// in the React Addons utils (name, label, details, tags, svgIcon, …).
'extra_addons' => apply_filters( 'storeengine/admin/extra_addons', [] ),
// Install-only teasers for the free companion plugins (StoreEngine
// Payments / Connectors). Each entry carries the plugin's active state,
// a download URL, and a catalog of what it unlocks — the React admin
// shows the teaser only while the companion plugin is inactive.
'satellite_plugins' => \StoreEngine\Admin\SatellitePlugins::get_teaser_data(),
'timezone' => wp_timezone_string(),
'current_user_can' => [
'manage_options' => current_user_can( 'manage_options' ),
],
'screen_options' => \StoreEngine\Admin\ScreenOptions::get_all(),
'currency_options' => Helper::get_currency_options(),
'localeConv' => localeconv(),
'is_tax_enabled' => Helper::get_settings( 'enable_product_tax' ),
'enable_product_tax' => Helper::get_settings( 'enable_product_tax' ),
'is_pro' => Helper::is_active_storeengine_pro(),
'ablocks_status' => Helper::is_plugin_installed( 'ablocks/ablocks.php' ) ? ( Helper::is_plugin_active( 'ablocks/ablocks.php' ) ? 'active' : 'not-active' ) : 'not-installed',
];
/**
* Filters the admin app's localized data (window.StoreEngineGlobal).
*
* Addons can inject extra flags here. The Role & Permission addon uses it
* to add the current user's per-resource write permissions so the CPT
* editors can render read-only (hide Save) for a view-only staff user.
*
* @param array $data Localized data array.
*/
return apply_filters( 'storeengine/admin/script_data', $data );
}
public function get_payment_method_script_data() {
$data = apply_filters( 'storeengine/frontend_scripts_payment_method_data', [] );
// Also walk every available gateway through the unified per-gateway
// data filter — `storeengine/checkout/gateway/{id}/data` — so third-
// party gateways that only hook the new public API still populate the
// legacy `payment_gateways.{id}` global that the /checkout/ page
// shells read from. The same filter feeds React Quick Checkout's
// snapshot, so one hook covers both surfaces.
try {
$gateways = Helper::get_payment_gateways()->get_available_payment_gateways();
} catch ( \Throwable $e ) {
$gateways = [];
}
foreach ( $gateways as $gateway ) {
$existing = isset( $data[ $gateway->id ] ) && is_array( $data[ $gateway->id ] ) ? $data[ $gateway->id ] : [];
$merged = apply_filters( "storeengine/checkout/gateway/{$gateway->id}/data", $existing, $gateway );
if ( is_array( $merged ) && ! empty( $merged ) ) {
$data[ $gateway->id ] = $merged;
}
}
return $data;
}
/**
* Register the JS-translation companion handle.
*
* The real admin/setup/frontend bundles are multi-megabyte webpack builds
* that the standard WP-CLI / translate.wordpress.org string extractor cannot
* parse, so on w.org no `storeengine--.json` language
* pack is ever generated and the screens stay untranslated. We ship one
* small, parseable file — assets/build/i18n-strings.js (generated from the
* POT by bin/make-i18n-strings.mjs) — that lists every JS string. w.org can
* read it and builds `storeengine--.json`.
* Because @wordpress/i18n keys locale data by TEXT-DOMAIN (not by file),
* loading that one JSON translates every bundle. The path is unversioned so
* the md5 — and the language packs — stay stable across releases.
*
* Enqueue this handle as a dependency of any StoreEngine bundle; that both
* loads the JSON and makes @wordpress/i18n available.
*/
public function register_i18n_strings() {
if ( wp_script_is( 'storeengine-i18n', 'registered' ) ) {
return;
}
$file = STOREENGINE_ASSETS_DIR_PATH . 'build/i18n-strings.js';
if ( ! is_file( $file ) ) {
return;
}
wp_register_script(
'storeengine-i18n',
STOREENGINE_ASSETS_URI . 'build/i18n-strings.js',
[ 'wp-i18n' ],
filemtime( $file ),
true
);
wp_set_script_translations(
'storeengine-i18n',
'storeengine',
STOREENGINE_ROOT_DIR_PATH . 'i18n/languages'
);
}
/**
* Enqueue Files on Start Plugin
*
* @param string $hook
*
* @function backend_scripts
*/
public function backend_scripts( string $hook ) {
if ( ! strpos( $hook, '_page_' . STOREENGINE_PLUGIN_SLUG ) !== false ) {
return;
}
remove_all_actions( 'admin_notices' );
wp_enqueue_style(
'storeengine-admin-icon',
STOREENGINE_ASSETS_URI . 'library/icons/storeengine-icons.css',
[ 'wp-components' ],
filemtime(
STOREENGINE_ASSETS_DIR_PATH .
'library/icons/storeengine-icons.css'
),
);
wp_enqueue_style(
'storeengine-admin-style',
STOREENGINE_ASSETS_URI . 'build/backend.css',
[ 'wp-components' ],
filemtime( STOREENGINE_ASSETS_DIR_PATH . 'build/backend.css' ),
'all'
);
wp_add_inline_style( 'storeengine-admin-style', $this->get_dynamic_css() );
if ( ! did_action( 'wp_enqueue_media' ) ) {
wp_enqueue_media();
}
// js — see note on the frontend equivalent above (use `include` so a
// second invocation in the same request still returns the array).
$asset_file = STOREENGINE_ASSETS_DIR_PATH . sprintf( 'build/backend.%s.asset.php', STOREENGINE_VERSION );
$dependencies = is_file( $asset_file ) ? include $asset_file : null;
if ( ! is_array( $dependencies ) ) {
$dependencies = [ 'dependencies' => [], 'version' => STOREENGINE_VERSION ];
}
// Load the JS-translation companion so w.org language packs apply here.
$this->register_i18n_strings();
wp_enqueue_script(
'storeengine-admin-scripts',
STOREENGINE_ASSETS_URI .
sprintf( 'build/backend.%s.js', STOREENGINE_VERSION ),
array_merge( (array) ( $dependencies['dependencies'] ?? [] ), wp_script_is( 'storeengine-i18n', 'registered' ) ? [ 'storeengine-i18n' ] : [] ),
$dependencies['version'] ?? STOREENGINE_VERSION,
true
);
wp_localize_script(
'storeengine-admin-scripts',
'StoreEngineGlobal',
$this->get_backend_script_data()
);
wp_set_script_translations(
'storeengine-admin-scripts',
'storeengine',
STOREENGINE_ROOT_DIR_PATH . 'i18n/languages'
);
}
public function get_backend_script_data(): array {
$currencies = [];
foreach ( Helper::get_currencies() as $code => $label ) {
$currencies[] = [
'code' => $code,
'name' => $label,
'symbol' => Helper::get_currency_symbol( $code ),
];
}
return apply_filters(
'storeengine/backend_scripts_data',
array_merge(
$this->_get_script_data(),
[
'first_order' => Helper::get_first_order_date( 'Y-m-d' ),
'currency_options' => Helper::get_currency_options(),
'localeConv' => localeconv(),
'countries' => Countries::init()->get_countries(),
'currencies' => $currencies,
'enable_after_purchase_redirect' => Helper::get_settings( 'enable_after_purchase_redirect', false ),
'enable_faqs' => (bool) Helper::get_settings( 'enable_faqs', true ),
'faq_mode' => Helper::get_settings( 'faq_mode', 'global' ),
// Store-wide default for new products. Settings → Products
// exposes a radio that writes this; the product editor's
// initial-values builder reads it for new products only.
'default_product_shipping_type' => in_array( Helper::get_settings( 'default_product_shipping_type', 'digital' ), [ 'digital', 'physical' ], true ) ? Helper::get_settings( 'default_product_shipping_type', 'digital' ) : 'digital',
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
]
)
);
}
public function get_dynamic_css(): string {
global $storeengine_settings;
/**
* Filter css properties
*/
$css = apply_filters( 'storeengine/dynamic_css_root_properties', [
'--storeengine-primary-color' => esc_attr( $storeengine_settings->global_primary_color ?? '' ),
'--storeengine-secondary-color' => esc_attr( $storeengine_settings->global_secondary_color ?? '' ),
'--storeengine-text-color' => esc_attr( $storeengine_settings->global_text_color ?? '' ),
'--storeengine-subtitle-color' => esc_attr( $storeengine_settings->global_subtitle_color ?? '' ),
'--storeengine-input-text-color' => esc_attr( $storeengine_settings->global_input_text_color ?? '' ),
'--storeengine-placeholder-color' => esc_attr( $storeengine_settings->global_placeholder_color ?? '' ),
'--storeengine-border-color' => esc_attr( $storeengine_settings->global_border_color ?? '' ),
'--storeengine-background-color' => esc_attr( $storeengine_settings->global_background_color ?? '' ),
] );
// Filter out empty values to avoid invalid CSS.
$filtered_css = array_filter( $css, fn( $value ) => trim( ltrim( rtrim( $value, ';' ), ':' ) ) !== '' );
return ':root {' . PHP_EOL . implode( PHP_EOL, array_map( fn( $key, $value ) => "\t$key:$value;", array_keys( $filtered_css ), $filtered_css ) ) . PHP_EOL . '}';
}
public function display_price_placeholder() {
?>