Skip to content

Instantly share code, notes, and snippets.

@doubleedesign
Last active March 19, 2024 10:05
Show Gist options
  • Save doubleedesign/8936eb1194f5ca608e1325696b2546f6 to your computer and use it in GitHub Desktop.
Save doubleedesign/8936eb1194f5ca608e1325696b2546f6 to your computer and use it in GitHub Desktop.
Export selected data about the currently shown WooCommerce orders to a CSV file
<?php
/**
* Function to export the orders list to a CSV download (not stored anywhere)
* CSV construction based on https://gist.github.com/vrushank-snippets/4274500
* Dev notes:
* - This file is designed to be called via AJAX, with that function providing the order IDs.
* - To use this without AJAX you would just need to define $order_ids = wp_list_pluck($wp_query->posts, 'ID') instead,
* and define $filename as something appropriate here.
*/
function doublee_export_orders() {
$order_ids = json_decode($_POST['order_ids']);
$results = array();
// Check security nonce from AJAX request
if (!check_ajax_referer('exporter-nonce', 'security', false)) {
wp_send_json_error('Invalid security token sent.');
wp_die();
}
if(is_admin() && !empty($order_ids)) {
// For each order:
foreach ($order_ids as $order_id) {
$order = wc_get_order($order_id);
// Get data we want to export
$user_id = $order->get_user_id();
$user_name = get_user_meta($user_id, 'first_name', true).' '.get_user_meta( $user_id, 'last_name', true );
$products = doublee_get_order_products($order_id);
$products_ordered = implode(', ', $products); // Comma-separated list of product names
$order_date = $order->get_date_created()->getTimestamp();
$order_date_formatted = date( 'd/m/Y' );
$status = $order->get_status();
// Put it all into an array
$order_data = array(
'Order ID' => $order_id,
'Customer name' => $user_name,
'Product(s)' => $products_ordered,
'Order date' => $order_date_formatted,
'Status' => $status
);
// Add it to the $results array
$results[$order_id] = $order_data;
}
// Temp name for the CSV file
$filename = 'temp.csv';
// Set headers for the CSV file download
header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
header( 'Content-Description: File Transfer' );
header( "Content-type: text/csv" );
header( "Content-Disposition: attachment; filename={$filename}" );
header( "Expires: 0" );
header( "Pragma: public" );
// Open the file
$fh = fopen( 'php://output', 'w' );
// When we first begin, the file doesn't have the header row
$headerDisplayed = false;
// Loop through the data array and add rows to the file
foreach ($results as $data) {
// Add a header row if it hasn't been added yet
if (!$headerDisplayed) {
// Use the keys from $data as the titles
fputcsv($fh, array_keys($data));
$headerDisplayed = true;
}
// Put the data into the stream
fputcsv($fh, $data);
}
// Close the file
fclose($fh);
}
exit();
}
add_action('wp_ajax_doublee_export_orders', 'doublee_export_orders');
<?php
/**
* Add the export button to the Orders screen.
* Note: Use CSS to adjust the appearance and positioning of the button.
*
* @param $which 'top' or 'bottom'
*/
function doublee_add_order_export_button_in_admin($which) {
$screen = get_current_screen();
if(is_admin() && $screen->base == 'edit' && $screen->post_type == 'shop_order' && $which == 'top') {
echo '<div class="alignleft export">';
echo '<button id="export-orders-button" class="button">Export these orders</button>';
echo '</div>';
}
}
add_action('manage_posts_extra_tablenav', 'doublee_add_order_export_button_in_admin');
<?php
/**
* The export function is called via AJAX for a smooth admin experience, but has no awareness of what page it's on and hence the current query.
* This function makes the current query's order IDs available to the JavaScript file so the AJAX function can send which order IDs it wants to export.
* Here we also set a limit on how many can be exported at once, to prevent timeouts,
* and let the script know what page we're on for file naming purposes ($wp_query accounts for the number of items per page set in Screen Options).
* The other localised variables are here too because having wp_localize_script run more than once causes only the last one to be used.
*/
function doublee_make_current_order_ids_ajaxable() {
if(current_user_can('manage_woocommerce')) {
global $wp_query;
$query_limit = 50;
$all_ids = wp_list_pluck($wp_query->posts, 'ID');
$limit_exceeded = false;
if(count($all_ids) > $query_limit) {
$limit_exceeded = true;
}
$page = '1';
if($wp_query->query['paged'] > 1) {
$page = $wp_query->query['paged'];
}
wp_localize_script('order-export', 'doubleedesign', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'security' => wp_create_nonce('exporter-nonce'),
'order_ids' => array_slice($all_ids, '0', 50),
'export_limit' => $query_limit,
'export_limit_exceeded' => $limit_exceeded,
'result_page' => $page
));
}
}
add_action('wp', 'doublee_make_current_order_ids_ajaxable', 50);
jQuery(document).ready(function($) {
$('#export-orders-button').on('click', function(event) {
event.preventDefault();
var default_label = $(this).text();
// Check that there's actually orders to export before proceeding.
if(doubleedesign.order_ids.length === 0) {
alert('Please select some orders to export');
return;
}
// Show message if limit is exceeded (limit exists to prevent timeouts)
if(doubleedesign.export_limit_exceeded == true) {
alert('Please select a maximum of 50 orders to export at a time');
return;
}
// Set filename
var today = new Date();
var filename = 'Orders-' + today.toISOString().substring(0, 10) + '--Page-' + doubleedesign.result_page + '.csv';
// Add loading state
$(this).addClass('loading');
$(this).text('Exporting orders');
// Ajax function to run the export
$.ajax({
method: 'POST',
dataType: 'text',
url: doubleedesign.ajaxurl,
data: {
action: 'doublee_export_orders',
security: doubleedesign.security,
order_ids: JSON.stringify(doubleedesign.order_ids)
},
success: function (response) {
// Make CSV in the response downloadable by creating a blob
var download_link = document.createElement("a");
var fileData = ['\ufeff'+response];
var blobObject = new Blob(fileData,{
type: "text/csv;charset=utf-8;"
});
// Actually download the CSV by temporarily creating a link to it and simulating a click
download_link.href = URL.createObjectURL(blobObject);
download_link.download = filename;
document.body.appendChild(download_link);
download_link.click();
document.body.removeChild(download_link);
// Remove the export button's loading state
$('#export-orders-button').removeClass('loading').text(default_label);
},
error: function(response) {
console.log(response);
$('#export-orders-button').removeClass('loading').addClass('error').text('Error exporting');
},
})
})
});
<?php
/**
* Utility function to get the product names (not every order item - disregard product addons) in an order
* as Product ID => Name pairs
* @param $order_id
*
* @return array
*/
function doublee_get_order_products($order_id) {
$order = wc_get_order($order_id);
$items = $order->get_items();
// Each product format (which is a Product Addon) shows up as an order item.
// Get just the products by looping through and getting the product IDs
$product_ids = array();
foreach($items as $item) {
array_push($product_ids, $item->get_product_id());
}
// Only include each product once, not for every addon
$product_ids = array_unique($product_ids);
// Create an array of product ID => name pairs
$product_list = array();
foreach($product_ids as $product_id) {
$product = wc_get_product($product_id);
if($product) {
$product_list[$product_id] = $product->get_name();
}
}
// Return that array
return $product_list;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment