Plugin for showing cross-sells on the single product page in WooCommerce
The code looks ok to me from a security point of view. You're not accessing the database directly and it looks like you are escaping the output ( assuming that the template part does the same ).

A couple of suggestions:

Instead of looking for the filename in the list of active plugins, use function_exists or class_exists to check to see if the plugin is enabled by looking for a function or class defined by the plugin. The reason being that options can be cached and filenames can be changed. Function/Class names rarely change and it's much cleaner looking.

if ( class_exists( 'WC_Query' ) ) {
    // Your plugin code.
}


Check to make sure that you're actually getting data returned in $crosssells = get_post_meta( get_the_ID(), '_crosssell_ids',true); before using it in the query. If there is no data, there's no point in querying the database.

// Put this before the query
$crosssells = get_post_meta( get_the_ID(), '_crosssell_ids',true);    
if ( empty( $crosssells ) {
    return;
}


WP_Query will always return something and so if( $products ) will always be true. Use if( $products-&gt;have_posts() ) instead. This will only return true if there were posts returned from the query.

To answer your question, the while loop is fine. That's what WordPress core and the bundled theme use.

The only other suggestion I would make is for performance. Add a couple of items to your query args:

    $args = array(
        'post_type' =&gt; 'product',
        'posts_per_page' =&gt; 4,
        'post__in' =&gt; $crosssells,

        /**
         * If you're not paginating, this skips MYSQL_CALC statments
         */
        'no_found_rows' =&gt; true,

        /**
         * Skips updating meta cache
         */
        'update_post_meta_cache' =&gt; false,

        /**
         * Skips updating term cache.
         */            
         'update_post_term_cache' =&gt; false,
    );


Hope it helps!