Pixel Acres

Display recent Twitter tweets using PHP

IMPORTANT! This script has been superseded by TweetPHP.

If you’ve ever wanted to display your latest Twitter tweets on a website, here is method to do that using PHP. My approach has the following features:

  • Tweets are cached to avoid exceeding Twitter’s limit of 150 requests for a user’s RSS feed per hour
  • A fallback is provided in case the twitter feed fails to load
  • Replies (tweets beginning with @) can optionally be ignored
  • A configuration parameter allows you to specify how many tweets are displayed
  • Dates can optionally be displayed in “Twitter style”, e.g. “12 minutes ago”
  • You can edit the HTML that wraps your tweets, tweet status and meta information
  • The username which prepends each tweet in Twitter RSS feeds is automatically stripped

<?php
 
/**
 * TWITTER FEED PARSER
 * 
 * @version	1.1.4
 * @author	Jonathan Nicol
 * @link	http://f6design.com/journal/2010/10/07/display-recent-twitter-tweets-using-php/
 * 
 * Notes:
 * Caching is employed because Twitter only allows their RSS feeds to be accesssed 150
 * times an hour per user client.
 * --
 * Dates can be displayed in Twitter style (e.g. "1 hour ago") by setting the 
 * $twitter_style_dates param to true.
 * 
 * Credits:
 * Hashtag/username parsing based on: http://snipplr.com/view/16221/get-twitter-tweets/
 * Feed caching: http://www.addedbytes.com/articles/caching-output-in-php/
 * Feed parsing: http://boagworld.com/forum/comments.php?DiscussionID=4639
 */
 
