Amazon Wish List Synchronizer


According to department stores, Christmas is fast approaching. This got me thinking about the fact that every year I scramble to assemble a list of things to put on my Christmas list. Last year, I vowed to start an Amazon wish-list to keep track of things I liked or wanted as the year progressed. I kept to my word but I also found a number of things throughout the year that weren't available on Amazon and I started thinking about how I could add them to my list (most notably this sweatshirt). This is how I built a simple module to solve my problem.

The Problem

Display an Amazon wish-list along-side items from other online stores throughout the web.

The Solution

After playing around with Jeff Eaton's fantastic Amazon Module I realized that wish list functionality was something that wouldn't be terribly difficult to do give Amazon's API. My answer is to create a content type that has an amazon field (provided by the Amazon Module and to then create a module that could be triggered by a cron job that would fetch the list from Amazon, check to see which products had already been imported, import any that had not been and finally check to see which had previously been imported that were no longer on this wishlist (allowing for automatic removal). It came together pretty cleanly easily as described below:

The Code

This is the code for amazon_wishlist_synchronizer.module. It was written for Drupal 6 but none of the functions are D6 specific so I it should work perfectly in D5 as well. It does use a SimpleXML function, however, so it will only work with PHP5. The module is also attached at the bottom of the page (though the attached module has fewer comments).

Warning: There is a now a known issue with this code. It worked perfectly for several days before it started reporting an error. I have disabled the module on this site and will update this post when I have the time to figure out what the heck is wrong.


Building a request

The hardest part of this section was actually finding the appropriate documentation. Amazon has some great docs but their web services site is a real pain to navigate. Finding this document really made things easier. Here is the function for building the web services request url:

  1. <?php
  2. /**
  3.  *  Build the Amazon request
  4.  */
  5. function _amazon_wishlist_synchronizer_build_request($subscription_id, $wishlist_id, $page = 1) {
  6.   // we start with the appropriate xml web services url
  7.   $request = 'http://webservices.amazon.com/onca/xml?Service=AWSECommerceService';
  8.   // we add the subscription_id
  9.   $request .= "&SubscriptionId=$subscription_id";
  10.   // specify that we are looking up a list and the kind of list
  11.   $request .= '&Operation=ListLookup';
  12.   $request .= '&ListType=WishList';
  13.   // we enter the specific wishlist_id
  14.   $request .= "&ListId=$wishlist_id";
  15.   // we specify what information to return
  16.   $request .= '&ResponseGroup=ListInfo,ListItems';
  17.   // we specify the page of the list (paging is unavoidable with longer lists)
  18.   $request .= "&ProductPage=$page";
  19.   return $request;
  20. }
  21. ?>

Get and convert the XML data

If you've never used PHP 5's SimpleXML before you're in for a treat! I hadn't and expected to have to jump through some hoops in order to perform the necessary DOM traversal but it wasn't necessary! using simlexml_load_string() you can convert a string of xml directly into a PHP object. At that point it couldn't be any easier to work with! Here's the function that fetches the xml, parses it and returns a PHP object:

  1. <?php
  2. /**
  3.  *   Fetch the xml and turn it into an object
  4.  * @param $request
  5.  *   the url of the xml to parse
  6.  * @return $parsed_xml
  7.  *   a PHP object of the Amazon data
  8.  */
  9. function _amazon_wishlist_synchronizer_get_xml($request) {
  10.   // Take the request and fetch the file contents retrieving it as a string
  11.   $response = file_get_contents($request);
  12.   if (!$response) {
  13.   // Report any errors getting data
  14.     drupal_set_message('Error retrieving Amazon data.', 'error');
  15.     return array('error' => 'Error loading Amazon XML data.');
  16.   }
  17.   // Use SimpleXML to convert the string into a native php object
  18.   $parsed_xml = simplexml_load_string($response);
  19.   // Return the php object
  20.   return $parsed_xml;
  21. }
  22. ?>

Synchronizing the Data

