My new company’s Web site will feature blogs by those of us who have interesting things to say about our industry. In order to make the blogs richer and more personalized, the guy coordinating them wants each blog to have its own del.icio.us feeds, Audioscrobbler feeds, Flickr feeds, etc. Initially, I had settled on using Movable Type because, in addition to providing multiple blogs for multiple users, there were already plugins to do all of these things, and there was a plugin to aggregate all posts into one blog, which blog could be used as the home page. And since each user had his own template, the feeds could all be distinct without the need to hack any code.
Another guy working on the Web site started pushing Word Press really hard, and with good reason. It’s a great blogging system and is in fact what I use for my blog. Furthermore, it’s written using PHP rather than perl (MT uses perl) and so is much more pleasant for me to maintain. But while it boasts multiple-blogger functionality, there’s no way short of installing a new version of the software for each blogger to let bloggers have different templates. Or I didn’t think there was, at any rate. It turns out that there’s a beta version of a multi-user Word Press. Essentially, it’s a set of hacks that let the application keep redundant data across blogs in some main tables, duplicating tables only for blog-specific info (such as posts). It’s pretty ugly, and one actually probably would have about as easy a time, for smaller blog setups, just installing the software multiple times. Matt Mullenweg, the creator of Word Press, has said as much. Word Press MU is still in beta, after all.
I took a stab at writing a plugin that would aggregate posts from all blogs into a single blog post listing, and it was a bit of a pain precisely because blogs are stored in different tables rather than in the same table with different ids. I got the base functionality working, but it actually becomes very complex to make it work with any granularity at all. For example, to display a permalink and have it link to the right blog (rather than simply the current one, for which the permalink might not be valid), you have to do all sorts of wizardry. And I didn’t even try to screw with displaying categories on a per-blog basis. I got things to a point at which they’re usable for my purposes, though, and decided to move on to integrating plugins to handle del.icio.us, audioscrobbler, and flickr. Luckily, plugins for these bits of functionality already exist.
Except that the widely-publicized one for audioscrobbler has been removed. So my fun late last week and this morning included rolling my own. Actually, I found another one, but it assumes that you have PHP5, which I don’t (and don’t want to upgrade right now). So I took pieces of that one and modified it. Further, I turned it into a smarty template plugin that makes it dead simple to use in the MU version of Word Press, which uses smarty to handle templating and template caching. So in order to add an audioscrobbler feed to a template now, I have to add only the following to the template:
<!--Audioscrobbler-->
<li>Audioscrobbler
<ul>
{audioscrobbler username="factoryjoe" cache_dir="/var/www/html/wpmu/wp-inst/cache/"}
</ul>
</li>
<!--End Audioscrobbler-->
And actually, only the middle line is essential; the other stuff’s there because that’s what makes the output appear correctly within my template. The username parameter of course represents the audioscrobbler username, and the cache_dir parameter tells the plugin where to cache feed information. Basically, it’s rude and in most cases in violation of a service agreement to fetch a feed for every page load, so my code caches the feed and refreshes the local cache periodically.
The primary thing the original plugin needed PHP5 for was XML parsing. PHP5 comes with lots of built-in tools for doing that, while PHP4 tends to rely on hand-rolled classes. There are some PEAR classes that’re good for parsing feeds, so I modified the relevant code to use them instead of PHP5’s tools. I also added code to register the code as a smarty template function. Here’s the 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; a default that’s overridden in the template parameters
define(“BASE_AS_URL”,”http://ws.audioscrobbler.com/rdf/history/”); //Where to look for the feed (will append username)
//Make this a smarty function by using the API’s naming convention.
function smarty_function_audioscrobbler($params, &$this){
//Bust params array into key=value pairs.
extract($params);
//If no username param passed, we can’t very well fetch a feed.
if($username==” || !$username){
print “Could not load Audioscrobbler plugin.”;
}
//Else call the workhorse function and print the resulting output.
else{
$output=show_audioscrobbler(BASE_AS_URL . $username);
print $output;
}
}
//Function to handle caching/fetching of URL.
function getXmlUrl($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_audioscrobbler($url) {
require_once “XML/Unserializer.php”;
//Get the XML from the passed URL.
$xml=getXmlUrl($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();
$output=””;
//If no items in feed, let user know.
if(sizeof($feed[“item”]) == 0){
$output .= “n
n”;
}
//Else iterate over the items and print out title/url.
else{
foreach($feed[“item”] as $item){
$output .= “n
n”;
}
}
return $output;
}
[/php]
I just added this code to a file called function.audioscrobbler.php in the smarty-plugins directory, added the template code listed above to my template, and — voila — I had an audioscrobbler feed in my sidebar. Pretty nifty.