array( 'left' => array( 'logo', 'site-title' ), 'center' => array( 'primary-menu' ), 'right' => array( 'search', 'account', 'cart', 'upload' ), ), // The following are unlocked by Pro; we still define them safely. '2' => array( // Centered logo, split menu (left/right), actions far right 'left' => array( 'primary-menu' ), 'center' => array( 'logo', 'site-title' ), 'right' => array( 'search', 'account', 'cart', 'upload' ), ), '3' => array( // Mega menu variant: logo left, mega menu center, actions right 'left' => array( 'logo' ), 'center' => array( 'primary-menu' ), // Pro may replace with mega menu automatically 'right' => array( 'search', 'upload', 'account', 'cart' ), ), '4' => array( // Topbar+Main (this map is for main bar only) 'left' => array( 'logo' ), 'center' => array( 'primary-menu' ), 'right' => array( 'search', 'account', 'cart', 'upload' ), ), '5' => array( // Minimal: logo left, actions right 'left' => array( 'logo' ), 'center' => array(), 'right' => array( 'search', 'upload', 'cart', 'account' ), ), ); /** * Component visibility toggles (Customizer IDs). * * @var array */ private $component_toggles = array( 'search' => 'bongoto_woocommerce_header_show_search', 'account' => 'bongoto_woocommerce_header_show_account', 'cart' => 'bongoto_woocommerce_header_show_cart', 'upload' => 'bongoto_woocommerce_header_show_upload', // logo/site-title/primary-menu are always considered present, // but site-title has its own general toggle. ); /** * Get instance. * * @return self */ public static function instance() { if ( null === self::$instance ) { self::$instance = new self(); self::$instance->register_defaults(); } return self::$instance; } /** * Register default variants (filterable). */ private function register_defaults() { $is_pro = ( defined( 'BONGOTO_WOOCOMMERCE_IS_PRO' ) && BONGOTO_WOOCOMMERCE_IS_PRO ); // Base set. $this->variants = array( '1' => array( 'title' => __( 'Header 1', 'bongoto-woocommerce' ), 'description' => __( 'Logo • Menu • Search/Button • Account • Cart • Upload', 'bongoto-woocommerce' ), 'available' => true, ), '2' => array( 'title' => __( 'Header 2', 'bongoto-woocommerce' ), 'description' => __( 'Centered Logo, Split Menu', 'bongoto-woocommerce' ), 'available' => $is_pro, ), '3' => array( 'title' => __( 'Header 3', 'bongoto-woocommerce' ), 'description' => __( 'Logo Left, Mega Menu, Actions Right', 'bongoto-woocommerce' ), 'available' => $is_pro, ), '4' => array( 'title' => __( 'Header 4', 'bongoto-woocommerce' ), 'description' => __( 'Topbar + Main', 'bongoto-woocommerce' ), 'available' => $is_pro, ), '5' => array( 'title' => __( 'Header 5', 'bongoto-woocommerce' ), 'description' => __( 'Minimal, Sticky-on-scroll', 'bongoto-woocommerce' ), 'available' => $is_pro, ), ); /** * Filter to modify/add variants from child theme or Pro plugin. * * Each item: * key (string) => [ * 'title' => string, * 'description' => string, * 'available' => bool * ] */ $this->variants = apply_filters( 'bongoto_woocommerce/header/variants', $this->variants ); } /** * Return list of available variant IDs (strings). * * @return string[] */ public function get_available_variants() { $out = array(); foreach ( $this->variants as $id => $meta ) { if ( ! empty( $meta['available'] ) ) { $out[] = (string) $id; } } return $out; } /** * Get current variant (sanitized). * * @return string */ public function current_variant() { $chosen = (string) get_theme_mod( 'bongoto_woocommerce_header_variant', '1' ); $available = $this->get_available_variants(); if ( ! in_array( $chosen, $available, true ) ) { $chosen = '1'; } return $chosen; } /** * Whether a component is enabled (Customizer visibility). * logo / site-title / primary-menu are considered enabled by default, * site-title checks its own toggle setting. * * @param string $slug * @return bool */ public function is_component_enabled( $slug ) { switch ( $slug ) { case 'logo': case 'primary-menu': return true; case 'site-title': return (bool) get_theme_mod( 'bongoto_woocommerce_show_site_title', true ); default: // If toggle not defined, assume true (future-proof). if ( isset( $this->component_toggles[ $slug ] ) ) { return (bool) get_theme_mod( $this->component_toggles[ $slug ], true ); } return true; } } /** * Get default zone layout for a variant (3 zones). * * @param string $variant * @return array {left:[], center:[], right:[]} */ public function get_default_zone_layout( $variant ) { $map = isset( $this->variant_layout_map[ $variant ] ) ? $this->variant_layout_map[ $variant ] : $this->variant_layout_map['1']; // Ensure all zones exist. $map = wp_parse_args( $map, array( 'left' => array(), 'center' => array(), 'right' => array() ) ); return $map; } /** * If a drag-drop JSON (from Sortable control) is present, flatten to a * single ordered list and then re-split into zones based on a simple rule: * - Keep the default zone of each item (from the default map) * - Respect the global order from the JSON among all items * * @param string $variant * @return array zone => [components...] */ public function get_zone_layout( $variant ) { $default = $this->get_default_zone_layout( $variant ); // Fetch user ordering (if present). Stored as JSON string. $json = (string) get_theme_mod( 'bongoto_woocommerce_header_components', '' ); $order_enabled = array(); if ( $json ) { $decoded = json_decode( $json, true ); if ( is_array( $decoded ) && ! empty( $decoded['enabled'] ) ) { $order_enabled = array_values( array_map( 'sanitize_key', (array) $decoded['enabled'] ) ); } } // If no ordering, use defaults. if ( empty( $order_enabled ) ) { return $this->filter_disabled_components( $default ); } // Build a lookup of "default zone" per component slug. $zone_of = array(); foreach ( $default as $zone => $items ) { foreach ( (array) $items as $slug ) { $zone_of[ $slug ] = $zone; } } // Initialize zones empty, then place items by their default zone in given order. $zones = array( 'left' => array(), 'center' => array(), 'right' => array() ); foreach ( $order_enabled as $slug ) { // Skip disabled by visibility toggles. if ( ! $this->is_component_enabled( $slug ) ) { continue; } $zone = isset( $zone_of[ $slug ] ) ? $zone_of[ $slug ] : 'right'; $zones[ $zone ][] = $slug; } // Ensure any items missing from order but in defaults are appended. foreach ( $default as $zone => $items ) { foreach ( (array) $items as $slug ) { if ( ! in_array( $slug, $zones[ $zone ], true ) && $this->is_component_enabled( $slug ) ) { $zones[ $zone ][] = $slug; } } } return $zones; } /** * Remove components that are disabled via toggles from a zone map. * * @param array $zones * @return array */ private function filter_disabled_components( $zones ) { foreach ( $zones as $zone => $items ) { $zones[ $zone ] = array_values( array_filter( (array) $items, function ( $slug ) { return $this->is_component_enabled( $slug ); } ) ); } return $zones; } /** * Render a component (delegates to template-parts/headers/components/*). * Safe guard: if unknown, no-op. * * @param string $slug * @param array $args */ public function render_component( $slug, $args = array() ) { if ( ! function_exists( 'bongoto_woocommerce_header_component' ) ) { // Fallback include. $allowed = array( 'logo', 'site-title', 'primary-menu', 'search', 'account', 'cart', 'upload' ); if ( in_array( $slug, $allowed, true ) ) { if ( ! empty( $args ) ) { set_query_var( 'bongoto_component_args', $args ); } get_template_part( 'template-parts/headers/components/' . $slug ); if ( isset( $GLOBALS['bongoto_component_args'] ) ) { unset( $GLOBALS['bongoto_component_args'] ); } } return; } // Preferred API. bongoto_woocommerce_header_component( $slug, $args ); } /** * Helper to echo an entire zone (loops components in order). * * @param string $zone 'left'|'center'|'right' * @param string|null $variant If null, current variant is used. */ public function render_zone( $zone, $variant = null ) { $variant = $variant ? (string) $variant : $this->current_variant(); $zones = $this->get_zone_layout( $variant ); $items = isset( $zones[ $zone ] ) ? (array) $zones[ $zone ] : array(); foreach ( $items as $slug ) { $this->render_component( $slug ); } } } /** * Convenience function. * * @return Bongoto_WooCommerce_Header_Manager */ function bongoto_woocommerce_header_manager() { return Bongoto_WooCommerce_Header_Manager::instance(); } endif; /** * ------------------------------------------------------------------------- * Template usage (inside template-parts/headers/header-*.php): * *