编辑文件:header-footer.php
<?php /** * Header + footer structure overrides for the yinbo catalog. * * Hook-based (no header.php/footer.php copy) so Astra upgrades don't drift: * - Top announcement bar → astra_header_before (priority 5) * - Header actions (CTA + lang) → astra_masthead (priority 25) * - Footer 4-column widget row → astra_footer (priority 9, before Astra's * default copyright bar at 10) * * The language switcher was previously a standalone astra_masthead callback in * functions.php — moved here and folded into the actions cluster so the two * right-hand elements stay aligned regardless of Astra's Header Builder config. * * Required from functions.php before init. * * @package ClothB2B_Child */ if ( ! defined( 'ABSPATH' ) ) { exit; } /* ============================================================ * Top announcement bar. Text is filterable (clothb2b_topbar_text) and * translatable; default is a B2B wholesale callout. * ============================================================ */ add_action( 'astra_header_before', function () { $text = apply_filters( 'clothb2b_topbar_text', __( 'Worldwide B2B wholesale · Inquire for custom orders & bulk pricing', 'clothb2b' ) ); if ( ! is_string( $text ) || '' === trim( $text ) ) { return; } ?> <div class="cb-topbar"><?php echo esc_html( $text ); ?></div> <?php }, 5 ); /* ============================================================ * Header actions cluster: "Inquire Now" CTA + language switcher. * Rendered as one .cb-header-actions container (absolutely positioned on the * right of the masthead) so the two elements stay aligned. The CTA hides on * mobile (components.css); the lang switcher stays. * ============================================================ */ add_action( 'astra_masthead', function () { ?> <div class="cb-header-actions"> <a class="cb-btn cb-btn--solid cb-btn--sm cb-header-cta" href="<?php echo esc_url( clothb2b_inquiry_page_url() ); ?>"> <?php echo esc_html( clothb2b_t( 'Inquire Now' ) ); ?> </a> <?php clothb2b_render_lang_switcher(); ?> </div> <?php }, 25 ); /** * Render the Polylang language switcher (language name only, no flag images). * Extracted from the old standalone astra_masthead callback. pll_the_languages * (['raw'=>1]) returns each language with name/url/current_lang; the flag <img> * is intentionally not echoed. */ function clothb2b_render_lang_switcher() { if ( ! function_exists( 'pll_the_languages' ) ) { return; } $langs = pll_the_languages( array( 'raw' => 1, 'hide_if_empty' => 0, ) ); if ( empty( $langs ) ) { return; } $current_slug = function_exists( 'pll_current_language' ) ? pll_current_language( 'slug' ) : ''; $current = ( $current_slug && ! empty( $langs[ $current_slug ] ) ) ? $langs[ $current_slug ] : null; ?> <div class="cb-lang-switcher"> <button type="button" class="cb-lang-toggle" aria-haspopup="true" aria-expanded="false"> <span class="cb-lang-current"><?php $cb_lang_label = $current ? $current['name'] : ''; if ( ! $cb_lang_label && function_exists( 'pll_default_language' ) ) { $cb_lang_label = pll_default_language( 'name' ); } if ( ! $cb_lang_label ) { $cb_lang_label = __( 'English', 'clothb2b' ); } echo esc_html( $cb_lang_label ); ?></span> <svg class="cb-lang-caret" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"><path d="M1 3l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg> </button> <ul class="cb-lang-list" aria-label="<?php esc_attr_e( 'Languages', 'clothb2b' ); ?>"> <?php foreach ( $langs as $slug => $lang ) : ?> <li class="<?php echo ! empty( $lang['current_lang'] ) ? 'is-current' : ''; ?>"> <a href="<?php echo esc_url( $lang['url'] ); ?>" hreflang="<?php echo esc_attr( $slug ); ?>"> <span><?php echo esc_html( $lang['name'] ); ?></span> </a> </li> <?php endforeach; ?> </ul> </div> <?php } /* ============================================================ * Polylang-aware URL helpers for footer/header links. * Resolve the page/category/shop in the visitor's language so a translated * slug (e.g. /fr/qui-sommes-nous/) does not 404. Mirrors the pattern in * clothb2b_inquiry_page_url() (mu-plugin). Each falls back to /slug/ if the * page/term is missing or Polylang is absent. * ============================================================ */ /** Resolve a WP page by slug to its translated permalink (fallback: /slug/). */ function clothb2b_page_url( $slug ) { $page = get_page_by_path( $slug ); $id = $page ? (int) $page->ID : 0; if ( $id && function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) { $translated = (int) pll_get_post( $id, pll_current_language( 'slug' ) ); if ( $translated ) { $id = $translated; } } if ( $id ) { $url = get_permalink( $id ); if ( $url ) { return $url; } } return home_url( '/' . $slug . '/' ); } /** Resolve the WooCommerce Shop page to its translated permalink (fallback: /shop/). */ function clothb2b_shop_url() { if ( function_exists( 'wc_get_page_id' ) ) { // wc_get_page_id() returns -1 when no Shop page is assigned — that is truthy // (so a bare `if ( $id )` would treat -1 as valid) and get_permalink(-1) // returns false. Gate on $id > 0 and on a truthy permalink. $id = (int) wc_get_page_id( 'shop' ); if ( $id > 0 && function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) { $translated = (int) pll_get_post( $id, pll_current_language( 'slug' ) ); if ( $translated ) { $id = $translated; } } if ( $id > 0 ) { $url = get_permalink( $id ); if ( $url ) { return $url; } } } return home_url( '/shop/' ); } /** Resolve a product_cat term link by slug (fallback: /product-category/slug/). */ function clothb2b_cat_url( $slug ) { if ( taxonomy_exists( 'product_cat' ) ) { $term = get_term_by( 'slug', $slug, 'product_cat' ); if ( $term ) { $url = get_term_link( $term, 'product_cat' ); if ( ! is_wp_error( $url ) ) { return $url; } } } return home_url( '/product-category/' . $slug . '/' ); } /* ============================================================ * Footer — 4-column row above Astra's copyright bar. * Columns: Brand · Quick links · Policies · Contact. All strings translatable. * Social icons render only when clothb2b_social_links returns URLs (filterable; * defaults empty so no broken links ship). * ============================================================ */ add_action( 'astra_footer', function () { $shop = clothb2b_shop_url(); $about = clothb2b_page_url( 'who-we-are' ); $faq = clothb2b_page_url( 'faq' ); $news = clothb2b_page_url( 'news' ); $inq = clothb2b_inquiry_page_url(); $priv = clothb2b_page_url( 'privacy-policy' ); $refund = clothb2b_page_url( 'refund_returns' ); $email = apply_filters( 'clothb2b_contact_email', get_option( 'admin_email' ) ); $social = apply_filters( 'clothb2b_social_links', array() ); ?> <div class="cb-footer"> <div class="cb-footer__cols"> <div class="cb-footer__col cb-footer__brand"> <span class="cb-footer__brand-name"><?php bloginfo( 'name' ); ?></span> <p><?php esc_html_e( 'Cost-effective clothing manufacturing for B2B partners worldwide. Reliable supply at scale, across every fitting and fabric.', 'clothb2b' ); ?></p> </div> <div class="cb-footer__col"> <h4 class="cb-footer__title"><?php esc_html_e( 'Shop', 'clothb2b' ); ?></h4> <ul> <li><a href="<?php echo esc_url( $shop ); ?>"><?php esc_html_e( 'All Products', 'clothb2b' ); ?></a></li> <li><a href="<?php echo esc_url( clothb2b_cat_url( 'women' ) ); ?>"><?php esc_html_e( 'Women', 'clothb2b' ); ?></a></li> <li><a href="<?php echo esc_url( clothb2b_cat_url( 'man' ) ); ?>"><?php esc_html_e( 'Man', 'clothb2b' ); ?></a></li> <li><a href="<?php echo esc_url( clothb2b_cat_url( 'kids' ) ); ?>"><?php esc_html_e( 'Kids', 'clothb2b' ); ?></a></li> </ul> </div> <div class="cb-footer__col"> <h4 class="cb-footer__title"><?php esc_html_e( 'Company', 'clothb2b' ); ?></h4> <ul> <li><a href="<?php echo esc_url( $about ); ?>"><?php esc_html_e( 'Who We Are', 'clothb2b' ); ?></a></li> <li><a href="<?php echo esc_url( $faq ); ?>"><?php esc_html_e( 'FAQ', 'clothb2b' ); ?></a></li> <li><a href="<?php echo esc_url( $news ); ?>"><?php esc_html_e( 'News', 'clothb2b' ); ?></a></li> <li><a href="<?php echo esc_url( $inq ); ?>"><?php echo esc_html( clothb2b_t( 'Inquire Now' ) ); ?></a></li> </ul> </div> <div class="cb-footer__col"> <h4 class="cb-footer__title"><?php esc_html_e( 'Contact', 'clothb2b' ); ?></h4> <ul class="cb-footer__contact"> <?php if ( $email ) : ?> <li><a href="mailto:<?php echo esc_attr( $email ); ?>"><?php echo esc_html( $email ); ?></a></li> <?php endif; ?> <li><a href="<?php echo esc_url( $priv ); ?>"><?php esc_html_e( 'Privacy Policy', 'clothb2b' ); ?></a></li> <li><a href="<?php echo esc_url( $refund ); ?>"><?php esc_html_e( 'Refund & Returns', 'clothb2b' ); ?></a></li> </ul> <?php if ( ! empty( $social ) ) : ?> <div class="cb-footer__social"> <?php foreach ( $social as $platform => $url ) : ?> <a href="<?php echo esc_url( $url ); ?>" aria-label="<?php echo esc_attr( ucfirst( $platform ) ); ?>" target="_blank" rel="noopener noreferrer"> <?php echo clothb2b_social_icon( $platform ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped — trusted inline SVG ?> </a> <?php endforeach; ?> </div> <?php endif; ?> </div> </div> </div> <?php }, 9 ); /** * Inline SVG icon for a social platform. Returns trusted markup (no escaping * on output). Returns '' for unknown platforms. * * @param string $platform Platform key (instagram, facebook, linkedin, …). * @return string */ function clothb2b_social_icon( $platform ) { $icons = array( 'instagram' => '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="1" fill="currentColor" stroke="none"/></svg>', 'facebook' => '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg>', 'linkedin' => '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-4 0v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/></svg>', 'whatsapp' => '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-9 8.5 8.5 8.5 0 0 1-3.8-.9L3 21l1.9-5.2A8.5 8.5 0 1 1 21 11.5z"/></svg>', ); return isset( $icons[ $platform ] ) ? $icons[ $platform ] : ''; } /* ============================================================ * Back-to-top button — appears after scrolling, smooth-scrolls to top. * Inline SVG + tiny vanilla JS (no dependency). Respects reduced-motion. * ============================================================ */ add_action( 'wp_footer', function () { ?> <button type="button" class="cb-to-top" aria-label="<?php esc_attr_e( 'Back to top', 'clothb2b' ); ?>"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 15l-6-6-6 6"/></svg> </button> <script> ( function () { var btn = document.querySelector( '.cb-to-top' ); if ( ! btn ) { return; } var ticking = false; function toggle() { if ( window.scrollY > 600 ) { btn.classList.add( 'is-visible' ); } else { btn.classList.remove( 'is-visible' ); } ticking = false; } window.addEventListener( 'scroll', function () { if ( ! ticking ) { window.requestAnimationFrame( toggle ); ticking = true; } }, { passive: true } ); toggle(); btn.addEventListener( 'click', function () { var reduce = window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches; window.scrollTo( { top: 0, behavior: reduce ? 'auto' : 'smooth' } ); } ); }() ); </script> <?php } );
保存
返回