WooCommerce – group downloads by product

In Woocommerce(*My Account page) I can see now an unordered list with all the downloads available, like:

<ul class="digital-downloads">
  <li><a href="#">Product 1 - File</a></li>
  <li><a href="#">Product 1 - Another File</a></li>
  <li><a href="#">Product 2 - File</a></li>
  <li><a href="#">Product 2 - Another File</a></li>
  <li><a href="#">Product 3 - File</a></li>
  <li><a href="#">Product 3 - Another File</a></li>
</ul>  

How can I group the downloads by product?, like:

Read More
<ul class="digital-downloads">
  <li>
    <span>Product 1</span>
    <ul>
      <li><a href="#">File</a></li>
      <li><a href="#">Another File</a></li>
    </ul>
  </li>

  <li>
    <span>Product 2</span>
    <ul>
      <li><a href="#">File</a></li>
      <li><a href="#">Another File</a></li>
    </ul>
  </li>

  <li>
    <span>Product 3</span>
    <ul>
      <li><a href="#">File</a></li>
      <li><a href="#">Another File</a></li>
    </ul>
  </li>
</ul> 

The code from my theme/woocommerce/my-account/my-downloads.php:

<ul class="digital_downloads">
    <?php foreach ( $downloads as $download ) : ?>
        <li>
            <?php
                do_action( 'woocommerce_available_download_start', $download );

                echo apply_filters( 'woocommerce_available_download_link', '<a href="' . esc_url( $download['download_url'] ) . '">' . $download['download_name'] . '</a>', $download );

                do_action( 'woocommerce_available_download_end', $download );
            ?>
        </li>
    <?php endforeach; ?>
</ul>

Related posts

Leave a Reply

