Cross-Sell-Vorschläge im FoodMaster (Woofood) – Quick-View öffnen (einheitliches Popup)

Cross-Sell with Discounts for WooCommerce zeigt passende Zusatzprodukte im Mini-Cart, im Warenkorb und im Checkout. Wenn du zusätzlich das FoodMaster (früher WooFood-Plugin ) einsetzt, gibt es dabei einen Stolperstein – und dieser Beitrag zeigt dir, wie du ihn mit einem kleinen Code-Snippet löst.

Das Problem

WooFood-Produkte sind technisch einfache Produkte. Ein Cross-Sell-Vorschlag zeigt deshalb standardmäßig einen normalen „In den Warenkorb“-Button und legt das Produkt ohne Auswahl der Extra-Optionen in den Warenkorb. Der Kunde kann seine Extras (z. B. Beilagen, Soßen, Zutaten) so gar nicht wählen.

Die Lösung

Dieses Snippet erkennt solche Produkte und öffnet sie stattdessen im WooFood-eigenen Quick-View-Popup. Dort wählt der Kunde die Extras und legt das Produkt korrekt in den Warenkorb. Der Button behält dabei 1:1 das gewohnte Design deiner Cross-Sell-Karten – er wird nur „umverdrahtet“, nicht ersetzt.

Wann ist das sinnvoll? (Szenarien)

  • Du bietest Speisen mit Pflicht- oder Wahl-Extras als Cross-Sell an (z. B. einen „Wunsch-Salat“ mit Zutaten-Auswahl).
  • Du möchtest ein einheitliches Auswahl-Erlebnis: Jedes Cross-Sell-Produkt öffnet zuerst das WooFood-Popup, statt direkt in den Warenkorb zu springen.
  • Du willst vermeiden, dass Extra-Produkte „leer“ (ohne gewählte Optionen) im Warenkorb landen.

Voraussetzungen

  • Aktives WooFood-Plugin (mit Quick-View und Extra-Optionen).
  • Aktives Cross-Sell with Discounts for WooCommerce (die kostenlose Version genügt).

Zwei Modi

Ganz oben im Snippet stellst du das Verhalten ein:

define( 'SPWFCS_MODE', 'extras_only' ); // 'extras_only' oder 'all'

extras_only (empfohlen)

Nur Cross-Sell-Produkte, die tatsächlich WooFood-Extra-Optionen haben, öffnen das Popup. Alle anderen behalten den normalen Ein-Klick-Kauf.

all

Jedes einfache Cross-Sell-Produkt öffnet zuerst das Quick-View-Popup – auch Produkte ohne Extras. Gut für ein durchgängig einheitliches Erlebnis.

Variable Produkte (optional)

Variable Produkte behalten standardmäßig ihre eigene Auswahl. Ausnahme: Wenn du SPWFCS_MODE auf all stellst und in der Pro-Version die Auswahl-Methode auf „Quick-View-Popup“ gesetzt hast, öffnen auch variable Cross-Sell-Produkte im WooFood-Popup. In den anderen Pro-Modi (Inline-Liste bzw. Produktseiten-Link) bleiben variable Produkte unberührt.

Menge-Auswahl

Auf den betroffenen Karten wird die kleine Mengen-Auswahl ausgeblendet – die Menge wählt der Kunde im Popup.

Button-Beschriftung

Standardmäßig zeigt ein Text-Button „Wähle Option“ (reine Icon-Buttons bleiben unverändert). Willst du die Original-Beschriftung behalten, setze:

define( 'SPWFCS_LABEL', '' );

Installation

Füge den folgenden Code in die functions.php deines (Child-)Themes ein – oder nutze ein Code-Snippets-Plugin (z. B. „Code Snippets“). Danach ist alles aktiv.

<?php
/**
 * Cross-Sell × WooFood — open cross-sell products in WooFood's Quick-View first
 * ---------------------------------------------------------------------------
 * For shops that use the "WooFood" plugin together with
 * "Cross-Sell with Discounts for WooCommerce".
 *
 * WooFood products are technically SIMPLE products, so by default a cross-sell
 * suggestion shows a normal "Add to cart" button and pushes the product into the
 * cart WITHOUT letting the customer pick the WooFood Extra Options first.
 *
 * This snippet turns the cross-sell button into a "Choose option" button that
 * opens WooFood's own Quick-View popup (where the extras + add-to-cart live).
 *
 * Two modes (see SPWFCS_MODE below):
 *   'extras_only' — only cross-sell products that actually have WooFood Extra
 *                   Options open the popup; everything else keeps the normal
 *                   one-click add. (Recommended.)
 *   'all'         — every simple cross-sell product opens the Quick-View first.
 *
 * Variable products keep the plugin's own "Choose option" handling by default.
 * Exception: in 'all' mode AND when the Pro "Selection method" is set to the
 * Quick-View popup, variable cross-sell products are routed to WooFood's popup
 * too (instead of the Pro variant modal), for a consistent experience.
 *
 * When active, the small quantity selector on the affected cross-sell cards is
 * hidden — the quantity is chosen inside the popup.
 *
 * How to use: drop this into your (child) theme's functions.php, or add it as a
 * snippet with a code-snippets plugin. Requires the WooFood plugin to be active.
 *
 * @author  SmartPixels
 * @license GPL-2.0-or-later
 */

defined( 'ABSPATH' ) || exit;

/* =========================== CONFIG =========================== */
// 'extras_only' (recommended) or 'all'.
if ( ! defined( 'SPWFCS_MODE' ) ) {
	define( 'SPWFCS_MODE', 'extras_only' );
}
// Button label shown on the cross-sell card.
if ( ! defined( 'SPWFCS_LABEL' ) ) {
	define( 'SPWFCS_LABEL', 'Wähle Option' );
}
/* ============================================================== */

add_action( 'init', function () {

	// Only run when WooFood (with its Quick-View + extras) is available.
	if ( ! function_exists( 'woofood_check_if_has_extra_options' ) ) {
		return;
	}

	// AJAX: which of the given product IDs have WooFood Extra Options?
	// (Only needed in 'extras_only' mode.)
	add_action( 'wp_ajax_spwfcs_check',        'spwfcs_ajax_check' );
	add_action( 'wp_ajax_nopriv_spwfcs_check', 'spwfcs_ajax_check' );

	add_action( 'wp_enqueue_scripts', 'spwfcs_enqueue', 99 );
} );

/**
 * Returns { productId: 1|0 } — whether each product has WooFood Extra Options.
 */
function spwfcs_ajax_check() {
	$ids = isset( $_POST['ids'] ) ? array_map( 'absint', (array) $_POST['ids'] ) : array(); // phpcs:ignore WordPress.Security
	$out = array();
	foreach ( array_unique( array_filter( $ids ) ) as $id ) {
		$out[ $id ] = woofood_check_if_has_extra_options( $id ) ? 1 : 0;
	}
	wp_send_json( $out );
}

/**
 * Enqueue the small front-end script (+ a touch of CSS) that rewires the
 * cross-sell buttons to WooFood's Quick-View.
 */
function spwfcs_enqueue() {
	if ( ! function_exists( 'is_woocommerce' ) ) {
		return;
	}

	// Inline-only script handle (the button keeps the cross-sell card's own
	// styling, so no extra CSS is needed).
	// Pro variant "Selection method" (wccsp_variations → variable_mode): link|inline|modal.
	// In 'all' mode we route variable products to WooFood's popup too, but ONLY when
	// the Pro setting is the Quick-View popup ('modal') — so an inline-list setup keeps
	// its own dropdown and a link setup keeps its product-page link.
	$pro          = get_option( 'wccsp_variations' );
	$pro_var_mode = ( is_array( $pro ) && ! empty( $pro['variable_mode'] ) ) ? (string) $pro['variable_mode'] : 'link';

	wp_register_script( 'spwfcs', false, array( 'jquery' ), '1.0', true );
	wp_enqueue_script( 'spwfcs' );
	wp_localize_script( 'spwfcs', 'SPWFCS', array(
		'ajaxUrl'        => admin_url( 'admin-ajax.php' ),
		'mode'           => SPWFCS_MODE,
		'label'          => SPWFCS_LABEL,
		'proVariantMode' => $pro_var_mode,
	) );
	wp_add_inline_script( 'spwfcs', spwfcs_inline_js() );
}

/**
 * The front-end logic (returned as a string for wp_add_inline_script).
 */
