Use first image of Woocommerce product gallery as post thumbnail

So I have a client who wanted to reuse Woocommerce Product gallery functionality somewhere else on the page, but didn’t needed to set a Featured Image for each individual product.

The culprit

Because the Woocommerce code checks if your product has a post thumbnail set, it won’t display the gallery if that’s empty.

https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/templates/single-product/product-thumbnails.php#L29

if ( $attachment_ids && $product->get_image_id() ) {

The solution

<?php

class FauxPostThumbnail {

    var $faux_product_image_id = false;
    
    public function register_hooks() {

        /**
         * Use first image in gallery as featured image when it's empty
         */
        add_filter('woocommerce_product_get_image_id', [ $this, 'maybe_overwrite_post_thumbnail_if_empty' ] );
        
        // Unset the first image from the gallery image ids array which it stored in $faux_product_image_id
        add_filter('woocommerce_product_get_gallery_image_ids', [ $this, 'maybe_unset_first_image_from_gallery' ]);
    }

    public function maybe_overwrite_post_thumbnail_if_empty( $value ){

        // If filled then return original value, because an attachment is selected
        if( ! empty( $value ) ) {
            return $value;
        }


        // Else we try to get the first image of the Gallery and return that as our product image
        global $product;

        $attachment_ids = $product->get_gallery_image_ids();

        if( ! empty( $attachment_ids ) ) {

            // Because if you would call $product->get_image_id() again, it would crash 
            $this->faux_product_image_id = $attachment_ids[0];

            return $attachment_ids[0];
        }

        // Return original image
        return $value;
    }


    public function maybe_unset_first_image_from_gallery( $attachment_ids ) {

        global $product;

        // If we have a filled Gallery and the product doesn't have a featured image, then we know we need to unset the first
        if( ! empty( $attachment_ids ) && ! \has_post_thumbnail( $product->get_id() ) ) {

            if( $this->faux_product_image_id ) {

                // Doesn't work because then the 2nd image becomes the first images and you have a duplicate image again
                // array_shift( $attachment_ids );
                
                // This doesn't work, because you get an error that says "cannot unset key 0"
                // if (($key = array_search($this->faux_product_image_id, $attachment_ids)) !== false) {
                //     unset($attachment_ids[$key]);
                // }

                // So we just loop the aray and don't add the faux_product_image_id to the $new list
                foreach( $attachment_ids as $key => $value ){
                    if( $value !== $this->faux_product_image_id ) {
                        $new[] = $value;
                    }
                }

                return $new;
            }
        }

        return $attachment_ids;
    }
}

Fallback image for product

This is also a nice way of having a more relevant fallback image for your product, instead of the default Woocommerce fallback image.