Sep 10 2010
Sep 10
Printer-friendly version

I was tasked recently with providing a client with the ability to export Ubercart orders to a .csv file formatted accordingly for their own internal reporting processes. No biggie, right?

Ubercart comes out of the box with an order management view that can be tweaked to our heart's content and integrates right nicely with Views Bulk Operations. Now, when I hear the words "view" and ".csv" in the same sentence nowadays I immediately go to ol' faithful: Views Bonus: Export.

The Views Bonus module comes with an export module that lets us modify any view so that it can be exported to various formats (including, you guessed it, CSV).

"Simple", I said to my account manager.

I've used Views Bonus on a number of sites in the past and have never had a major issue with it (and the majority of minor issues I've had with it can be attributed more to business requirements than limitations in the module). To implement you simply enable Views Bonus and the Views Bonus: Export modules, go to whatever view you want to export, add a new "Feed" display to that view, attach that display to your view's page display, and Viola! Done and done. So, you can understand the confidence I had when I told my account manager "2 hours" for the estimate (and I was even padding that number a little for the "just in case" factor).

I jumped into my site, installed and enabled the module, and setup my view all in less than a half-hour. Gravy. I go to my view, click on the big orange"CSV" icon and am prompted to download my report's .csv file. Just like ol' times. One more test to run and I can close this ticket. Ubercart's order management view (admin/store/order-management) utilizes Views Bulk Operations and - as such - its page view is a tabular form with checkboxes assigned to each row so you can select specific orders to perform bulk operations on. I checked a couple of random orders and clicked on my CSV icon once again. I opened up the freshly generated report, giddy to be rid of this ticket and look like a rock star for knocking it out in under an hour (a coder's virility is proportional to their ego, doncha know).

My giddiness was immediately crushed when the new report looked exactly like the first report, that is, it contained ALL of the orders, not just the ones I had checked.

Ok, focus. Let's do the first thing we always do in this situation: clear-cache, hard refresh, and try again...ya know, just 'cause (yeah, yeah, yeah I know, this really is only a viable thing to do when messing with menu items or the theme layer...but hey, if I had a dollar for every time that method has fixed weirdness...well, I could afford to outsource these bugs). Cache cleared, page refreshed, couple of orders checked, report generated again, no whammies, no whammies, big bucks, aaaaaaand STOP!


A breakdancing evil cherub minion spawn of 80's game show gold is dancing across my monitor. Yes, nothing has changed about the report. All records. Again.

Alright, check the view for anything missed.

Is the Feed display attached to the page display? Yes.

Does it have a unique path? Yes.

Are permissions set appropriately? Yes.

At this point, I believe the view is not the issue. All things check out there. So, the obvious answer at this point seems to be in Views Bonus: Export and it, for whatever reason, not respecting VBO's checkboxes. Sure enough, after digging around some issue queues and forums for a few minutes I stumble across a couple of others experiencing the same issue.

Right now, I am under the gun. I have about a half-hour left or I'm going to blow my original estimate and have to have "that" conversation with my account manager. No bueno. I ask myself a very serious question: "Am I gonna have to implement my own bulk operation?". Sure. Why not?

Having used VBO on a number of sites since it's 1.x beta days I already knew that it "auto-detects" Drupal actions as bulk operations. I also knew that any modules implementing hook_node_operations() or hook_user_operations() get included as bulk operations. Oh, but our Ubercart order management view isn't a "node" view, it's specific to Ubercart and doesn't get to play with hook_node_operations() and only has a very limited set of bulk operations, three of which are implemented by Ubercart. Wait. Say that again. Three of which are implemented by Ubercart. I'm giddy again. That means Ubercart has some code I can look at to get some idea of what I need to do to implement my own Ubercart order management bulk operation. Time to look at some code.

So, I head straight for the .module file in the uc_views submodule - uc_views_bulk_operations. I scroll down to the third function defined in the module and receive a boon of heavenly mana in the function's Docblock comment: "Implementation of hook_node_operations(). (rather, hook_order_operations, which is based off the other)"

I love you guys. No, really. It looks and acts just like hook_node_operations() but is specific to Ubercart orders. This is powerful stuff here. Here's what it looks like in code:

 * Implementation of hook_node_operations().
 *   (rather, hook_order_operations, which is based off the other)