3 comments

  1. I know, I am answering this question very late but I am posting an answer for this just in case anyone else is looking out for the answer.

    Please create a child theme and in that child theme create file /woocommerce/my-account/my-downloads.php. Add following content in that file

    <?php
    /**
     * My Orders
     *
     * Shows recent orders on the account page
     *
     * @author      WooThemes
     * @package     WooCommerce/Templates
     * @version     2.0.0
     */
    if ( !defined( 'ABSPATH' ) ) {
        exit;
    }
    
    function wdm_print_download_file_name( $download, $product_meta ) {
        if ( is_numeric( $download['downloads_remaining'] ) )
            echo apply_filters( 'woocommerce_available_download_count', '<span class="count">' . sprintf( _n( '%s download remaining', '%s downloads remaining', $download['downloads_remaining'], 'woocommerce' ), $download['downloads_remaining'] ) . '</span> ', $download );
    
        echo apply_filters( 'woocommerce_available_download_link', '<a href="' . esc_url( $download['download_url'] ) . '">' . $product_meta[$download['download_id']]['name'] . '</a>', $download ); //Print file name
    }
    
    if ( $downloads = WC()->customer->get_downloadable_products() ) :
        ?>
    
        <h2><?php echo apply_filters( 'woocommerce_my_account_my_downloads_title', __( 'Available downloads', 'woocommerce' ) ); ?></h2>
    
        <ul class="digital-downloads">
            <?php
            $all_product_ids = array();
            $all_download_ids = array();
            $download_ids_used_till_now = array();
            foreach ( $downloads as $download ) :
                ?>
    
                <?php
                do_action( 'woocommerce_available_download_start', $download );
    
                if ( !in_array( $download['product_id'], $all_product_ids ) ) { //Check if current product id is already there in $all_product_ids array. If it goes in this loop, that means it is new product id
                    $product_meta = get_post_meta( $download['product_id'], '_downloadable_files', true ); //All download ids of the product
    
                    $all_product_ids[] = $download['product_id']; //Push Current download's Product id to an array
    
                    $all_download_ids = array_keys( $product_meta ); //Get all download ids of the current product
    
                    echo '<li><span>' . get_the_title( $download['product_id'] ) . '</span><ul>'; //Print product name
    
                    $download_ids_used_till_now[] = $download['download_id']; //add download id to an array
    
                    echo '<li>';
                    wdm_print_download_file_name( $download, $product_meta );
                    echo '</li>';
    
                    $check_if_this_is_last_element = array_values( array_diff( $all_download_ids, $download_ids_used_till_now ) );
    
                    if ( empty( $check_if_this_is_last_element ) ) { //This is last download id of a product
                        echo '</ul></li>'; //close ul and li tags
                    }
                } else { //product id is already in use.
                    $check_if_this_is_last_element = array_values( array_diff( $all_download_ids, $download_ids_used_till_now ) );
    
                    if ( isset( $check_if_this_is_last_element[0] ) && $download['download_id'] == $check_if_this_is_last_element[0] ) { //This is last download id of a product
                        echo '<li>';
                        wdm_print_download_file_name( $download, $product_meta );
                        echo '</li>';
    
                        echo '</ul></li>'; //close ul and li tags
    
                        unset( $download_ids_used_till_now );
                        unset( $all_download_ids );
                    } else { //There are few more download ids
                        $download_ids_used_till_now[] = $download['download_id'];
    
                        echo '<li>';
                        wdm_print_download_file_name( $download, $product_meta );
                        echo '</li>';
                    }
                }
                do_action( 'woocommerce_available_download_end', $download );
                ?>
    
            <?php endforeach; ?>
        </ul>
    
        <?php endif; ?>
    

    Above code is tested with WooCommerce 2.1.12.

  2. The code above seemed to close out the child list too early. So if I had two downloads under one product it would display as

    <ul class="digital-downloads">
      <li>
        <span>Product 1</span>
          <ul>
            <li><a href="#">Product 1 File 1</a></li>
          </ul>
      </li>
      <li><a href="#">Product 1 File 2</a></li>
    </ul>
    

    Instead I just changed the html markup to divs. This might not be semantically as correct but got the job done and was more simple to code.

    function wdm_print_download_file_name( $download, $product_meta ) {
    if ( is_numeric( $download['downloads_remaining'] ) )
        echo apply_filters( 'woocommerce_available_download_count', '<span class="count">' . sprintf( _n( '%s download remaining', '%s downloads remaining', $download['downloads_remaining'], 'woocommerce' ), $download['downloads_remaining'] ) . '</span> ', $download );
    
    echo apply_filters( 'woocommerce_available_download_link', '<a href="' . esc_url( $download['download_url'] ) . '">' . $product_meta[$download['download_id']]['name'] . '</a>', $download ); //Print file name
    }
    
    if ( $downloads = WC()->customer->get_downloadable_products() ) :
    ?>
    
    <h2><?php echo apply_filters( 'woocommerce_my_account_my_downloads_title', __( 'Available Downloads and Videos', 'woocommerce' ) ); ?></h2>
    
    <div class="digital-downloads">
        <?php
        $all_product_ids = array();
        $all_download_ids = array();
        foreach ( $downloads as $download ) :
            ?>
    
            <?php
            do_action( 'woocommerce_available_download_start', $download );
    
            if ( !in_array( $download['product_id'], $all_product_ids ) ) { //Check if current product id is already there in $all_product_ids array. If it goes in this loop, that means it is new product id
                $product_meta = get_post_meta( $download['product_id'], '_downloadable_files', true ); //All download ids of the product
    
                $all_product_ids[] = $download['product_id']; //Push Current download's Product id to an array
    
                $all_download_ids = array_keys( $product_meta ); //Get all download ids of the current product
    
                echo '<div class="download-title">' . get_the_title( $download['product_id'] ) . '</div>'; //Print product name
    
            } else { 
                //product id is already in use.
            }
    
            echo '<div class="download-product">';
            wdm_print_download_file_name( $download, $product_meta );
            echo '</div>';
    
            do_action( 'woocommerce_available_download_end', $download );
            ?>
    
        <?php endforeach; ?>
    </div>
    <?php endif; ?>
    

    This was done in WooCommerce 2.5.2

  3. i fix

    Change
    the line

                if ( isset( $check_if_this_is_last_element[0] ) && $download['download_id'] == $check_if_this_is_last_element[0] )
    

    to

                if ( isset( $check_if_this_is_last_element[1] ) && $download['download_id'] == $check_if_this_is_last_element[1] )