Word Press and Del.icio.us; Word Press and Amazon

In a post this morning, I went over briefly my inclination to write an audioscrobbler plugin for Word Press. Other things I needed to aggregate for the project I was doing that for included del.icio.us feeds and amazon book lists. Flickr already provides a javascript snippet to display images, and I’m not going to take the time to rewrite that one for now. But I did decide to go ahead and write my own plugins to display del.icio.us and amazon information.

Why not use something that’s already out there, including my own amazon plugin? Well, the existing plugins aren’t as flexible as what I wanted to do. For example, you have to hack your index.php file in order to add the amazon wishlist output to your page unless you want it just automatically appended to your categories listing. Plus the amazon plugin retrieves wishlist info only, and you might want to display books you’re currently reading without risking adding them to your wishlist and getting duplicate copies. Additionally, I’m really digging the smarty engine that Word Press 1.5 uses, and in order to add stuff to templates, it makes pretty good sense to write the plugins as smarty plugins. The del.icio.us plugin doesn’t really require much flexibility and is essentially a duplication of the audioscrobbler plugin, though I’ve added a parameter that allows you easily to limit the number of links displayed. The template code is as follows:



<li>Del.icio.us
<ul>
{delicious feed="http://del.icio.us/rss/tag/r2" cache_dir="/var/www/html/wpmu/wp-inst/cache/" records="10"}
</ul>
</li>

You pass a feed URL, a writable directory to cache the feed in so we don’t tax the del.icio.us servers, and the maximum number of records to display. Part of the beauty of this plugin (or actually of the API that supports it) is that you can include the smarty tags several times in your template to display different feeds. And because things like the records limit are passed on a per-instance basis, there’s no global or function-scoped setting forcing you to always have X number of links, so you can have 10 blog links and 5 design links and 3 links of some other type, for example. The actual plugin code looks very much like the audioscrobbler code:

[php]
define(“CACHE_EXPIRY”, 60*10); //seconds to cache data for
define(“CACHE_DIR”, “/var/www/html/wpmu/wp-inst/cache/”);//where to cache

//Make this a smarty function by using the API’s naming convention.
function smarty_function_delicious($params, &$this){
//Bust params array into key=value pairs.
extract($params);

if($records==” || !$records || intval($records)==0){
$records=10;
}
//If no username param passed, we can’t very well fetch a feed.
if($feed==” || !$feed){
print “Could not load del.icio.us plugin.”;
}
//Else call the workhorse function and print the resulting output.
else{
$output=show_delicious($feed,$records);
print $output;
}
}

//Function to handle caching/fetching of URL.
function getDeliciousXmlUrl($url) {
$goodToUpdate = true;
$hash = md5($url);
$filename = CACHE_DIR.$hash;

//If we’ve already cached this feed…
if ( file_exists($filename) ) {
$time = filectime($filename);
//Check the time it was last cached based on cache file timestamp.
if ( $time > (time() – CACHE_EXPIRY) ) {
//it was last written less than CACHE_EXPIRY seconds ago
$goodToUpdate = false;
}
//Set $xmlFile to contents of cached file in case we need it.
$xmlFile = file_get_contents($filename);
}

//If we’re not cached or the cache has expired…
if ( $goodToUpdate ) {
//Get the url content. Your php.ini has to allow remote URLs to be opened as files for this to work.
$xmlString = file_get_contents($url);
if(!$xmlString){
//something broke, sliently move on
}
else{
//Write the contents to the cache filename determined above.
$fp=fopen($filename,’w’);
fwrite($fp,$xmlString);
fclose($fp);

//Set $xmlFile to the newer data.
$xmlFile = $xmlString;
}

}
return $xmlFile;
}

//This does the real work of the plugin.
function show_delicious($url,$records) {
require_once “XML/Unserializer.php”;

//Get the XML from the passed URL.
$xml=getDeliciousXmlUrl($url);

//PEAR class that does the work invoked here.
$rss =& new XML_Unserializer();
$result= $rss->unserialize($xml,false); //False makes it treat the param as a string rather than as a file.
$feed=$rss->getUnserializedData();

//print “

$feed,1) . "

“;

