When we started redesigning the Pop Art blog, one of the chief requirements was to integrate everyone’s Twitter feeds into the site. In addition to the Pop Art Twitter feed in the sidebar, we wanted to add individual twitter feeds on the profile pages. The problem is that the javascript code that Twitter provides can only be called once in a single page, or it gets confused.
Since we were switching to WordPress, I checked out a bunch of Twitter plugins, but ultimately found them all to be unreliable or just missing features. In the end, I hacked together one of my own, based heavily on code by Ryan Barr. His PHP script was very nearly perfect, but I ran into three problems.
First, his script echoed out the exact date and time of the tweet, but I wanted the fancy “2 hours ago” style dates. To do that, I used a chunk of code from Stack Overflow. Now, I’m far from an advanced PHP programmer, so I’m sure that this code could be cleaned up and condensed down to something like 12 characters, but I like this because it works, and it’s very easy for a mid-level programmer like myself to understand.
<?php
/*
Relative Time Function
based on code from http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time/501415#501415
For use in the "Parse Twitter Feeds" code below
*/
define("SECOND", 1);
define("MINUTE", 60 * SECOND);
define("HOUR", 60 * MINUTE);
define("DAY", 24 * HOUR);
define("MONTH", 30 * DAY);
function relativeTime($time)
{
$delta = strtotime('+2 hours') - $time;
if ($delta < 2 * MINUTE) {
return "1 min ago";
}
if ($delta < 45 * MINUTE) {
return floor($delta / MINUTE) . " min ago";
}
if ($delta < 90 * MINUTE) {
return "1 hour ago";
}
if ($delta < 24 * HOUR) {
return floor($delta / HOUR) . " hours ago";
}
if ($delta < 48 * HOUR) {
return "yesterday";
}
if ($delta < 30 * DAY) {
return floor($delta / DAY) . " days ago";
}
if ($delta < 12 * MONTH) {
$months = floor($delta / DAY / 30);
return $months <= 1 ? "1 month ago" : $months . " months ago";
} else {
$years = floor($delta / DAY / 365);
return $years <= 1 ? "1 year ago" : $years . " years ago";
}
}
?>
Secondly, and most important, Ryan’s code didn’t cache the results. That’s obviously bad form, but I didn’t really think about trying to fix it until Apple’s WWDC traffic drove Twitter to a standstill while I was testing our new site. Realizing that our Twitter feeds would die anytime anything big happened on the internet motivated me, and I was able to integrate some caching code from Kien Tran and Snipplr that meshed well with Ryan’s existing code.
When I was done, I had a fully functional script that created a cache file for each twitter feed, and only tried to update it every ten minutes. I was especially pleased to discover that it worked when Twitter again ground to a halt due to the Iran elections and Michael Jackson’s death in the last few weeks.
So here’s the final code that we’re using on the Pop Art blog. It looks pretty overwhelming, but it’s basically broken down into three sections. First, it checks to see if the cache file exists, and whether it needs to be updated. Second, it parses the XML from the cache file to create a series of variables. Finally, it echos out a chunk of HTML for each tweet, or an error message if there aren’t any.
<?php
/*
Parse Twitter Feeds
based on code from http://spookyismy.name/old-entries/2009/1/25/latest-twitter-update-with-phprss-part-three.html
and cache code from http://snipplr.com/view/8156/twitter-cache/
and other cache code from http://wiki.kientran.com/doku.php?id=projects:twitterbadge
*/
function parse_cache_feed($usernames, $limit) {
$username_for_feed = str_replace(" ", "+OR+from%3A", $usernames);
$feed = "http://search.twitter.com/search.atom?q=from%3A" . $username_for_feed . "&rpp=" . $limit;
$usernames_for_file = str_replace(" ", "-", $usernames);
$cache_file = dirname(__FILE__).'/cache/' . $usernames_for_file . '-twitter-cache';
$last = filemtime($cache_file);
$now = time();
$interval = 600; // ten minutes
// check the cache file
if ( !$last || (( $now - $last ) > $interval) ) {
// cache file doesn't exist, or is old, so refresh it
$cache_rss = file_get_contents($feed);
if (!$cache_rss) {
// we didn't get anything back from twitter
echo "<!-- ERROR: Twitter feed was blank! Using cache file. -->";
} else {
// we got good results from twitter
echo "<!-- SUCCESS: Twitter feed used to update cache file -->";
$cache_static = fopen($cache_file, 'wb');
fwrite($cache_static, serialize($cache_rss));
fclose($cache_static);
}
// read from the cache file
$rss = @unserialize(file_get_contents($cache_file));
}
else {
// cache file is fresh enough, so read from it
echo "<!-- SUCCESS: Cache file was recent enough to read from -->";
$rss = @unserialize(file_get_contents($cache_file));
}
// clean up and output the twitter feed
$feed = str_replace("&", "&", $rss);
$feed = str_replace("<", "<", $feed);
$feed = str_replace(">", ">", $feed);
$clean = explode("<entry>", $feed);
$clean = str_replace(""", "'", $clean);
$clean = str_replace("'", "'", $clean);
$amount = count($clean) - 1;
if ($amount) { // are there any tweets?
for ($i = 1; $i <= $amount; $i++) {
$entry_close = explode("</entry>", $clean[$i]);
$clean_content_1 = explode("<content type=\"html\">", $entry_close[0]);
$clean_content = explode("</content>", $clean_content_1[1]);
$clean_name_2 = explode("<name>", $entry_close[0]);
$clean_name_1 = explode("(", $clean_name_2[1]);
$clean_name = explode(")</name>", $clean_name_1[1]);
$clean_user = explode(" (", $clean_name_2[1]);
$clean_lower_user = strtolower($clean_user[0]);
$clean_uri_1 = explode("<uri>", $entry_close[0]);
$clean_uri = explode("</uri>", $clean_uri_1[1]);
$clean_time_1 = explode("<published>", $entry_close[0]);
$clean_time = explode("</published>", $clean_time_1[1]);
$unix_time = strtotime($clean_time[0]);
$pretty_time = relativeTime($unix_time);
?>
<blockquote>
<p class="tweet">
<?php echo $clean_content[0]; ?>
<br /><small>
<?php echo $pretty_time; ?>
</small>
</p>
</blockquote>
<?php
}
} else { // if there aren't any tweets
?>
<blockquote>
<p class="tweet">
I have been terribly busy recently shoveling pixels and clearing out the tubes that make up the Internet, so I haven't had a chance to tweet recently. I am truly very sorry about this, so with just a bit more prodding I'll update as soon as possible.
</p>
</blockquote>
<?php
}
}
?>
You’ll notice that I’m creating more variables than I actually need, noticeably the ones for the user names. On the Pop Art blog, we’re always showing just one person’s tweets at a time, but the code is built to allow you to pass a list of twitter accounts, and it’ll pull them all in. We used that code on another website, and I just left it in place here in case I wanted to use it at some point in the future. Since the variables already exist, I would just need to add them to the output HTML and they would show up.
Which brings me to the final problem I ran into. Ryan’s script uses the Twitter search API, which is great because it gives you the option of pulling in multiple twitter accounts. The downside of the search API is that it will only return recent tweets (the documentation says 7 days, but it looks like it actually returns two weeks).
I did some digging into the API documentation, and there’s no way around this. Twitter has two APIs, the search API and the REST API. The search API is more full-featured, including search operators and automatic link highlighting, with the downside that it only searches recent tweets. The REST API will return all tweets for a user, but it won’t highlight links, merge multiple twitter feeds, and worst of all, it requires a login.
Given those restrictions, I left the code using the search API, despite the restriction. To make the best of the situation, we wrote a funny error message for users with no tweets, and asked everyone linking their Twitter account to post at least once a week.
Awesome! I’m working on the same, however I need to display the last tweets for several different people on the same page. Any thoughts on how I could use this to make that work?
Sure thing! When you call the function in your PHP for a single person, it’ll probably look like this:
<?php
parse_cache_feed(spaceninja, 4)
?>
That’ll get the last four tweets from spaceninja. Now if you use something like this instead:
<?php
$usernames = "popartinc spaceninja";
$limit = "1"; // Number of tweets to pull in
parse_cache_feed( $usernames, $limit );
?>
That will get the most recent tweet from each user in the list — in this case, spaceninja and popartinc.
Hope that helps!
twitter has got to be the easiest API to use with PHP, i have worked with a few but none as simple as twitters.
[...] http://spaceninja.com/2009/07/twitter-php-caching/ (formerly referencing http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time/501415#501415) [...]
Hi.. thanks for the code, but I am having trounle with it.. I have downloaded the files from your 11/2009 edit… when I do this the tweets appear correctly… but it always says ‘over 40 years ago’ for posted info… if I add ‘$time = time();’ as the first line of the relativeTime function then all the posts say posted 2 hours ago no matter when they were posted… the cache file is being created properly.. I just don’t know why this isn’t working. Any ideas?
Hi Steve,
without seeing your site or code, I can’t say for sure what’s wrong, so here’s a few things to check – sorry if they’re a bit basic.
1) are both the relativeTime function and the tweets function being loaded correctly?
2) is the relativeTime function before the tweets function in the source code?
3) do you have any javascript errors?
4) what version of PHP are you running (don’t think this code is anything fancy, but if you’re using a very old version, it might be a problem)
Thanks Scott… nothing is ever too basic for me :)
The site is a test site that is being created… here http://www.standupsites.com/Bert2010/Test6.php is the site…
To answer your questions:
#1 I don’t know how to check this.
#2 Yes.. I have the relativeTime function at the very top of my code.
#3 No javascript errors
#4 Current PHP version: 4.3.11
I can paste the code here if you want, wasn’t sure if you want it.
Thanks,
Steve
Hi Steve,
Sorry for the delay, I’ve been sick. It looks like your code might have gotten messed up when you pasted it in. I think I found the answer, but if this doesn’t help, try emailing your code to me – fake-scott (at) this domain.
From your code, I saw two problems:
1) You added a line that says
$time = time();in therelativeTime()function. This is what’s causing all the tweets to say two hours ago – essentially, you’re telling therelativeTime()function to ignore the time that’s passed in, so that will have to come out.2) It looks like the two functions got mashed together a bit. Your code looks like this:
$years = floor($delta / DAY / 365);return $years $interval) ) {
// cache file doesn't exist, or is old, so refresh it
$cache_rss = file_get_contents($feed);
That looks like the last few lines of the
relativeTime()function and the first several lines of theparse_cache_feed()function are missing, which would cause problems.Again, though, if I’ve got it wrong, email me your code.
- Scott
I took your code, as is, put an include file for the time.php into the twitter.php and added the funcation call and this is what I get back. Any ideas?
I have been terribly busy recently shoveling …
Hi Matt,
I don’t know exactly what that error message means, but the fopen, fwrite, and fclose bits make me think that the code is having trouble writing or reading from the cache file.
Make sure that there’s a cache/ folder in the same directory as the PHP files, and make sure that it’s got appropriate permissions for the script to write to it (if you’re not sure, set it to 777).
If that doesn’t work, try emailing me your code and I’ll see if I can reproduce the error. fake-scott (at) this domain.
Hey Scott,
So I found the issue I was having and am posting it here at your request.
The issue was that every post would say “2 hours ago” regardless of when it was posted… This was happeninig on 2 different hosting accounts yet when you tried the same exact code on your server it wourkd as intended.
What I did to correct it was:
Changed the line that sets $clean_time to:
$clean_time = explode(“Z”, $clean_time_1[1]);
Changed the line that set’s $delta to:
$delta = strtotime(‘+7 hours’) – $time;
This seems to have corrected the issue for me.
Thanks for the great code… will be using it a lot!
Scott – couple of questions:
1) If I initially set the number of tweets to show, for example, “2″, then I change it to “10″, it still only shows the initial number. How do I increase or decrease what the script shows?
2) Where is the cache file located? I created a “cache” folder with the right permissions (777) but there’s nothing inside of it (and the script stops working if I remove it).
I’m on a 1and1 shared server running the latest version of WordPress. Thanks!
-Brandon
1) That sounds like it’s probably a cache issue – Did it start showing the correct number later, or does it still show 2?
2) The cache folder should be in the same directory as your PHP – in my case, I put the PHP in my theme’s functions.php file, so I added a cache directory in
wp-content/themes/$THEME/cache– I’m not sure why you’re not seeing any files in there. Since the script is dying when you remove the directory, that implies you have it in the right location. If you view source on the page, there should be a comment in the twitter block that gives the status of the cache – what does it say?Scott – I’ve checked the folder and it looks like there’s a cache file in there and the comment reads:
<!-- SUCCESS: Cache file was recent enough to read from -->Still, the cache file only has two entries in it. Can I add to this XML and then trust that if the function says “7″, it will keep adding to the cache?
Again, if I use another twitter ID, this doesn’t happen. Is twitter restricting the number of XML feeded items I can grab with this script? Or is the cache remembering “2″ and not allowing it to change to another number?
The comment reads: “SUCCESS: Cache file was recent enough to read from”. When I delete the cache file and refresh the page, it creates a new cache file but still with the original number, not the number from the function.
Really like this script, I’d like to get it to work as intended!
-Brandon
Hi Brandon,
I’m not sure what’s going on here – I don’t think Twitter limits the number of results you can get, so changing to 7 should work just fine. I was able to change mine from 2 to 15, deleted the cache file, and it worked fine. To be clear, your code should look like this:
Oh, something occurs to me – are you using any of the wordpress cache plugins like SuperCache? Those would cause the kind of problems you’re seeing because they cache the code in the sidebar that talks to Twitter.
For those whose hosts have
allow_url_fopen = off, this code won’t work, and will constantly fail to read from twitter.A solution I found that works for me is to use cURL instead of file_get_contents.
Replace
$cache_rss = file_get_contents($feed);withI was having a problem with the most recent version of this code for a while. IE had huge fits about it, and it wouldn’t Validate.
I changed the code to loop extra information for better styling:
Since I did it with echos, I got errors in the
onClicksin the links. To fix it, I changed what the PHP converts"to.$clean = str_replace(""", '"', $clean);fixed my problems. The code validates now, and IE will process it without throwing a fit.
Hey Anna great find and thanks for sharing that! I was going to re-write the entire thing because I couldn’t figure out where the bad attribute was coming from, but that fixed it for me!
Thanks for this great bit of code — after looking around at other bloated solutions around the web this one’s a great starting point for beginning to customize the feed display.
Cheers!