'pdf', 'application/zip' => 'zip', 'application/x-zip-compressed' => 'zip', 'application/x-rar-compressed' => 'rar', 'application/vnd.rar' => 'rar', 'application/x-7z-compressed' => '7z', 'application/json' => 'json', 'application/xml' => 'xml', 'application/msword' => 'doc', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 'application/vnd.ms-excel' => 'xls', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 'application/vnd.ms-powerpoint' => 'ppt', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 'text/plain' => 'txt', 'text/csv' => 'csv', 'text/html' => 'html', 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp', 'image/svg+xml' => 'svg', 'audio/mpeg' => 'mp3', 'audio/ogg' => 'ogg', 'video/mp4' => 'mp4', 'video/quicktime' => 'mov', ); $mime = strtolower( (string) $mime ); return isset( $map[ $mime ] ) ? $map[ $mime ] : ''; } } /** Parse filename from Content-Disposition: attachment; filename="file.ext" */ if ( ! function_exists( 'bongoto_filename_from_cd' ) ) { function bongoto_filename_from_cd( $cd ) { if ( ! is_string( $cd ) || '' === $cd ) { return ''; } // filename* (RFC 5987) if ( preg_match( '/filename\*\s*=\s*(?:UTF-8\'\')?([^;\r\n]+)/i', $cd, $m ) ) { $name = trim( $m[1], " \t\"'" ); $name = rawurldecode( $name ); return sanitize_file_name( $name ); } // filename= if ( preg_match( '/filename\s*=\s*("?)([^";\r\n]+)\1/i', $cd, $m ) ) { return sanitize_file_name( $m[2] ); } return ''; } } /* ---------------------------------------------------- * 1) Woo supports, sizes, grid * ---------------------------------------------------- */ if ( ! function_exists( 'bongoto_woocommerce_setup' ) ) { function bongoto_woocommerce_setup() { if ( ! bongoto_is_woo_active() ) { return; } add_theme_support( 'woocommerce' ); add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); add_theme_support( 'woocommerce', array( 'thumbnail_image_width' => 540, 'single_image_width' => 720, 'product_grid' => array( 'default_rows' => 3, 'min_rows' => 1, 'max_rows' => 6, 'default_columns' => 3, 'min_columns' => 2, 'max_columns' => 5, ), ) ); } add_action( 'after_setup_theme', 'bongoto_woocommerce_setup' ); } /* ---------------------------------------------------- * 2) Replace wrappers + remove default sidebar * ---------------------------------------------------- */ if ( ! function_exists( 'bongoto_wc_remove_default_wrappers' ) ) { function bongoto_wc_remove_default_wrappers() { if ( ! bongoto_is_woo_active() ) { return; } remove_action( 'woocommerce_before_main_content', 'woocommerce_output_content_wrapper', 10 ); remove_action( 'woocommerce_after_main_content', 'woocommerce_output_content_wrapper_end', 10 ); remove_action( 'woocommerce_sidebar', 'woocommerce_get_sidebar', 10 ); } add_action( 'after_setup_theme', 'bongoto_wc_remove_default_wrappers', 20 ); function bongoto_wc_wrapper_start() { echo '
'; } function bongoto_wc_wrapper_end() { echo '
'; } add_action( 'woocommerce_before_main_content', 'bongoto_wc_wrapper_start', 10 ); add_action( 'woocommerce_after_main_content', 'bongoto_wc_wrapper_end', 10 ); } /* ---------------------------------------------------- * 3) Shop grid: minimal inline CSS * ---------------------------------------------------- */ if ( ! function_exists( 'bongoto_woocommerce_shop_inline_css' ) ) { function bongoto_woocommerce_shop_inline_css() { if ( ! bongoto_is_woo_active() ) { return; } $css = <<cart ) ? (int) WC()->cart->get_cart_contents_count() : 0; ob_start(); // IMPORTANT: // Output in a single line so it can be truly "empty" visually when count is 0, // and add data-count for reliable CSS hiding. echo ''; // Selector must match the element in header markup $fragments['span.bt-cart-count[data-bt-cart-count]'] = ob_get_clean(); return $fragments; } add_filter( 'woocommerce_add_to_cart_fragments', 'bongoto_woocommerce_cart_count_fragment' ); } /* ---------------------------------------------------- * 5) Loop grid config * ---------------------------------------------------- */ add_filter( 'loop_shop_columns', function () { return 4; } ); add_filter( 'loop_shop_per_page', function ( $cols ) { return 16; }, 20 ); /* ---------------------------------------------------- * 6) BUY NOW → checkout redirect * ---------------------------------------------------- */ if ( ! function_exists( 'bongoto_buy_now_redirect_to_checkout' ) ) { function bongoto_buy_now_redirect_to_checkout() { if ( ! bongoto_is_woo_active() || is_admin() ) { return; } if ( isset( $_GET['buy-now'] ) && '1' === $_GET['buy-now'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $checkout = function_exists( 'wc_get_checkout_url' ) ? wc_get_checkout_url() : home_url( '/' ); wp_safe_redirect( $checkout ); exit; } } add_action( 'template_redirect', 'bongoto_buy_now_redirect_to_checkout', 9 ); } /* ---------------------------------------------------- * 7) Single product: Buy Now beside Add to Cart (non-free) * ---------------------------------------------------- */ if ( ! function_exists( 'bongoto_print_buy_now_button' ) ) { function bongoto_print_buy_now_button() { if ( ! bongoto_is_woo_active() || ! is_product() ) { return; } $product = wc_get_product( get_queried_object_id() ); if ( ! $product || ! $product->is_purchasable() ) { return; } $price = (float) $product->get_price(); if ( $price <= 0 ) { return; // not applicable for free features } $buy_url = add_query_arg( array( 'add-to-cart' => $product->get_id(), 'buy-now' => '1', ), home_url( '/' ) ); printf( '%2$s', esc_url( $buy_url ), esc_html__( 'Buy Now', 'bongoto-woocommerce' ) ); } add_action( 'woocommerce_after_add_to_cart_button', 'bongoto_print_buy_now_button', 10 ); } /** Tiny critical style */ if ( ! function_exists( 'bongoto_single_btns_critical_css' ) ) { function bongoto_single_btns_critical_css() { if ( ! is_product() ) { return; } $css = <<get_price(); // Free condition: // 1) Price is empty/0 OR // 2) Product is in "Free Download" category (slug/name variations supported) $is_free_price = ( $price <= 0 ); $is_free_cat = has_term( array( 'free-download', 'free-downloads', 'free' ), 'product_cat', $product->get_id() ) || has_term( array( 'Free Download', 'Free Downloads' ), 'product_cat', $product->get_id() ); $is_free = ( $is_free_price || $is_free_cat ); if ( ! $is_free ) { return; } // If it's not downloadable, we can still show the "Related Products" button, // but the download handler will fall back safely. // Hide default buttons. remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_add_to_cart', 30 ); remove_action( 'woocommerce_after_add_to_cart_button', 'bongoto_print_buy_now_button', 10 ); // Show our buttons. add_action( 'woocommerce_single_product_summary', 'bongoto_render_free_download_buttons', 31 ); } add_action( 'wp', 'bongoto_single_free_mode_setup', 9 ); } /** Render Download Now + Related Products */ if ( ! function_exists( 'bongoto_render_free_download_buttons' ) ) { function bongoto_render_free_download_buttons() { $product = wc_get_product( get_queried_object_id() ); if ( ! $product ) { return; } // Secure download link (our handler). $dl_url = add_query_arg( array( 'bt-free-download' => $product->get_id(), '_bt' => wp_create_nonce( 'bt_free_dl_' . $product->get_id() ), ), home_url( '/' ) ); // Related Products anchor on this page (scroll to related section). $related_url = '#bt-related-products'; echo '
'; printf( '%2$s', esc_url( $dl_url ), esc_html__( 'Download Now', 'bongoto-woocommerce' ) ); printf( '%2$s', esc_url( $related_url ), esc_html__( 'Related Products', 'bongoto-woocommerce' ) ); echo '
'; } } /** Anchor for "Related Products" button scroll */ if ( ! function_exists( 'bongoto_related_products_anchor' ) ) { function bongoto_related_products_anchor() { if ( function_exists( 'is_product' ) && is_product() ) { echo ''; } } add_action( 'woocommerce_after_single_product_summary', 'bongoto_related_products_anchor', 19 ); } /** Shop page filter ?bt-cat=slug */ if ( ! function_exists( 'bongoto_shop_filter_by_bt_cat' ) ) { function bongoto_shop_filter_by_bt_cat( $q ) { if ( is_admin() || ! $q->is_main_query() ) { return; } if ( function_exists( 'is_shop' ) && is_shop() && ! empty( $_GET['bt-cat'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $q->set( 'product_cat', sanitize_title( wp_unslash( $_GET['bt-cat'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } } add_action( 'pre_get_posts', 'bongoto_shop_filter_by_bt_cat' ); } /* ---------------- Force download core (with correct extension) ------------- */ /** * Force download a URL, preserving filename + extension when possible. * - Detect filename from Content-Disposition/URL * - Detect MIME, set proper Content-Type * - If no extension, add inferred extension from MIME */ if ( ! function_exists( 'bongoto_force_download_url' ) ) { function bongoto_force_download_url( $url, $suggested_name = '' ) { if ( headers_sent() ) { wp_safe_redirect( $url ); exit; } $url = (string) $url; $parsed = wp_parse_url( $url ); $home_url = home_url( '/' ); $home = wp_parse_url( $home_url ); // Start with best-guess filename (suggested > URL basename). $filename = $suggested_name ?: ( isset( $parsed['path'] ) ? basename( $parsed['path'] ) : '' ); $filename = sanitize_file_name( $filename ); $is_local = ( isset( $parsed['host'], $home['host'] ) && $parsed['host'] === $home['host'] ); // ---------- LOCAL FILE ---------- if ( $is_local ) { $base = trailingslashit( $home_url ); $rel = ltrim( str_replace( $base, '', $url ), '/' ); $path = wp_normalize_path( ABSPATH . $rel ); if ( file_exists( $path ) && is_readable( $path ) ) { // Prefer real file name if suggested empty. if ( ! $filename ) { $filename = basename( $path ); } // Ensure extension present. if ( ! bongoto_has_extension( $filename ) ) { $ext = pathinfo( $path, PATHINFO_EXTENSION ); if ( $ext ) { $filename .= '.' . $ext; } } // Detect MIME. $ctype = ''; if ( function_exists( 'finfo_open' ) ) { $finfo = finfo_open( FILEINFO_MIME_TYPE ); if ( $finfo ) { $ctype = finfo_file( $finfo, $path ); finfo_close( $finfo ); } } if ( ! $ctype ) { $ft = wp_check_filetype( $filename ); if ( ! empty( $ft['type'] ) ) { $ctype = $ft['type']; } } if ( ! $ctype ) { $ctype = 'application/octet-stream'; } nocache_headers(); header( 'Content-Description: File Transfer' ); header( 'Content-Type: ' . $ctype ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Transfer-Encoding: binary' ); header( 'Content-Length: ' . filesize( $path ) ); @readfile( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile exit; } // if local path not found, fallthrough to remote flow. } // ---------- REMOTE FILE ---------- // Try HEAD first for headers/filename. $remote_filename = ''; $remote_ctype = ''; $head = wp_remote_head( $url, array( 'timeout' => 15, 'redirection' => 3, ) ); if ( ! is_wp_error( $head ) ) { $headers = wp_remote_retrieve_headers( $head ); if ( $headers ) { $cd = isset( $headers['content-disposition'] ) ? $headers['content-disposition'] : ''; if ( is_array( $cd ) ) { $cd = implode( '; ', $cd ); } $remote_filename = bongoto_filename_from_cd( $cd ); $remote_ctype = isset( $headers['content-type'] ) ? ( is_array( $headers['content-type'] ) ? reset( $headers['content-type'] ) : $headers['content-type'] ) : ''; } } // Choose best filename so far. if ( $remote_filename ) { $filename = $remote_filename; } elseif ( ! $filename ) { $filename = 'download'; } // Stream body to temp. $tmp = wp_tempnam( $url ); if ( ! $tmp ) { wp_safe_redirect( $url ); exit; } $resp = wp_remote_get( $url, array( 'timeout' => 60, 'stream' => true, 'filename' => $tmp, 'redirection' => 3, ) ); if ( is_wp_error( $resp ) ) { @unlink( $tmp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink wp_safe_redirect( $url ); exit; } // If filename absent in headers and URL (or no extension), try to fix with MIME. $ctype = $remote_ctype; if ( ! $ctype && function_exists( 'finfo_open' ) ) { $finfo = finfo_open( FILEINFO_MIME_TYPE ); if ( $finfo ) { $ctype = finfo_file( $finfo, $tmp ); finfo_close( $finfo ); } } if ( ! $ctype ) { $ctype = 'application/octet-stream'; } // Ensure extension exists; if missing, append from MIME guess. if ( ! bongoto_has_extension( $filename ) ) { $ext = bongoto_mime_to_ext( $ctype ); if ( $ext ) { $filename .= '.' . $ext; } } // Final fallback: still no extension? Try to read from URL path. if ( ! bongoto_has_extension( $filename ) && ! empty( $parsed['path'] ) ) { $ext = pathinfo( $parsed['path'], PATHINFO_EXTENSION ); if ( $ext ) { $filename .= '.' . $ext; } } // And one last resort via wp_check_filetype on current filename (may refine ctype). $ft = wp_check_filetype( $filename ); if ( ! empty( $ft['type'] ) ) { $ctype = $ft['type']; } nocache_headers(); header( 'Content-Description: File Transfer' ); header( 'Content-Type: ' . $ctype ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Transfer-Encoding: binary' ); header( 'Content-Length: ' . filesize( $tmp ) ); @readfile( $tmp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile @unlink( $tmp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink exit; } } /** Handle Download Now: ?bt-free-download=ID&_bt=nonce */ if ( ! function_exists( 'bongoto_handle_free_download' ) ) { function bongoto_handle_free_download() { if ( empty( $_GET['bt-free-download'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $product_id = absint( $_GET['bt-free-download'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $nonce = isset( $_GET['_bt'] ) ? sanitize_text_field( wp_unslash( $_GET['_bt'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! wp_verify_nonce( $nonce, 'bt_free_dl_' . $product_id ) ) { wp_die( esc_html__( 'Invalid download link.', 'bongoto-woocommerce' ) ); } if ( ! function_exists( 'wc_get_product' ) ) { wp_safe_redirect( home_url( '/' ) ); exit; } $product = wc_get_product( $product_id ); if ( ! $product ) { wp_safe_redirect( home_url( '/' ) ); exit; } $price = (float) $product->get_price(); if ( $price > 0 ) { wp_safe_redirect( get_permalink( $product_id ) ); exit; } $download_url = ''; $filename = sanitize_title( get_the_title( $product_id ) ); if ( $product->is_downloadable() ) { $files = $product->get_downloads(); // array of WC_Product_Download if ( ! empty( $files ) ) { $first = reset( $files ); if ( is_object( $first ) && method_exists( $first, 'get_file' ) ) { $download_url = $first->get_file(); } elseif ( is_array( $first ) && ! empty( $first['file'] ) ) { $download_url = $first['file']; } // Prefer named file if provided. if ( is_object( $first ) && method_exists( $first, 'get_name' ) && $first->get_name() ) { $filename = sanitize_file_name( $first->get_name() ); } } } /** * Filter: bongoto/free_download_url * * @param string $download_url URL resolved from product files. * @param WC_Product $product Product instance. */ $download_url = apply_filters( 'bongoto/free_download_url', $download_url, $product ); if ( $download_url ) { bongoto_force_download_url( $download_url, $filename ); } wp_safe_redirect( get_permalink( $product_id ) ); exit; } add_action( 'template_redirect', 'bongoto_handle_free_download', 5 ); } // === Free buttons: size & spacing (single product) === if ( ! function_exists( 'bongoto_free_buttons_css' ) ) { function bongoto_free_buttons_css() { if ( ! is_product() ) { return; } $css = <<