Search form

Search

Ubercart: Product Imports

Submitted by dziemecki on Tue, 07/13/2010 - 14:31

When a client asked me to build him an eCommerce site that allowed him to occasionally re-upload his product catalog, it seemed like Drupal and UberCart would be the obvious solution. Both are well established projects with tons of high-quality free plug-in modules. It was a safe bet there was a collection of downloads I could assemble like Lego bricks into the perfect site. But, oddly enough, it turns out that the product upload capability was simply not supported.

I was a bit surprised at that, as I assumed this was the sort of request that a lot of vendors might have. Anyone with a large number of products, particularly anyone who is already tracking that data in some other format, would not want to have to maintain that process by hand. None the less, the gap existed, and it was up to me to find a way around it.

I should acknowledge that there are modules for uploading nodes in Drupal, which is essentially what a product is. Unfortunately, these tools have far too many 3rd party dependencies, are too thinly supported, and are too difficult to integrate to be considered ideal solutions. So I set out to build my own.

Now for anyone reading this, hoping for a solution to their problem, let me tell you exactly where this is going. I AM going to show you how I built the import solution. And I AM going to give you my code, an UberCart (v2.2 for Drupal 6) module, for you to freely use (and maintain). But you ARE going to have to tweak it to work in your environment. If there is anything I learned in this process, there is no simple way to do this that will work for everyone.

To start off, I needed to provide my client an interface by which they could delete old products and add new ones. To keep it as simple as possible, I consolidated those interfaces. Step one was to create a link to the tool using the menu hook.

As you can see, I chose to name my module uc_prodimport, or “UberCart Product Import”, to match the normal UberCart naming convention. In the $items array primary index value, I set the menu item to appear under the pre-existing Products adminstration structure. I used the “page arguments'” index value to make the link call the uc_prodimport_form function I am about to build.

This is a form with three elements - A checkbox to choose whether or not to purge the current database, a Drupal file element to select the upload file, and a submit button. The $form['#redirect'] = array('admin/store/products'); setting sends the page back to the normal Product lists after the purge/upload to see the effects of your change.

The default action for a form submission in Drupal is to execute a function named “module_formname_submit. The first thing the function does is import the data file, if it exists. I’ve already documented that process in detail, so I’ll move on to the next step - the purge. Here, a function I named uc_prodimport_purge_nodes is called. Let me jump out to that function.

The first thing to note is the $amt = 8000; line. I found that a large database could choke the web server, which could cause the script to fail in an unpredictable manner. The simplest solution was to set how many products I would try to delete at once to something I knew wouldn’t be a problem, and then run it multiple times, if necessary.

Next I select an array of node IDs of the appropriate type and count, then start to loop through the nodes, invoking the Drupal “node_delete” function to cleanly remove them from the site. Where ever possible, I used Drupal functions rather than straight SQL to avoid data corruption issues. Then I TRUNCATE the product table to quickly and cleanly empty them. This is a very fast operation that essential drops and then recreates the tables, but if you’re not running MySQL, it might not work with your database. Note the reference to uc_quotes. If you aren't using the module, you'll obviously not need this line.

Back to the form submit, I next call the meat of the module, the “insert” function.

This function starts by importing the file and assigning it to an array. That process was discussed previously and is in the source, anyway. The next step is to make sure there are the right number of columns in the source data, which is cheap way to make sure you didn’t upload the wrong file. Then I pop off the top row because, in my input file, this is just a title row to make it easier to read the import file. Finally, I create a new $node object, assign it the values from my rows array, and insert the row into the database with the Drupal uc_prodimport_insert function. This invokes the node insert hook and makes sure the appropriate add-in tables, like those associated with UberCart’s Product module, are correctly updated.

This returns the inserted rows count to the calling submit function to use in feedback messaging to, for instance, ensure that you got more than 10 rows out of a 5000 line file. At this point, the product list loads, and you can see the results of your efforts.

And that’s basically it. I encourage you to tweak the attached code to match your own needs and give it a run. As with any other code I post, your mileage may vary, but this implementation works for me. Good luck!

Comments

I got a request for a sample import file to be used with the code, so I added "sample.csv_.7z" to the original post. A couple things to be aware of:

1. The contents of the file are imported as part of the uc_prodimport_read_file function, with each field inserted into the array that is passed to the uc_prodimport_import_new function.

2. This being PHP, the first column in the CSV will be $aProd[0], and the last will be $aProd[x-1].

3. You can ignore file content, as I have in this example, by simply not assigning the value to a node element. For example, my sample file has a "shipping type" at $aProd[17], but I chose to hard-code that value since it never changed. If I were tidier, I'd remove it entirely from the CSV.

4. If you make a change to the CSV format, make sure to update the column count test in uc_prodimport_import_new. If you get an error message that says, "Unexpected column count on row in import file!", well, that's what it means.

You save the day with posting this module to public. So Big Thanks for that! It's realy a must have module if you run a webshop.

I personaly have only one problem:

When importing products with your module, the add to queue button appear in different language, what I need.
I modified the
$node->language = 'en'; // constant to
$node->language = 'hu'; // constant and it's importing well in the SQL database.

So as you can see i need hungarian add to queue button :), but it appears in english.
If i make a product manualy then it's everything OK, i have my own buttons.

The easiest way to achieve this in Drupal is to use the Localization API. Enable the "Locale" module in your Admin screen, and set up a translation of "Add to queue" to "Adjunk hozzá a sor" (just an online translation). Then make sure to wrap any words you want translated in "t()" functions.

... on your definition of "small". Send me an email (see the link by my picture) or a PM and we can take this offline.

On another note, I'm glad you found this to be a good solution. I personally think it's a bit of kludge, and it shouldn't have to exist, but I guess it's not enough of a priority to make the features list for the core UC distribution.

Thanks a lot for posting this module, it helped me a lot with my adapted prodimport module.
In the code you use the module uc_imageurl and this sounds like an interesting one. Unfortunately I wasn't able to find it, could you point me in the right direction (or mail me the source).

I was wondering could this be translated for Drupal 7? Where can I look for it (assuming it would not work for D7?)
Also if this could be expanded to support adding/importing taxonomy/categories and options as discovered in the CSV that would be great. Are there a couple of API calls I could be looking into to do this?

I'm no longer working this project, so I couldn't give you a definitive answer. Since my code changes are all stand-alone modules, I don't see why they couldn't be ported to the latest versions, however. Just crack open the code and look at what the "modern" equivalents" are. I can't imagine they've changed *that* much.

About

I'm Dan Ziemecki, the author of this site and a technology professional working in the Atlanta area. "Excellent Cruft" is a journal wherein I log the lessons of my various projects (before I forget them) in an effort to give back to the community. Feel free to use anything you find here.