Using Ubercart to sell files: ui improvements and creating file feature programmatically
uc_file module that allows selling files in Ubercart is definitely not perfect in terms of API, but it is a nice piece of functionality.
We’ve just finished creating one e-book store which has product-import feature.
Each product has multiple formats (pdf, epub, rtf) - some products can have pdf+epub, some can have just pdf, others have all three formats attached.
On product page, we wanted to show nice block like this:
![]()
where pdf and doc are just text for regular users, and download links for users that already bought this book.
All the code was created for Drupal 7 + Ubercart 7.x-3.x
Creating file feature programmatically
Let’s start from
Creating ubercart product node
<?php
$title = 'My test product';
$price = 100;
$sku = 'testSKU';
$descr = 'Product description is here';
$node = new StdClass();
$node->type = 'product';
node_object_prepare($node);
$node->language = LANGUAGE_NONE;
$node->title = $title;
// Set Ubercart variables
$node->model = $sku; // the SKU is a required field, so I generated a SKU based on the node title
$node->list_price = $price;
$node->cost = $price;
$node->sell_price = $price;
// populate some required ubercart fields
foreach (array('width', 'height', 'length', 'weight', 'weight_units', 'length_units', 'shippable') as $attr) {
$node->{$attr} = 0;
}
// get rid of "The quantity cannot be zero." errors
foreach (array('pkg_qty', 'default_qty') as $attr) {
$node->{$attr} = 1;
}
$node->body[LANGUAGE_NONE][0]['value'] = $descr; // set body field
node_save($node);
?>Attaching file feature
Now we have $node object ready for adding files.
Make sure that uc_file module is enabled, and you’ve configured directory for files, and put files there (by uploading to ftp/sftp)
Here is the code:
<?php
$filename = $node->model; // in my case, filename = product sku + extension
$exts = array('zip', 'pdf'); // the code below expects that there are files testSKU.pdf and testSKU.zip in my ubercart files directory
uc_file_refresh(); // copy files from file system to ubercart files table in database
foreach ($exts as $ext) {
$ext = trim($ext);
$fullname = $filename . '.' . $ext;
$file = uc_file_get_by_name($fullname);
if (!empty($file)) {
$existing_file = db_query("SELECT fpid FROM {uc_file_products} WHERE
fid=:fid AND model=:sku", array(':fid' => $file->fid, ':sku' => $product->model))->fetchField();
if (!$existing_file) {
$feature = array(
'nid' => $product->nid,
'fid' => 'file',
'description' => $product->title,
);
drupal_write_record('uc_product_features', $feature);
$file_product = array(
'fid' => $file->fid,
'pfid' => $feature['pfid'],
'filename' => $file->filename,
'model' => $product->model,
'shippable' => $product->shippable,
);
drupal_write_record('uc_file_products', $file_product);
}
}
}
?>That’s it!
Now put these two pieces of code together to your module and you should get product with attached files.
Showing available formats and downloadable list of files on product node
It’s surprising that Ubercart doesn’t have such functionality in core - the only interface it has for user is a separate page with complete list of all downloads available for him on the website.
In my project, I just put the code below to
and then in node—product.tpl.php I write
<?php
echo $files_table;
?>Here is the code:
<?php
$node = $variables['node']; // that looks like this because the code was used
// in preprocess function - feel free to replace by other node loading code
$variables['files_table'] = _uc_file_user_downloads_by_sku($GLOBALS['user'], $node->model);
// user doesn't have available downloads, get plain text list of extensions
if (!$variables['files_table']) {
// get files list for product
$files = db_query("SELECT f.filename FROM {uc_file_products} p INNER JOIN {uc_files} f ON p.fid=f.fid WHERE p.model = :sku", array(':sku' => $node->model))->fetchCol();
foreach ($files as &$file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
$file = '<span class="' . $ext . '">'. $ext .'</span>';
}
$variables['files_table'] = '<div class="available-files">'.theme('item_list', array('items' => $files, 'title' => t('File formats available for download'))).'</div>';
}
// Function inspired by code in uc_file.module
function _uc_file_user_downloads_by_sku($account, $sku) {
$files = db_query(
"SELECT u.granted, f.filename, u.accessed, u.addresses, p.description, u.file_key, f.fid, u.download_limit, u.address_limit, u.expiration FROM {uc_file_users} as u ".
"LEFT JOIN {uc_files} as f ON u.fid = f.fid ".
"LEFT JOIN {uc_file_products} as p ON p.pfid = u.pfid WHERE uid = :uid AND p.model=:sku", array(':uid' => $account->uid, ':sku' => $sku)
)->fetchAllAssoc('fid');
$rows = array();
foreach ($files as $file) {
$row = count($rows);
$download_limit = $file->download_limit;
// Set the JS behavior when this link gets clicked.
$onclick = array(
'attributes' => array(
'onclick' => 'uc_file_update_download('. $row .', '. $file->accessed .', '. ((empty($download_limit)) ? -1 : $download_limit) .');', 'id' => 'link-'. $row
),
);
$ext = pathinfo($file->filename, PATHINFO_EXTENSION);
// Expiration set to 'never'
if ($file->expiration == FALSE) {
$file_link = '<span class="'. $ext .'">' . l($ext, 'download/'. $file->fid .'/'. $file->file_key, $onclick) . '</span>';
}
// Expired.
elseif (time() > $file->expiration) {
$file_link = $ext;
}
// Able to be downloaded.
else {
$file_link = '<span class="'. $ext .'">' . l($ext, 'download/'. $file->fid .'/'. $file->file_key, $onclick) . '</span>' .' ('. t('expires on @date', array('@date' => format_date($file->expiration, 'custom', variable_get('uc_date_format_default', 'm/d/Y')))) .')';
}
$rows[] = $file_link;
}
if (!empty($rows)) {
$output = theme('item_list', array('title' => t('Files available for download'), 'items' => $rows));
$output .= '<div class="form-item"><p class="description">'.
t('Once your download is finished, you must refresh the page to download again. (Provided you have permission)') .
'</p></div>';
return $output;
} else {
return FALSE;
}
}
?>Forgive me that the code is a bit ugly, it can (and needs to!) be refactored and improved.
May be someone is interested in such functionality as module? Tell me by leaving a comment, I will be glad to create a module if it will be useful for someone.
Anton Sidashin
senior developer, Pixeljets co-founder
I'm a web developer specializing in PHP and Javascript, and Drupal, of course. I'm building Drupal projects since 2005, and I was working as full-time senior engineer in CS-Cart for a while, building revolutionary e-commerce software. In my free time, I enjoy playing soccer, building my body in gym, and playing guitar.
Drupal.org ID: restyler
Comments
nice, that's was something I was looking for, I still have to try it, than i'll let you know how it works.
Hey
Just wondering if I could grab this write up for a tutorial section on my Ubercart promo site. I'd give you full credit and link back to this site. I'm also looking for developers who are familiar with Ubercart for the Dev For Hire section on my site.
Regards,
end user
Hi! yes, you can :)
Post new comment