function spwfcs_inline_js() {
	return <<<'JS'
( function ( $ ) {
	'use strict';
	if ( typeof SPWFCS === 'undefined' ) { return; }
	var MODE     = SPWFCS.mode;
	var LABEL    = SPWFCS.label;
	var PRO_MODE = SPWFCS.proVariantMode; // Pro "Selection method": link|inline|modal
	var extrasCache = {}; // productId -> 1|0

	// Rewire the EXISTING cross-sell add-to-cart button so it keeps its exact
	// design, but opens WooFood's Quick-View instead of adding directly:
	//   - add the .woofood-quickview-button class + qv-id → WooFood's own
	//     body-delegated handler opens the popup,
	//   - remove data-product-id → the plugin's direct AJAX-add is neutralised
	//     (its click handler bails out without a product id).
	function rewire( $item, type ) {
		if ( $item.data( 'spwfcsDone' ) ) { return; }
		$item.data( 'spwfcsDone', 1 );
		var pid = parseInt( $item.attr( 'data-product-id' ), 10 );
		if ( ! pid ) { return; }

		var $btn;
		if ( 'variable' === type ) {
			// Variable product: the plugin renders a ".wccs-select-options" link.
			// Drop that class so the Pro variant modal no longer handles it, then
			// wire it to WooFood's popup instead.
			$btn = $item.find( '.wccs-select-options' ).first();
			if ( ! $btn.length ) { return; }
			$btn.removeClass( 'wccs-select-options' );
		} else {
			$btn = $item.find( '.wccs-add-to-cart' ).not( '.wccs-select-options' ).first();
			if ( ! $btn.length ) { return; }
		}
		if ( $btn.hasClass( 'woofood-quickview-button' ) ) { return; }

		$btn.addClass( 'woofood-quickview-button' )
			.attr( 'qv-id', pid )
			.removeAttr( 'data-product-id' );

		// Optional: relabel a TEXT button (icon-only buttons keep their icon).
		if ( LABEL ) {
			var $lbl = $btn.find( '.wccs-btn-label' );
			if ( $lbl.length ) { $lbl.text( LABEL ); }
			$btn.attr( 'aria-label', LABEL );
		}

		// The quantity is picked inside WooFood's popup → hide it on the card.
		$item.find( '.wccs-quantity' ).hide();
	}

	// Cross-sell items that still have a real add button (NOT the variable
	// "Choose option" link — those stay with the plugin's own handling).
	function candidates() {
		var out = [];
		$( '.wccs-item[data-product-id]' ).each( function () {
			var $i = $( this );
			if ( $i.data( 'spwfcsDone' ) ) { return; }
			var pid = parseInt( $i.attr( 'data-product-id' ), 10 );
			if ( ! pid ) { return; }
			if ( $i.find( '.wccs-add-to-cart' ).not( '.wccs-select-options' ).length ) {
				// Simple product with a real add button.
				out.push( { el: $i, pid: pid, type: 'simple' } );
			} else if ( MODE === 'all' && PRO_MODE === 'modal' && $i.find( '.wccs-select-options' ).length ) {
				// Variable product + Pro "Quick-View popup" + mode 'all' → WooFood too.
				out.push( { el: $i, pid: pid, type: 'variable' } );
			}
		} );
		return out;
	}

	function process() {
		var items = candidates();
		if ( ! items.length ) { return; }

		if ( MODE === 'all' ) {
			items.forEach( function ( it ) { rewire( it.el, it.type ); } );
			return;
		}

		// extras_only: look up which product IDs carry WooFood extras.
		var toCheck = [];
		items.forEach( function ( it ) {
			if ( extrasCache[ it.pid ] === undefined ) { toCheck.push( it.pid ); }
		} );

		var apply = function () {
			items.forEach( function ( it ) {
				if ( extrasCache[ it.pid ] ) { rewire( it.el, it.type ); }
				else { it.el.data( 'spwfcsDone', 1 ); } // no extras → keep normal add
			} );
		};

		if ( ! toCheck.length ) { apply(); return; }
		$.post( SPWFCS.ajaxUrl, { action: 'spwfcs_check', ids: toCheck }, function ( res ) {
			res = res || {};
			toCheck.forEach( function ( id ) { extrasCache[ id ] = res[ id ] ? 1 : 0; } );
			apply();
		}, 'json' );
	}

	$( function () {
		process();
		// Re-run whenever the mini-cart / cart / checkout re-renders.
		$( document.body ).on(
			'wc_fragments_refreshed wc_fragments_loaded added_to_cart updated_wc_div updated_checkout',
			function () { setTimeout( process, 50 ); }
		);
	} );

}( jQuery ) );
JS;
}

Hinweis: Dieses Snippet ist kein Bestandteil des Plugins – es richtet sich gezielt an WooFood-Nutzer und wird von SmartPixels als kostenloser Service bereitgestellt. Nutzung auf eigene Verantwortung; teste es zuerst in einer Staging-Umgebung.

Dieser Eintrag wurde veröffentlicht am Allgemein. Setze ein Lesezeichen auf den permalink.