function display_latest_tweets(
	$twitter_user_id,
	$cache_file = './twitter.txt',
	$tweets_to_display = 100,
	$ignore_replies = false,
	$twitter_wrap_open = '<h2>Latest tweets</h2><ul id="twitter">',
	$twitter_wrap_close = '</ul>',
	$tweet_wrap_open = '<li><span class="status">',
	$meta_wrap_open = '</span><span class="meta"> ',
	$meta_wrap_close = '</span>',
	$tweet_wrap_close = '</li>',
	$date_format = 'g:i A M jS',
	$twitter_style_dates = false){
 
	// Seconds to cache feed (1 hour).
	$cachetime = 60*60;
	// Time that the cache was last filled.
	$cache_file_created = ((file_exists($cache_file))) ? filemtime($cache_file) : 0;
 
	// A flag so we know if the feed was successfully parsed.
	$tweet_found = false;
 
	// Show file from cache if still valid.
	if (time() - $cachetime < $cache_file_created) {
 
		$tweet_found = true;
		// Display tweets from the cache.
		readfile($cache_file);	
 
	} else {
 
		// Cache file not found, or old. Fetch the RSS feed from Twitter.
		$rss = file_get_contents('https://api.twitter.com/1/statuses/user_timeline.rss?screen_name='.$twitter_user_id);
 
		if($rss) {
 
			// Parse the RSS feed to an XML object.
			$xml = simplexml_load_string($rss);
 
			if($xml !== false) {
 
				// Error check: Make sure there is at least one item.
				if (count($xml->channel->item)) {
 
					$tweet_count = 0;
 
					// Start output buffering.
					ob_start();
 
					// Open the twitter wrapping element.
					$twitter_html = $twitter_wrap_open;
 
					// Iterate over tweets.
					foreach($xml->channel->item as $tweet) {
 
						// Twitter feeds begin with the username, "e.g. User name: Blah"
						// so we need to strip that from the front of our tweet.
						$tweet_desc = substr($tweet->description,strpos($tweet->description,":")+2);
						$tweet_desc = htmlspecialchars($tweet_desc);
						$tweet_first_char = substr($tweet_desc,0,1);
 
						// If we are not ignoring replies, or tweet is not a reply, process it.
						if ($tweet_first_char!='@' || $ignore_replies==false){
 
							$tweet_found = true;
							$tweet_count++;
 
							// Add hyperlink html tags to any urls, twitter ids or hashtags in the tweet.
							$tweet_desc = preg_replace('/(https?:\/\/[^\s"<>]+)/','<a href="$1">$1</a>',$tweet_desc);
							$tweet_desc = preg_replace('/(^|[\n\s])@([^\s"\t\n\r<:]*)/is', '$1<a href="http://twitter.com/$2">@$2</a>', $tweet_desc);
							$tweet_desc = preg_replace('/(^|[\n\s])#([^\s"\t\n\r<:]*)/is', '$1<a href="http://twitter.com/search?q=%23$2">#$2</a>', $tweet_desc);
 
 							// Convert Tweet display time to a UNIX timestamp. Twitter timestamps are in UTC/GMT time.
							$tweet_time = strtotime($tweet->pubDate);	
 							if ($twitter_style_dates){
								// Current UNIX timestamp.
								$current_time = time();
								$time_diff = abs($current_time - $tweet_time);
								switch ($time_diff) 
								{
									case ($time_diff < 60):
										$display_time = $time_diff.' seconds ago';                  
										break;      
									case ($time_diff >= 60 && $time_diff < 3600):
										$min = floor($time_diff/60);
										$display_time = $min.' minutes ago';                  
										break;      
									case ($time_diff >= 3600 && $time_diff < 86400):
										$hour = floor($time_diff/3600);
										$display_time = 'about '.$hour.' hour';
										if ($hour > 1){ $display_time .= 's'; }
										$display_time .= ' ago';
										break;          
									default:
										$display_time = date($date_format,$tweet_time);
										break;
								}
 							} else {
 								$display_time = date($date_format,$tweet_time);
 							}
 
							// Render the tweet.
							$twitter_html .= $tweet_wrap_open.html_entity_decode($tweet_desc).$meta_wrap_open.'<a href="http://twitter.com/'.$twitter_user_id.'">'.$display_time.'</a>'.$meta_wrap_close.$tweet_wrap_close;
 
						}
 
						// If we have processed enough tweets, stop.
						if ($tweet_count >= $tweets_to_display){
							break;
						}
 
					}
 
					// Close the twitter wrapping element.
					$twitter_html .= $twitter_wrap_close;
					echo $twitter_html;
 
					// Generate a new cache file.
					$file = fopen($cache_file, 'w');
 
					// Save the contents of output buffer to the file, and flush the buffer. 
					fwrite($file, ob_get_contents()); 
					fclose($file); 
					ob_end_flush();
 
				}
			}
		}
	} 
	// In case the RSS feed did not parse or load correctly, show a link to the Twitter account.
	if (!$tweet_found){
		echo $twitter_wrap_open.$tweet_wrap_open.'Oops, our twitter feed is unavailable right now. '.$meta_wrap_open.'<a href="http://twitter.com/'.$twitter_user_id.'">Follow us on Twitter</a>'.$meta_wrap_close.$tweet_wrap_close.$twitter_wrap_close;
	}
}
 
display_latest_tweets('YOUR_TWITTER_ID');
 
?>

Usage

You should edit the Twitter ID in the function call above before using the function (it appears at the very bottom of the code snippet).

You probably also want to edit the location where the twitter feed is cached – by default it is written to the root level of your domain. To change the location, modify the $cache_file variable, or pass the new location as a function parameter.

Notes

Twitter feeds may contain UTF-8 characters. I have found that running PHP’s utf_decode method on tweets didn’t have the expected result, so my recommendation is to instead set the charset of your HTML page to UTF-8. Really we should all be doing this anyway.

Credits

The hashtag/username parsing in my example is from Get Twitter Tweets by gripnrip.

My RSS parsing is based on replies in the forum discussion “embedding twitter tweets” on the Boagworld website.

The file caching is based on the AddedBytes article “Caching output in PHP”.

Changelog

v1.1.4, 14 October 2012

  • Changed the URL that tweet feeds are fetched from, after Twitter killed the old rss feeds