function uc_views_bulk_operations_order_operations() {
  return array(
    'process_orders' => array(
      'label' => 'Move Orders to next State',
      'callback' => 'uc_views_bulk_operations_orders_process_orders',
      'disabled' => TRUE,
    'print_orders' => array(
      'label' => 'Print Orders',
      'callback' => 'uc_views_bulk_operations_orders_print_orders',
      'disabled' => TRUE,
    'delete_orders' => array(
      'label' => 'Delete Orders',
      'callback' => 'uc_views_bulk_operations_orders_delete_orders',
      'disabled' => TRUE,

Ok, so all I need to do is spin-up a custom module and implement hook_order_operations() and write a callback function for my bulk operation that will export "selected" orders to a .csv file. Awesome.

15 minutes to go.

I get a custom module going, and implement hook_order_operations() first:

  * Implementation of hook_order_operations().
function mymodule_order_operations() {
  return array(
    'export_orders' => array(
      'label' => 'Export Orders to CSV',
      'callback' => 'mymodule_export_orders',
      'disabled' => TRUE,

And, then the callback function which generates the .csv file (edited for brevity):

function mcndev_export_orders($orders) {
  // include this so we can use the uc_currency_format() function
  module_load_include('inc', 'uc_reports', 'uc_reports.admin');

  $header = array(

  $rows = array();

  foreach ($orders as $order_id) {
    $order = uc_order_load($order_id);

    // grab state abbreviations and country names
    $billing_state = db_result(db_query("SELECT zone_code
                                         FROM {uc_zones}
                                         WHERE zone_id = %d", $order->billing_zone));
    $shipping_state = db_result(db_query("SELECT zone_code
                                          FROM {uc_zones}
                                          WHERE zone_id = %d", $order->delivery_zone));
    $billing_country = db_result(db_query("SELECT country_name
                                           FROM {uc_countries}
                                           WHERE country_id = %d", $order->billing_country));
    $shipping_country = db_result(db_query("SELECT country_name
                                            FROM {uc_countries}
                                            WHERE country_id = %d", $order->delivery_country));

    $products = $order->products;

    foreach ($products as $product) {
      $rows[] = array(
        '"'. $order->order_id .'"',
        '"'. $order->created .'"',
        '"'. uc_currency_format($order->order_total) .'"',
        '"'. uc_currency_format($order->quote['rate']) .'"',
        '"'. $order->quote['method'] .'"',
        '"'. $order->billing_first_name .' '. $order->billing_last_name .'"',
        '"'. $order->billing_street1 .'"',
        '"'. $order->billing_street2 .'"',
        '"'. $order->billing_city .'"',
        '"'. $billing_state .'"',
        '"'. $order->billing_postal_code .'"',
        '"'. $billing_country .'"',
        '"'. $order->delivery_first_name .' '. $order->delivery_last_name .'"',
        '"'. $order->delivery_street1 .'"',
        '"'. $order->delivery_street2 .'"',
        '"'. $order->delivery_city .'"',
        '"'. $shipping_state .'"',
        '"'. $order->delivery_postal_code .'"',
        '"'. $shipping_country .'"',
        '"'. $product->title .'"',
        '"'. $product->qty .'"',
        '"'. uc_currency_format($product->price) .'"',
        '"'. $order->billing_phone .'"',
        '"'. $order->primary_email .'"',

  $csv = implode(',', $header) ."\n";

  foreach ($rows as $row) {
    $csv .= implode(',', $row) ."\n";

  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Content-Type: text/csv');
  header('Content-Disposition: attachment; filename="orders.csv"');

  die(print $csv);

I went back to the order management view and checked the VBO settings and my new custom bulk operation was there in the list. So far so good.

I enable it, save the view, and proceed to admin/store/order-management to check it out. Lo and behold there's a new button on the view that has my bulk operation's label printed on it. Moment of truth. I check a couple of orders and press the button and am prompted to download a .csv file. I open up my .csv and only the orders I had checked are showing. Success is sweet. With a few minutes to spare I pass it back to my account manager to review and get the thumbs up.

Ticket = closed.


Hope this may help you out there if you ever come across a situation where you may need to implement your own bulk operation for a view. Once you know where to start, it is satisfyingly simple to make it happen.

Until next time, happy coding.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web