Search code examples
phpwordpresswoocommercemethodsproduct

Interlink WooCommerce products between them for cross sells settings


In WooCommerce, I would like:

  • if Product A is added in the cross sells of Product B, then Product B should also automatically be added in the cross sells of Product A.
  • if Product A is removed from the cross sells of Product B, then Product B should also automatically be removed from the cross sells of Product A.

This is the code that I have:

add_action('save_post_product', 'auto_link_cross_sells', 20, 2);

function auto_link_cross_sells($post_id, $post) {
    // Prevent infinite loop using a transient
    if (get_transient('auto_cross_sell_processing')) {
        return;
    }

    // Get the current product's cross-sell IDs
    $cross_sell_ids = get_post_meta($post_id, '_crosssell_ids', true);

    // If there are no cross-sell IDs, exit the function
    if (empty($cross_sell_ids) || !is_array($cross_sell_ids)) {
        return;
    }

    // Set a transient to prevent an infinite loop
    set_transient('auto_cross_sell_processing', true, 10);

    // Loop through each cross-sell product ID
    foreach ($cross_sell_ids as $cross_sell_id) {
        // Check if the cross-sell product ID is valid
        if (!get_post($cross_sell_id)) {
            continue;
        }

        // Get the existing cross-sell IDs for the cross-sell product
        $existing_cross_sells = get_post_meta($cross_sell_id, '_crosssell_ids', true);
        if (!is_array($existing_cross_sells)) {
            $existing_cross_sells = [];
        }

        // If the current product is not already a cross-sell of the cross-sell product, add it
        if (!in_array($post_id, $existing_cross_sells)) {
            $existing_cross_sells[] = $post_id;
            update_post_meta($cross_sell_id, '_crosssell_ids', $existing_cross_sells);
        }
    }

    // Remove the transient to allow the function to run again later
    delete_transient('auto_cross_sell_processing');
}

But it still doesn't do the job.


Solution

  • There are some missing things in your code, as what you are trying to do is more complicated than you thought.

    Also, since WooCommerce 3, you should better use WC_Product available methods than WordPress Post Meta functions, as WooCommerce uses custom database tables.

    Here we need to use a custom field to keep track of previous history, allowing to process removed Cross-Sell IDS.

    Try the following code instead, that should process and interlink cross-sells between products:

    add_action( 'woocommerce_process_product_meta', 'process_interlinked_product_cross_sells', 100 );
    function process_interlinked_product_cross_sells( $post_id ) {
        // Get current WC_Product object
        $product = wc_get_product( $post_id );
    
        // Get the current product cross-sell IDs
        $cross_sell_ids = (array) $product->get_cross_sell_ids();
        
        // Get current product cross-sell IDs previous history track
        $previous_cs_ids = (array) $product->get_meta('_previous_cs_ids');
    
        // Check differences related to history
        $differences_add    = array_diff($cross_sell_ids, $previous_cs_ids); 
        $differences_remove = array_diff($previous_cs_ids, $cross_sell_ids);
    
        // Added IDS case
        if ( count($differences_add) > 0 ) {
            // Loop through product cross-sell IDs (added)
            foreach ( $differences_add as $cross_sell_id ) {
                // get the WC_Product object from current cross sell ID
                $_product = wc_get_product( $cross_sell_id ); 
    
                if ( ! $_product ) continue;
    
                // Get cross-sell IDs for the cross-sell product
                $_cross_sell_ids = (array) $_product->get_cross_sell_ids();
    
                if ( ! in_array( $post_id, $_cross_sell_ids  ) ) {
                    $_cross_sell_ids[] = $post_id; // Add the initial current product Id to the array
                    $_product->set_cross_sell_ids( $_cross_sell_ids ); // Set updated cross-sells Ids
                    $_product->update_meta_data( '_previous_cs_ids', $_cross_sell_ids ?: '' ); // Update history
                    $_product->save(); // Sync and save
                }
            }
        }
    
        // Removed IDS case
        if ( count($differences_remove) > 0 ) {
            // Loop through product cross-sell IDs (removed)
            foreach ( $differences_remove as $key => $cross_sell_id ) {
                // get the WC_Product object from current cross sell ID
                $_product = wc_get_product( $cross_sell_id ); 
    
                if ( ! $_product ) continue;
    
                // Get cross-sell IDs for the cross-sell product
                $_cross_sell_ids = (array) $_product->get_cross_sell_ids();
                
                if ( in_array( $post_id, $_cross_sell_ids  ) ) {
                    // Remove the initial current product Id from the array
                    unset($_cross_sell_ids[$key]); 
                    
                    $_product->set_cross_sell_ids( $_cross_sell_ids ); // Set updated cross-sells Ids
                    $_product->update_meta_data( '_previous_cs_ids', $_cross_sell_ids ?: '' ); // Update history track
                    $_product->save(); // Sync and save
                }
            }
        }
    
        // Update current product history track
        if ( count($differences_add) > 0 || count($differences_remove) > 0 ) {
            $product->update_meta_data( '_previous_cs_ids', $cross_sell_ids ?: '' ); // Update history track
            $product->save();// Sync and save
        }
    }
    

    Tested and works.