v1.1.2, 17 April 2012

  • HTML entities in tweets are now decoded e.g. &lt;

v1.1.1, 28 January 2011

  • Fixed bug in the logic that pluralises the number of hours since a tweet

v1.1, 14 January 2011

  • Fixed URL parsing regular expression to make it much more liberal. It will no longer choke on hyphens.
  • Added an optional parameter $twitter_style_dates which will format dates the same way as the Twitter website, e.g. “12 minutes ago”. It is set to false by default.
  • Dates are now formatted the same as on the Twitter website, e.g. “3:48 PM Jan 14th”. This can be overridden using the $date_format parameter.
  • The username parser will no longer include a trailing colon as part of the username, so the autolink for a string like “@username: hello!” won’t get messed up.

125 Responses to “Display recent Twitter tweets using PHP”

  1. Jaitra says:

    Very useful post, thanks Jonathan.

    Correct me if I am wrong, but this approach would also have the advantage (over JS) of making your Twitter content accessible to Google.

  2. Jonathan says:

    @Jaitra – quite right! I have avoided JavaScript based approaches for that reason.

  3. Matt says:

    Hey this is a great piece of code – but how can I get it the date format to be “X hours ago” or “X days ago” etc rather than a static date?

  4. Jonhatan, thanks for such a great piece of code!
    I was wondering if is possible to pass the parameter of “$tweets_to_display” directly in the call: display_latest_tweets(‘YOUR_TWITTER_ID’);

    I’m not sure of how to do this via PHP, I’ll be grateful with your help.

    Best regards and thanks again.

  5. Jonhatan! I just noticed a rendering error, or it seem like. If a tweet have an URL embedded with more than 38 characters or 40 (not sure exactly) the URL is cut.

    Check this printscreen:
    http://nicolasfranz.com/dropbox/error-tweet.jpg

    You’ll notice that the URL doesn’t go til the ending character, but it parse the link before it.

    I can’t send you a link of the website now because I have it in my localhost, but let me know if you have an idea of what could be happening here, best regards.

  6. Jonathan says:

    @Nicolas The function takes 10 parameters, and the 3rd one is the number of tweets to display. So you need to pass the function those first three parameters:

    display_latest_tweets(‘YOUR_TWITTER_ID’,’./twitter.txt’,10);

    The second parameter is the location of the cache file.

    Regarding the URL parsing error, I used a 3rd party regular expression for URL parsing, but it looks like you’ve discovered a bug with it! I expect it’s choking when it hits the hyphen in your URL. I’ll look into it when I have some spare time.

  7. Thank you Jonathan! I just notices that I was passing only two parameters:
    display_latest_tweets(‘YOUR_TWITTER_ID’,10);

    The result was that, in one page, where I called the function with only 2 tweets, it loaded the 10 recent tweets from the /twitter.txt file at Cache. And vice versa, when I was in the page where I called 10 tweets, the cache had stored only 2 tweets.

    But now with the 3 parameters this issue is fixed. Thanks a lot. Best regards!

  8. Lance says:

    Jonathan -

    Great idea on caching the tweets. Quick question.. I’m wanting to bring in tweets that contain a specific hash tag… any idea how this might work?

    thanks again!

  9. Otto Rask says:

    Hello and many thanks for this script! :)

    I noticed the script parses some URLs wrongly, ones which look like http://www.blabla.com/~blabla. It thinks the URL ends at the ‘~’ and leaves it broken.

    As I can’t make anything out of regular expressions, I was hoping you would take a look at them.

    Thanks! :)

  10. Jonathan says:

    I have updated the script to fix the URL parsing, and to include optional “Twitter style” dates, e.g. “12 minutes ago”. I fixed a few other bugs too. All the changes are listed in the changelog above.

    Thanks to those of you who posted suggestions and bugs :)

  11. Otto Rask says:

    Many thanks Jonathan! Gonna get updating. :) ->

  12. negru says:

    Doesn’t work too well for me, most of the time I see “Oops, our twitter feed is unavailable right now.”
    I think is a cache issue, or is my server… Any idea?

  13. Jonathan says:

    @negru – Sound like a cache problem. All I can suggest is making sure that the RSS feed is being correctly cached on your server, and once cached is being successfully loaded.

    To test this, look for the file twitter.txt in your domain’s root level, or wherever you specified that it be written. Edit that text file with some telltale modification that will let you know your application is reading data from the cached text file, rather than a Twitter RSS feed.

    If you don’t see your modification reflected in your application, then you know that the data is still being loaded from Twitter’s server. Since Twitter limits the times that a single client can access a feed per hour, after 150 page views the feed will stop loading.

  14. t.avison says:

    Thanks Jonathan
    I have looked through tones of code snips and plugins but this works awesomely and the way I wanted to display my tweets.
    Excellent work and Thanks very much

  15. Jonathan says:

    No worries Toby, I’m pleased you found it useful :)

  16. colin says:

    i’m having trouble writing to the text file as well.

    the script doesn’t seem to be able to generate its own cache if i don’t specify a file, and then when I do specify, it can’t write to it.

  17. Duncan says:

    I’m having trouble with the option to add s to the hour when it is greater than one.

    This is the code I have, but with this I get no s at all.

    case ($time_diff > 3600 && $time_diff 1){ $display_time .= ‘s’; }
    $display_time .= ‘ ago’;
    break;

    Can you help? I’ve been racking my brains, but can’t think why it’s not working.

    Thanks in advance,
    Duncan

  18. Duncan says:

    This is the code, not that in my previous comment…

    case ($time_diff > 3600 && $time_diff 1){ $display_time .= ‘s’; }
    $display_time .= ‘ ago’;
    break;

  19. Duncan says:

    Hi, Duncan again. Just to let you know I’ve solved the issues.

    I’ve replaced

    _if ($display_time>1){ $display_time .= ‘s’; }

    with…

    _if ($hour>1){ $display_time .= ‘s’; }

    Thanks.

  20. Jonathan says:

    @Duncan – Gosh, thanks for spotting that bug! I have updated to fix the error.

  21. Adam Cap says:

    Hey I just wanted to let you know this is awesome! I’ve been searching all morning for different scripts to display your latest tweets along with the time, and this is by far the best solution I’ve found. Thanks man!

    I’ve got 1 bug and 1 request though…

    The bug is that the date/time link goes to my general twitter account, and not the individual tweet. So for example, the date at the end of the tweet links to:

    twitter.com/my_twitter

    and not

    twitter.com/my_twitter/status/123452315325235 (a bunch of numbers)

    I don’t know if you designed it that way, but I was expecting the individual status to be linked to (and not my account).

    Also my request for future improvement would be to give a way to filter retweets, like how you already included a way to filter out replies.

    Oh and lastly do you think 1 hour is a good time to cache the feed, or could it be cached for less time? An hour seems long to me, but I dunno.

    Thanks!!

    -Adam

  22. Jonathan says:

    @Adam Cap – thanks for the feedback and suggestions.

    The reason I chose to link to the Twitter profile rather than the individual tweet is because that was the implementation I needed when I wrote the script. But I think you’re right, the behaviour most people would expect is to link to the tweet, so I will make that change when I have a moment.

    I will look at filtering out retweets too…

    The reason the cache has a lifespan of one hour is because every hour Twitter enforce a limit of 150 requests for a user’s RSS feed from the same IP. Your web application can easily exceed that limit if it fetches the remote RSS feed instead of reading from a local cache. Setting a cache time of one hour ensures this never happens.

  23. Adam Cap says:

    Cool, thanks for the reply man!

    Also I changed to code a little bit to show days, weeks, months, and years ago for the time. Might be a little excessive… but I wanted to add “days ago” and thought what the hell… I’ll add the rest too:

    switch ($time_diff)
    {
    case ($time_diff = 60 && $time_diff = 3600 && $time_diff 1){ $display_time .= ‘s’; }
    $display_time .= ‘ ago’;
    case ($time_diff >= 86400 && $time_diff 1){ $display_time .= ‘s’; }
    $display_time .= ‘ ago’;
    break;
    case ($time_diff >= 604800 && $time_diff 1){ $display_time .= ‘s’; }
    $display_time .= ‘ ago’;
    break;
    case ($time_diff >= 2592000 && $time_diff 1){ $display_time .= ‘s’; }
    $display_time .= ‘ ago’;
    break;
    case ($time_diff >= 31536000):
    $year = floor($time_dif/31536000)
    $display_time = $year.’ year’;
    if ($year> 1){ $display_time .= ‘s’; }
    $display_time .= ‘ ago’;
    break;
    default:
    $display_time = date($date_format,$tweet_time);
    break;
    }

  24. Have spent most of my evening looking for something that produced just this. I’m no PHP pro. I handle front end HTML/CSS build and I have a very good grasp of WordPress, but tonight I wanted to feature recent tweets within a simple php page and it’s taken me hours to find this truly wonderful post.

    Thanks for sharing – I can now go to bed without worrying about disappointing my client in the morning.

  25. Ross says:

    Thanks for this. I tried a few other twitter scripts but none of them had the functionality and diversity of this one. I can now stop banging my head against the wall due to php issues :D

    Thanks again :D

  26. Daniel Foss says:

    Very useful code, would it be ok for you if I paste it into a custom Widget which is part of a ThemeForest-Theme I’m developing actually and sell this theme, or you don’t allow use for commercial projects?

  27. Jonathan says:

    @Daniel Yeah go for it. But if you could leave an author credit/link in your source code that would be appreciated.

  28. Daniel Foss says:

    Thanks for your fast answer Jonathan!
    Will leave your original credits into the widget source, appreciate your support!

  29. Laveena says:

    Hi,

    Thanks for sharing this script.

    I have integrated your coding and it is working fine.

    But sometimes it displays the “Oops, our twitter feed is unavailable right now”

    Could you please anyone help me to out this issue.

    Am i did any application error ?

    Thanks to looking in to that,

  30. Jonathan says:

    @Laveena See the comment above where I reply to negru

  31. Dan O'Neil says:

    This is wonderful! Ditch another wordpress plugin…

    I’ve changed the twitter style dates a bit – so that I can have yesterday or some days ago, last week or some weeks ago etc. You can add in months and years if you need to, seems like overkill to me! Thanks to Adam Cap for the inspiration with his tweak above.

    switch ($time_diff) {
    case ($time_diff = 60 && $time_diff = 3600 && $time_diff 1){ $display_time .= ‘s’; }
    $display_time .= ‘ ago’;
    break;

    case ($time_diff >= 86400 && $time_diff < 604800):
    $day = floor($time_diff/86400);
    $display_time = 'about '.$day.' days ago';
    if ($day = 604800 && $time_diff < 2592000):
    $week = floor($time_diff/604800);
    $display_time = 'about '.$week.' weeks ago';
    if ($week < 1){ $display_time = 'last week'; }
    break;

    default:
    $display_time = date($date_format,$tweet_time);
    break;
    }

  32. Dan O'Neil says:

    Another quick mod to get the proper status links for each of your tweets:

    // Render the tweet.
    $twitter_html .= $tweet_wrap_open.$tweet_desc.$meta_wrap_open.’<a href=”‘.$tweet->link.’”>’.$display_time.’‘.$meta_wrap_close.$tweet_wrap_close;
    }

    A simple change from the previous:
    <a href=”http://twitter.com/’.$twitter_user_id.’” rel=”nofollow”>

    changed to:
    <a href=”‘.$tweet->link.’”>

    Job done! There’s probably a cleaner way, but it works!

  33. Ian P says:

    @Jonathan

    This is great, thank you. What I’d like to do is package it up and tie it into the ModX Revolution OpenSource CMS. imho, the community is lacking twitter integration of this quality and I’d like to build on your code and provide them with something they can quickly pick up (downloadable through the new package manager system) and start to use on their websites using minimal configuration and just simple snippet calls.

    Would this be okay? I’d give you credit for all your hard work, of course.

  34. Jonathan says:

    @Ian P Definitely, go for it! From what I’ve seen ModX is a great CMS, so if my code helps make it even better then I’ll be very happy.

  35. qwerty says:

    Hi Jonathan, awesome plugin! I have it all working but is there any way to aggregate two or more users into a single feed?

  36. Jonathan says:

    @qwerty Sorry, as it currently stands that wouldn’t be possible.

  37. Sudeer says:

    Hi Jonathan,

    I am trying to use the script to fetch the latest tweets, but i am getting a blank screen on executing the script after updating the user_id value.

    And when i started debuging the code, i see my RSS url returns blank (below url)

    http://twitter.com/statuses/user_timeline/sudeerde.rss
    http://twitter.com/statuses/user_timeline/delaware_gov.rss

    can you let me know what i am doing wrong here?

    Thanks for your help in advance,
    Sudeer.

  38. Jonathan says:

    @Sudeer – Not sure why it’s not working for you. Both those rss feeds return data, and I tested them both with my display_latest_tweets function and they worked…

  39. Linda says:

    Hello,

    I have set the twitter_style_date to true :$twitter_style_dates = true){

    However, it’s still showing the date and time (from a different time zone).

    Can anybody help?

    I tried copying and pasting some of the above codes in the comments, but I was getting syntax errors.

    Any help would be greatly appreciated.

  40. Linda says:

    Actually…none of the changes I’ve made under “function display_latest_tweets” have worked…I must be missing something really stupid.

  41. Jonathan says:

    @Linda If none of your changes are taking effect that is most likely because the cached version of the twitter feed is being displayed. Delete the cached twitter.txt file (by default it should be saved in your root level directory), then you should see your changes take effect on the next page refresh.

  42. Andy says:

    There’s an issue displaying tweets posted one minute ago (they show up as posted “1 Minutes Ago”).

    A simple fix by adding:

    case ($time_diff >= 60 && $time_diff < 120):
    $min = floor($time_diff/60);
    $display_time = $min.' minute ago';
    break;

    (Make sure to then change the existing case from 60 to 120.)

  43. sean says:

    thanks! very helpful after finding several script examples that didn’t work.

  44. Charlie says:

    Hi Jonathan,

    Nice work! Got one question: is it possible to add any filters in case you want to read tweets with certain hash tags?

    Regards, Charlie

  45. Jonathan says:

    @Charlie That’s not possible, but it’s an interesting idea. If I find time I’ll add that feature in.

  46. Jon says:

    I keep getting syntax errors when I try to use the modified code pasted in over here. I’m struggling with Dan O’Neil time_diff code.

    I can see that the ‘ symbols is wrong.. I guess this website converts them. But even if I change them I still get a syntax error. :(

    Help please.

  47. w3 says:

    Is it possible to hide the hashtags ?

    commenting this part only removes the hyperlink on the hashtags

    //$tweet_desc = preg_replace('/(^|[\n\s])#([^\s"\t\n\r<:]*)/is', '$1<a href="http://twitter.com/search?q=%23$2">#$2</a&gt;', $tweet_desc);

    Thanks for such wonderful code!!!

  48. Jonathan says:

    @w3 – you should be able to change that line to:

    $tweet_desc = preg_replace('/(^|[\n\s])#([^\s"\t\n\r<:]*)/is', '$1', $tweet_desc);

  49. Adrian says:

    Just wanted to say thanks for this code. I’d been using some other code prior to this which worked ok, but had issues with caching. Your code works beautifully. Thanks!

  50. Steven Clark says:

    THe code has just stopped working for me. It no longer creates the twitter.txt file in the root of the site. It previously worked fine, but has just stopped for some reason. Any advice? Thanks