$output=””;
//If no items in feed, let user know.
if(sizeof($feed[“item”]) == 0){
$output .= “n

  • no recent links (” . sizeof($feed[“channel”][“item”]) . “)
  • n”;
    }
    //Else iterate over the items and print out title/url.
    else{
    $del_count=1;
    foreach($feed[“item”] as $item){
    if($del_count < = $records){
    $output .= "n

  • ” . $item[“title”]. “n”;
    }
    else{
    break;
    }
    $del_count++;
    }
    }

    return $output;
    }
    [/php]

    The amazon plugin is a little more involved. Initially, it was very much like the other two plugins I’ve mentioned here, with the output hard-coded, but I wanted to make this one more flexible. For example, I wanted a user to be able to dictate what data fields were displayed and with what CSS selectors attached to them without having to define and hard-code a bunch of selectors and flags to determine what to code. In other words, I didn’t want users to have to type a tag like the following and still be limited as far as layout by some hard-coded values:


    {amazon dev_token="XXXXXXXXXXXX" associate_id="YYYYYYYY" isbn="4770029055,1400043662" cache_dir="/var/www/html/wpmu/wp-inst/cache/" image_class="small_image" show_small_image="1" title_class="amazon_title" show_title="1" price_class="price_class" show_price="1" release_date_class="rd_class" show_release_date="1"}

    Instead, I worked it out so that users can add something like the following to the template:


    <!--Amazon-->
    <li>Amazon
    <ul>
    {amazon dev_token="D3TF1MNM1YQKGD" associate_id="rationalisofeast" isbn="4770029055,1400043662" cache_dir="/var/www/html/wpmu/wp-inst/cache/"}
    {foreach from=$amazon_list key=key item=hits}
    <li><a class="amazon_link" href="{$hits.url}"><img class="amazon_image" src="{$hits.small_image}"><br/><span class="amazon_title">{$hits.title}</span><br/></a><b>Price: </b>{$hits.list_price}</li>
    {/foreach}
    </ul>
    </li>
    <!--End Amazon-->

    In other words, the design and determining what elements to display can be controlled completely through the template so that designers don’t have to go poking through PHP code to try to figure out how to change a style attribute on an image or a title. The key is knowing what parameters are valid to use within the template, and in this case, they’re as follows:

    • isbn
    • title
    • url
    • authors
    • catalog
    • small_image
    • medium_image
    • large_image
    • availability
    • list_price
    • sale_price
    • used_price
    • release_date

    Now for a brief description of how the template parsing works (or, more accurately, what you need to know in order to incorporate these attributes into your template). First, you have to include the line:


    {amazon dev_token="XXXXXXXXXXXXX" associate_id="YYYYYYYY" isbn="4770029055,1400043662" cache_dir="/var/www/html/wpmu/wp-inst/cache/"}

    The dev_token parameter is something you get from amazon, as is the associate_id (which allows you to get monetary credit for books purchased through the link this generates). The cache_dir, as in the other plugins, should represent a Web-server-writable path on your system and is used to help keep load down on the servers we’re connecting to in order to get the data. The isbn parameter contains a comma-separated list of the ISBNs for the books you want to display. Ideally, this can one day be dynamic or based on info pulled from a database and populated through an admin tool, but for now, this does the trick. This line of code causes the data to be grabbed from amazon and stored in the smarty object. Which is where the foreach in the template comes in. The forech block tells smarty to iterate over the variable $amazon_list (which the line above created and populated with an array for each book retrieved) and to store the array in a local variable named “hits.” The values of the array for each iteration can be accessed using dot-syntax, where the piece after the dot corresponds to one of the keys defined in the list above. In other words, we’re iterating over the array returned by the initial line and, for each array within that array, plugging its values in by name where listed within brackets here using the dot-syntax. This allows designers to wrap whatever layout and design tags they wish to around the data and keeps design-retarded coders like me from making stupid design decisions that are hard-coded into the smarty plugin. Within the foreach loop, to get the title for a given book, we’d use {$hits.title}. To get the url, we’d use {$hits.url}. And so on. The code for the plugin itself is as follows:

    [php]
    define(“CACHE_EXPIRY”, 60*10); //seconds to cache data for
    define(“CACHE_DIR”, “/var/www/html/wpmu/wp-inst/cache/”);//where to cache
    define(“BASE_AMAZON_URL”,’http://xml.amazon.com/onca/xml3?page=1&type=lite&locale=us&f=xml&#8217;);
    //http://xml.amazon.com/onca/xml3?locale=us&t=rationalistsofeas&dev-t=D3TF1MNM1YQKGD&AsinSearch=4770029055&mode=books&sort=+titlerank&offer=All&type=lite&page=1&f=xml

    //Make this a smarty function by using the API’s naming convention.
    function smarty_function_amazon($params, &$this){
    //Bust params array into key=value pairs.

    extract($params);

    if($dev_token==” || $isbn==”){
    print “Must pass dev_token and isbn as params”;
    exit;
    }
    if($records==” || !$records || intval($records)==0){
    $records=5;
    }
    $feed=BASE_AMAZON_URL . “&dev-t=” . $dev_token . “&t=” . $associate_id . “&AsinSearch=” . $isbn;
    // print $feed; exit;
    $output=show_amazon($this,$feed,$records,$associate_id);
    print $output;
    }

    //Function to handle caching/fetching of URL.
    function getAmazonXmlUrl($url) {
    $goodToUpdate = true;
    $hash = md5($url);
    $filename = CACHE_DIR.$hash;

    //If we’ve already cached this feed…
    if ( file_exists($filename) ) {
    $time = filectime($filename);
    //Check the time it was last cached based on cache file timestamp.
    if ( $time > (time() – CACHE_EXPIRY) ) {
    //it was last written less than CACHE_EXPIRY seconds ago
    $goodToUpdate = false;
    }
    //Set $xmlFile to contents of cached file in case we need it.
    $xmlFile = file_get_contents($filename);
    }

    //If we’re not cached or the cache has expired…
    if ( $goodToUpdate ) {
    //Get the url content. Your php.ini has to allow remote URLs to be opened as files for this to work.
    $xmlString = file_get_contents($url);
    if(!$xmlString){
    //something broke, sliently move on
    }
    else{
    //Write the contents to the cache filename determined above.
    $fp=fopen($filename,’w’);
    fwrite($fp,$xmlString);
    fclose($fp);

    //Set $xmlFile to the newer data.
    $xmlFile = $xmlString;
    }

    }
    return $xmlFile;
    }

    //This does the real work of the plugin.
    function show_amazon(&$smarty,$url,$records,$associate_id) {
    require_once “XML/Unserializer.php”;

    //Get the XML from the passed URL.
    $xml=getAmazonXmlUrl($url);

    //PEAR class that does the work invoked here.
    $rss =& new XML_Unserializer();
    $result= $rss->unserialize($xml,false); //False makes it treat the param as a string rather than as a file.
    $feed=$rss->getUnserializedData();

    //print “

    " . print_r($feed,1) . "

    “;

    $output=””;
    //If no items in feed, let user know.
    if(sizeof($feed[“Details”]) == 0){
    $output .= “n

  • no products found
  • n”;
    }
    //Else iterate over the items and print out title/url.
    else{
    $am_count=1;
    $items=array();
    foreach($feed[“Details”] as $item){
    if($am_count $item[“Asin”],
    “title” => $item[“ProductName”],
    “url” => “http://www.amazon.com/exec/obidos/ISBN%3D&#8221; . $item[“Asin”] . “/” . $associate_id,
    “authors” => join(“, “,$item[“Authors”]),
    “catalog” => $item[“Catalog”],
    “small_image” => $item[“ImageUrlSmall”],
    “medium_image” => $item[“ImageUrlMedium”],
    “large_image” => $item[“ImageUrlLarge”],
    “availability” => $item[“Availability”],
    “list_price” => $item[“ListPrice”],
    “sale_price” => $item[“OurPrice”],
    “used_price” => $item[“UsedPrice”],
    “release_date” => $item[“ReleaseDate”],
    ));
    //$output .= “n


  • ” . $item[“ProductName”]. “
  • n”;
    }
    else{
    break;
    }
    $am_count++;
    }
    $smarty->assign(“amazon_list”,$items);
    }

    return $output;
    }
    [/php]

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out /  Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out /  Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out /  Change )

    Connecting to %s