'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 ] : '';
}
}
/** Small util: check if an IP is publicly routable (not private/reserved). */
if ( ! function_exists( 'bongoto_is_public_ip' ) ) {
function bongoto_is_public_ip( $ip ) {
if ( ! is_string( $ip ) || '' === $ip ) {
return false;
}
// FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE ensure only public IPs pass.
return (bool) filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE );
}
}
/**
* Small util: validate a remote URL to reduce SSRF risk.
*
* - only http/https
* - blocks credentials in URL
* - blocks localhost/private/reserved IPs (best-effort)
*/
if ( ! function_exists( 'bongoto_is_safe_remote_url' ) ) {
function bongoto_is_safe_remote_url( $url ) {
if ( ! is_string( $url ) || '' === $url ) {
return false;
}
// Basic WP validation when available.
if ( function_exists( 'wp_http_validate_url' ) && ! wp_http_validate_url( $url ) ) {
return false;
}
$p = wp_parse_url( $url );
if ( empty( $p['scheme'] ) || empty( $p['host'] ) ) {
return false;
}
$scheme = strtolower( $p['scheme'] );
if ( 'http' !== $scheme && 'https' !== $scheme ) {
return false;
}
// Disallow user:pass@host
if ( ! empty( $p['user'] ) || ! empty( $p['pass'] ) ) {
return false;
}
$host = strtolower( $p['host'] );
// Block obvious local names.
if ( 'localhost' === $host || ( '.local' === substr( $host, -6 ) ) || ( '.internal' === substr( $host, -9 ) ) ) {
return false;
}
// If host is an IP, ensure it's public.
if ( filter_var( $host, FILTER_VALIDATE_IP ) ) {
return bongoto_is_public_ip( $host );
}
// Best-effort DNS resolution (IPv4). If it resolves to a private/reserved IP, block.
$resolved = gethostbyname( $host );
if ( is_string( $resolved ) && $resolved !== $host ) {
if ( ! bongoto_is_public_ip( $resolved ) ) {
return false;
}
}
return true;
}
}
/** 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_shop_setup' ) ) {
function bongoto_shop_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_shop_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_shop_shop_inline_css' ) ) {
function bongoto_shop_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 ''
. ( $count > 0 ? esc_html( number_format_i18n( $count ) ) : '' )
. '';
// 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_shop_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-shop' )
);
}
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 '';
}
}
/** 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 );
$real = realpath( $path );
// Allow only files inside safe roots (uploads + wp-content by default).
$uploads = function_exists( 'wp_upload_dir' ) ? wp_upload_dir() : array( 'basedir' => '' );
$roots = array_filter(
array(
$uploads && ! empty( $uploads['basedir'] ) ? realpath( (string) $uploads['basedir'] ) : false,
defined( 'WP_CONTENT_DIR' ) ? realpath( WP_CONTENT_DIR ) : false,
)
);
$roots = apply_filters( 'bongoto_shop_allowed_download_roots', $roots, $url );
$is_allowed = false;
if ( $real ) {
foreach ( (array) $roots as $rroot ) {
$rroot = $rroot ? wp_normalize_path( (string) $rroot ) : '';
if ( $rroot && 0 === strpos( wp_normalize_path( $real ), trailingslashit( $rroot ) ) ) {
$is_allowed = true;
break;
}
}
}
if ( $real && $is_allowed && file_exists( $real ) && is_file( $real ) && is_readable( $real ) ) {
// Prefer real file name if suggested empty.
if ( ! $filename ) {
$filename = basename( $real );
}
// Ensure extension present.
if ( ! bongoto_has_extension( $filename ) ) {
$ext = pathinfo( $real, 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, $real );
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( 'X-Content-Type-Options: nosniff' );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
header( 'Content-Transfer-Encoding: binary' );
header( 'Content-Length: ' . filesize( $real ) );
bongoto_stream_file_to_output( $real );
exit;
}
// if local path not found, fallthrough to remote flow.
}
// ---------- REMOTE FILE ----------
// Validate remote URL (best-effort) to reduce SSRF / abuse.
if ( ! bongoto_is_safe_remote_url( $url ) ) {
wp_die( esc_html__( 'Invalid download URL.', 'bongoto-shop' ) );
}
$max_bytes = (int) apply_filters( 'bongoto_shop_max_download_bytes', 50 * 1024 * 1024, $url );
// Try HEAD first for headers/filename.
$remote_filename = '';
$remote_ctype = '';
$head = wp_remote_head(
$url,
array(
'timeout' => 15,
'redirection' => 3,
'reject_unsafe_urls'=> true,
'sslverify' => true,
)
);
if ( ! is_wp_error( $head ) ) {
$headers = wp_remote_retrieve_headers( $head );
if ( $headers ) {
// Enforce max size when available.
$cl = isset( $headers['content-length'] ) ? (int) $headers['content-length'] : 0;
if ( $cl > 0 && $max_bytes > 0 && $cl > $max_bytes ) {
wp_die( esc_html__( 'File is too large to download.', 'bongoto-shop' ) );
}
$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( 'X-Content-Type-Options: nosniff' );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
header( 'Content-Transfer-Encoding: binary' );
header( 'Content-Length: ' . filesize( $tmp ) );
bongoto_stream_file_to_output( $tmp );
@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-shop' ) );
}
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 = <<