Next we need to take that data and synchronize it with the server. This is hard coded for my setup which includes; a content type called "product" (which would cause problems if I ever installed an e-commerce solution) which has a taxonomy term that specifies "wish list" and an Amazon field (provided by the Amazon module) called "field_amazon_asin".

  1. <?php
  2. /**
  3.  *  Synchronize the wish list
  4.  * @param $subscription_id
  5.  *   your Amazon subscription Id
  6.  * @param $wishlist_id
  7.  *   the id of the wishlist you are going to process
  8.  */
  9. function amazon_wishlist_synchronizer_sync_wish_list($subscription_id = "075W___________", $wishlist_id = "1AG3608ZU9F9R") {
  10.   // set initial variables to track progress
  11.   $page = 1;
  12.   $processed = 1;
  13.   // get existing amazon field entries, their node id's, and status (but only those in the wish list taxonomy)
  14.   $result = db_query('SELECT ctp.nid, ctp.field_amazon_asin, n.status FROM {content_type_product} ctp JOIN node n ON n.nid = ctp.nid JOIN term_node tn ON tn.nid = n.nid WHERE tn.tid = 2 AND field_amazon_asin IS NOT NULL');
  15.   // we'll need all of the products with the Amazon field in an associative array keyed with the asin in order to do our syncing
  16.   $existing_amazon_nodes = array();
  17.   // add all of the entries data to the existing nodes array
  18.   while($existing_entry = db_fetch_array($result)) {
  19.     $existing_amazon_nodes[$existing_entry['field_amazon_asin']] = array('nid' => $existing_entry['nid'], 'asin' => $existing_entry['field_amazon_asin']);
  20.   }
  21.   // call our other two functions to get the parsed xml
  22.   $parsed_xml = _amazon_wishlist_synchronizer_get_xml(_amazon_wishlist_synchronizer_build_request($subscription_id, $wishlist_id));
  23.   // find out how many pages we need to get the whole list
  24.   $total_pages = (int) $parsed_xml->Lists->List->TotalPages;
  25.   // setup a new object to collect the data so that it can be processed at once
  26.   // (this is crucial if we are going to remove nodes that are no longer on the wish list)
  27.   $product_list = new stdClass;
  28.   // for each page get the page and process it
  29.   while($page <= $total_pages) {
  30.     $parsed_xml = _amazon_wishlist_synchronizer_get_xml(_amazon_wishlist_synchronizer_build_request($subscription_id, $wishlist_id, $page));
  31.   // get the xml object
  32.     foreach($parsed_xml->Lists->List->ListItem as $item){
  33.         // it's important to specify "(string)" or you get a SimpleXML object
  34.       $asin = (string) $item->Item->ASIN;
  35.        // create a new object containing objects keyed with the asin so that it can be
  36.        // compared to the array of existing nodes
  37.       $product_list->$asin = new stdClass;
  38.       $product_list->$asin->asin = $asin;
  39.       $product_list->$asin->title = (string) $item->Item->ItemAttributes->Title;
  40.       $created = (string) $item->DateAdded;
  41.       // reformat the date added into a unix timestamp
  42.       $created =explode("-",$created);
  43.       $created = mktime(0, 0, 0, $created[1], $created[2], $created[0]);
  44.       $product_list->$asin->created = $created;
  45.       $processed += 1;
  46.     }
  47.     $page += 1;
  48.   }
  49.   // Now that we have a complete list, look though each product
  50.   foreach($product_list as $product) {
  51.     // Check to see if there is an existing node with that asin
  52.     if(!isset($existing_amazon_nodes[$product->asin])){
  53.       $node = new stdClass;
  54.       $node->type = 'product';
  55.       $node->uid = 1;
  56.       $node->promote = 0;
  57.       $node->moderate;
  58.       $node->field_amazon = array(array('asin' => $product->asin));
  59.       $node->title = $product->title;
  60.       $node->taxonomy = array('tid' => 2);
  61.       $node->created = $product->created;
  62.       node_save($node);
  63.     }
  64.     //if there is an existing product that was unpublished with that asin
  65.     //publish it
  66.     elseif(!$existing_amazon_nodes[$product->asin]['status']) {
  67.       $node = node_load($existing_amazon_nodes[$product->asin]['nid']);
  68.       $node->status = 1;
  69.       $node->created = $product->created;
  70.       node_save($node);
  71.     }
  72.   }
  73.   // Check for existing nodes that have been removed from the wish list
  74.   foreach($existing_amazon_nodes as $existing_amazon_node) {
  75.     if(!isset($product_list->$existing_amazon_node['asin'])) {
  76.       // If an asin is found that is no longer on the wish list then unpublish the node
  77.       $node = node_load($existing_amazon_node['nid']);
  78.       $node->status = 0;
  79.       node_save($node);
  80.     }
  81.   }
  82. }
  83. ?>

Run a cron job

In order to keep the site updated automagically behind the scenes, we will add a cron job that calls the function that above. If you have cron running often and have a long wish list you may want to add some logic the prevents it from running quite so frequently.

  1. <?php
  2. /**
  3.  *  Implementation of hook_cron
  4.  */
  5. function amazon_wishlist_synchronizer_cron() {
  6.   amazon_wishlist_synchronizer_sync_wish_list();
  7. }
  8. ?>

If you would like to play around with the module, it is attached.

Warning: This module is provided as is for reference only and comes with no warranty expressed or implied!

AttachmentSize
amazon_wishlist_synchronizer_2.zip2.6 KB

Comments

Post new comment

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is to prevent SPAM. You ARE a human right?