Search code examples
phpjquerywoocommercegroupingproduct

Group WooCommerce downloads by product in a dropdown menu


I sell online courses in my website using WooCommerce. Each course(product) has number of downloadable files. Unfortunately, WooCommerce does not group each product downloads together and make a long list of available downloads. If someone buy several courses, there will be a very long and confusing list of downloads which is not user-friendly.

I am looking for some codes (PHP, Java, CSS) to group each product downloads together under a drop-down menu. This way if someone buy 7 courses, then in download page there will be seven dropdown lists and by clicking on each one, downloads of that specific product just appear.

I used below PHP code. It groups downloads together but there is no drop-down menu.

/**
 * Group Downloadable products by product ID
 *
 * @param array $downloads
 * @return array
 */
function prefix_group_downloadable_products( array $downloads ) {
    $unique_downloads = [];

    foreach ( $downloads as $download ) {
        $list = [
            'download_url' => $download['download_url'],
            'file_name'    => $download['file']['name']
        ];

        if ( array_key_exists( $download['product_id'], $unique_downloads ) ) {
            $unique_downloads[ $download['product_id'] ]['list'][] = $list;
            continue;
        }

        $data = $download;
        $data['list'] = [ $list ];
        $unique_downloads[ $download['product_id'] ] = $data;
    }

    return $unique_downloads;
}

add_filter( 'woocommerce_customer_get_downloadable_products', 
'prefix_group_downloadable_products' );


/**
 * Show number list of downloadable files for group product
 * 
 * @param array $download
 * @return void
 */ 
function prefix_downloads_column_download_file( array $download ) {
    $lists = $download['list'];

    if ( empty( $lists ) ) {
        _e( 'No Download Files', 'storefront' );
        return;
    }

    echo '<ol>';

    foreach ( $lists as $list ) {
        echo '<li>';
        echo '<a href="' . esc_url( $list['download_url'] ) . '" class="woocommerce-MyAccount-downloads-file">';
        echo esc_html( $list['file_name'] );
        echo '</a></li>';
    }

    echo '</ol>';
}

add_action( 'woocommerce_account_downloads_column_download-file', 'prefix_downloads_column_download_file' );

Also, this code causes an error appears in "my orders" and "order report" page, preventing to display downloads there. The error is this:

Warning: Undefined array key "list" in /home/beatop/domains/sdrecords.ir/public_html/wp-content/themes/hello-theme-child-master/functions.php on line 156 No Download Files

The line 156 is related to this: $lists = $download['list'];

How to rectify this error and add drop-down menu?


Solution

  • You need to check first that $download['list'] exist, to avoid that issue.

    To get a dropdown of downloads grouped by product, some changes and additions are needed (JavaScript/jQuery is required).

    Note (update): On email notifications, we keep the Downloads table as it is by default, as the dropdown can't work.

    The following code will handle this dropdown everywhere, in the front end:

    // Group downloads data by product
    add_filter( 'woocommerce_customer_get_downloadable_products', 'prefix_group_downloadable_products', 10, 2 );
    add_filter( 'woocommerce_order_get_downloadable_items', 'prefix_group_downloadable_products', 10, 2 );
    function prefix_group_downloadable_products( $downloads = array(), $order = null ) {
        // Only on front-end
        if ( is_admin() || ! is_wc_endpoint_url() ) { 
            return $downloads;
        }
    
        $unique_downloads = []; // Initializing
    
        foreach ( $downloads as $download ) {
            $list = [
                'download_url' => $download['download_url'],
                'file_name'    => $download['file']['name']
            ];
    
            if ( array_key_exists( $download['product_id'], $unique_downloads ) ) {
                $unique_downloads[ $download['product_id'] ]['list'][] = $list;
                continue;
            }
    
            $data = $download;
            $data['list'] = [ $list ];
            $unique_downloads[ $download['product_id'] ] = $data;
        }
        return $unique_downloads;
    }
    
    // Display a dropdown of the downloads by product
    add_action( 'woocommerce_account_downloads_column_download-file', 'customize_downloads_columns' );
    function customize_downloads_columns( $download = array() ) {
        $lists = isset($download['list']) ? $download['list'] : array();
    
        if ( empty( $lists ) ) {
            _e( 'No Download Files', 'storefront' );
            return;
        }
    
        echo '<select class="downloads-dropdown">
        <option value="">'. __(' Select download', 'storefront' ) .'</option>';
    
        foreach ( $lists as $list ) {
            printf( '<option value="%s">%s</option>', 
            esc_url($list['download_url']), esc_html($list['file_name']) );
    
        }
        echo '</select>';
    }
    
    // Javascript: Trigger the download when selecting a file in the dropdown
    add_action( 'wp_head', 'trigger_download_from_selected_file_js' );
    function trigger_download_from_selected_file_js() {
        if ( is_wc_endpoint_url('downloads') || is_wc_endpoint_url('view-order') || is_wc_endpoint_url('order-received') ) {
            wc_enqueue_js("$(document.body).on('change', 'select.downloads-dropdown', function(){
            if( $(this).val() != '' ) {
                window.location.href = $(this).val();
            }
            });");
        }
    }
    

    You will get something like (in My account > Downloads page):

    enter image description here

    In My account > View Order and in Order received (thankyou) pages:

    enter image description here

    The downloads table on email notifications stays unchanged (default WooCommerce behavior):

    enter image description here


    Addition for email notifications (optional)

    Replace the downloads table, with a text linked to My Account "downloads" section:

    // Remove the downloads table
    add_action( 'woocommerce_init', function(){
        remove_action( 'woocommerce_email_order_details', array( WC()->mailer(), 'order_downloads' ), 10 );
    });
    
    // Email notifications: Display a text linked to My Account downloads
    add_action( 'woocommerce_email_order_details', 'display_linked_text_to_my_account_downloads', 9, 4 );
    function display_linked_text_to_my_account_downloads( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) {
        $show_downloads = $order->has_downloadable_item() && $order->is_download_permitted() && ! $sent_to_admin && ! is_a( $email, 'WC_Email_Customer_Refunded_Order' );
        
        if ( ! $show_downloads ) {
            return;
        }
    
        $text_align    = is_rtl() ? 'right' : 'left';
        $downloads_url = wc_get_endpoint_url('downloads', '', get_permalink( get_option('woocommerce_myaccount_page_id') ) );
        ?>
        <h2 class="woocommerce-order-downloads__title"><?php esc_html_e( 'Downloads', 'woocommerce' ); ?></h2>
        <table class="td" cellspacing="0" cellpadding="6" style="width: 100%; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; margin-bottom: 40px;" border="1">
            <tr>
                <td class="td" style="text-align:<?php echo esc_attr( $text_align ); ?>;">
                    <?php printf( __('Downloads are available in your Account %s.', 'storefront'),
                    '<a href="' . $downloads_url . '" class="button">'. __('"Downloads" section', 'storefront') .'</a>'); ?>
                </td>
            </tr>
        </table>
        <?php 
    }
    

    enter image description here