list = $quote_list; add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ) ); // Classic themes. add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'render_for_current_product' ) ); // Out of stock / no price products: add-to-cart form is missing, use summary instead. add_action( 'woocommerce_single_product_summary', array( $this, 'render_fallback' ), 35 ); // Block themes: same renderer appended to the add-to-cart-form block. add_filter( 'render_block_woocommerce/add-to-cart-form', array( $this, 'append_to_block' ) ); // Hide prices / add-to-cart (catalog mode light). if ( 'yes' === Plugin::setting( 'hide_price', 'no' ) ) { add_filter( 'woocommerce_get_price_html', '__return_empty_string', 99 ); add_filter( 'woocommerce_get_variation_price_html', '__return_empty_string', 99 ); } if ( 'yes' === Plugin::setting( 'hide_add_to_cart', 'no' ) ) { add_filter( 'woocommerce_loop_add_to_cart_link', '__return_empty_string', 99 ); // Block themes: product collections. Priority 5 so the Pro loop button (10) is still appended. add_filter( 'render_block_woocommerce/product-button', '__return_empty_string', 5 ); } add_filter( 'body_class', array( $this, 'body_class' ) ); } /** * Enqueue assets and pass config to JS. */ public function enqueue() { wp_enqueue_style( 'bbquote-frontend', BBQUOTE_URL . 'assets/css/frontend.css', array(), BBQUOTE_VERSION ); if ( 'yes' === Plugin::setting( 'hide_add_to_cart', 'no' ) && is_product() ) { // Hide the single add-to-cart button via CSS so variation selectors keep working. wp_add_inline_style( 'bbquote-frontend', '.single_add_to_cart_button,.wc-block-components-product-button__button,form.cart .quantity{display:none!important}' ); } wp_enqueue_script( 'bbquote-frontend', BBQUOTE_URL . 'assets/js/frontend.js', array(), BBQUOTE_VERSION, true ); wp_localize_script( 'bbquote-frontend', 'boringQuoteConfig', array( 'restUrl' => esc_url_raw( rest_url( RestController::NAMESPACE ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'pageUrl' => Plugin::get_page_url(), 'i18n' => array( 'chooseOptions' => __( 'Please choose product options first.', 'boring-quote' ), 'viewList' => __( 'View quote list', 'boring-quote' ), 'error' => __( 'Something went wrong. Please try again.', 'boring-quote' ), 'added' => __( 'Added to quote', 'boring-quote' ), ), ) ); } /** * Whether the button should be shown for a product in a given context. * * @param \WC_Product $product Product. * @param string $context 'single' (product page) or 'loop' (shop/category listings, used by Pro). * @return bool */ public function should_show( $product, $context = 'single' ) { if ( ! $product instanceof \WC_Product ) { return false; } $types = 'loop' === $context ? array( 'simple' ) : array( 'simple', 'variable' ); $show = in_array( $product->get_type(), $types, true ) && 'yes' !== $product->get_meta( '_bbquote_hide_button' ) && ( 'single' !== $context || 'yes' === Plugin::setting( 'show_on_single', 'yes' ) ); /** * Filter button visibility. * * @param bool $show Show flag. * @param \WC_Product $product Product. * @param string $context single|loop. */ return (bool) apply_filters( 'bbquote_show_button', $show, $product, $context ); } /** * The ONE renderer used by every placement (classic hooks, blocks, shortcode, Pro loop). * * @param \WC_Product $product Product. * @param string $context single|loop. * @return string HTML. */ public function render_button( $product, $context = 'single' ) { if ( ! $this->should_show( $product, $context ) ) { return ''; } $guard = $context . '-' . $product->get_id(); if ( isset( $this->printed[ $guard ] ) ) { return ''; } $this->printed[ $guard ] = true; do_action( 'bbquote_before_button', $product, $context ); // An emptied setting is saved as '', which would render a button without text. $label = trim( (string) Plugin::setting( 'button_label', '' ) ); if ( '' === $label ) { $label = __( 'Request a quote', 'boring-quote' ); } ob_start(); wc_get_template( 'button.php', array( 'product' => $product, 'context' => $context, 'is_variable' => $product->is_type( 'variable' ), 'in_list' => ! $product->is_type( 'variable' ) && $this->list->exists( $product->get_id() ), 'label' => $label, 'page_url' => Plugin::get_page_url(), ), 'boring-quote/', BBQUOTE_TEMPLATE_PATH ); return (string) ob_get_clean(); } /** * Classic hook callback. */ public function render_for_current_product() { global $product; echo $this->render_button( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- template escapes. } /** * Fallback when there is no add-to-cart form (out of stock, no price). */ public function render_fallback() { global $product; if ( $product instanceof \WC_Product && ( ! $product->is_purchasable() || ! $product->is_in_stock() ) ) { $this->render_for_current_product(); } } /** * Block themes: append button after the add-to-cart form block. * * @param string $content Block HTML. * @return string */ public function append_to_block( $content ) { global $product; $current = $product instanceof \WC_Product ? $product : wc_get_product( get_the_ID() ); return $current ? $content . $this->render_button( $current ) : $content; } /** * Body class on the quote page. * * @param array $classes Classes. * @return array */ public function body_class( $classes ) { $page_id = Plugin::get_page_id(); if ( $page_id && is_page( $page_id ) ) { $classes[] = 'bbquote-quote-page'; } return $classes; } }