Warning, long post
The introduction
If you have ever seen my twitter interaction with Grace Smith then you know that we are pretty endearing to each other as friends and colleagues in the world of freelance. Over the past few years that we’ve known of each other, we have grown to trust each other and rely on the other’s professionalism and knowledge in our respective areas of expertise. Needless to say, we knew we’d work together professionally eventually, it was simply always a question of when.
Grace knew that her professional portfolio website needed a facelift in 2011. However, like with a lot of freelance professionals, personal projects tend to get the back seat frequently to incoming client work. This, she realized, opened the door for her bring myself into the mix and finally get us working on a project together. Grace proceeded to get the site to a certain point within her skills and knowledge, and then created a spec list of more advanced functionality that she had always desired for her site. The topic of this post was not one of her original requests. The “social” page was an idea that I brought to the table due to my desire to tinker and play with social media APIs. Grace ended up loving the idea of a single page showing her activity on many websites and gave me the green-light to have some fun and see what comes to fruition.
The Implementation
The files and code that I’m going to discuss are not the original implementation, but are what is live on Postscript5 Socialise page at the moment. I have taken only the relevant code/markup from the socialise page, as the rest is just a WordPress template for the page. I’m sure there is room for more consistency but I’m going to call that a work in progress.
Text files with the code. Viewing these directly may be easier to read than the Pre tags I’ll use below.
PHP Classes for Twitter/Dribbble
Retrieving our data
<?php
include('ps5socialclass.php');
//Twitter
$twitter = new Twitter();
$tweets = $twitter->getTweets('gracesmith', 'tweet-list', '1');
//Dribbble
$dribbble = new Dribbble();
$shots = $dribbble->getShots('gracesmith', 'dribbble-shots');
//Google+
$googledata = file_get_contents("https://plus.google.com/_/stream/getactivities/?&sp=[1,2,'117113113218324562969',null,null,10,null,'social.google.com',[]]");
//remove excess stuff
$gplus = str_replace(")]}'", "", $googledata);
$gplus = str_replace('[,' , '["",' , $gplus);
$gplus = str_replace(',,' , ',"",' , $gplus);
$gplus = str_replace(',,' , ',"",' , $gplus);
$gplus_data = json_decode($gplus);
//Sc.ripted
$scriptedfeed = file_get_contents('http://sc.ripted.com/rss');
//gracesmith.co.uk
$blogfeed = file_get_contents('http://feeds2.feedburner.com/gracesmith');
//last.fm
$lastfm = file_get_contents('http://ws.audioscrobbler.com/1.0/user/postscript5/recenttracks.rss');
//Flickr
$flickr = json_decode( file_get_contents("http://api.flickr.com/services/rest/?method=flickr.people.getPublicPhotos&format=json&api_key=KEY_HIDDEN&user_id=27475012@N06&per_page=3&nojsoncallback=1") );
$photoset = array();
if(isset($flickr)) {
foreach($flickr->photos->photo as $thepic) {
$picid = $thepic->id;
$photoset[] = json_decode(file_get_contents("http://api.flickr.com/services/rest/?method=flickr.photos.getInfo&api_key=KEY_HIDDEN&photo_id=$picid&format=json&nojsoncallback=1") );
}
}
Most of this should be pretty easy to follow. I am using file_get_contents() to pull in our data from the various APIs and RSS feeds, with the exception of Twitter and Dribbble. I stuck with JSON when possible, but it wasn’t available for everything that Grace wanted.
Google+ doesn’t have a proper API quite yet, but searching around showed various methods to get a person’s public stream in JSON format, after a bit of cleanup. That explains the multiple str_replace() calls. Once one gets publicly released, I hope to return to the Socialise page and alter the working code.
Flickr didn’t offer a direct way, that I could see at least, to pull the actual image URLs from a person’s public stream. So I looked over their documentation and found that I could construct the necessary url with the Public Photo stream information and a second API call with each photo’s information. This allowed me to complete what you see on the page and get thumbnail versions of her three latest pictures.
Now that I had all of this information in arrays or objects, I could start to loop through them all and output the appropriate markup.
<div class="twitter">
<?php if($tweets) { ?>
<div class="social_icon">
<a href="http://www.twitter.com/<?php echo $tweets[0]->user->screen_name; ?>" title="Twitter Profile">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Twitter-Social.png" alt="Twitter icon" /></a>
</div>
<?php foreach($tweets as $tweet) { ?>
<div class="fake_lemon_quotes">
<p class="thetweet"><?php echo $tweet->text; ?><br/>
- <a href="http://www.twitter.com/<?php echo $tweet->user->screen_name; ?>/status/<?php echo $tweet->id; ?>" title="Permalink"><?php echo human_time_diff(strtotime($tweet->created_at)) . ' ago'; ?></a></p>
</div>
<?php }
} else { ?>
<div class="social_icon error">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Twitter-Social.png" alt="Twitter icon" />
</div>
<p>We're sorry, we seem to be experiencing some technical difficulties with Twitter. Hopefully we'll be back shortly</p>
<?php } ?>
</div>
<div class="flickr">
<?php if($flickr) { ?>
<div class="social_icon">
<a href="http://www.flickr.com/people/<?php echo $flickr->photos->photo[0]->owner; ?>" title="Flickr Profile">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Flickr-Social.png" alt="Flickr icon" /></a>
</div>
<div class="flickrset">
<?php $counter = 1;
foreach($photoset as $photo) { ?>
<?php if($counter < 4) { ?>
<div class="flickrimg <?php if($counter == 3) echo 'lastshot'; ?>">
<a href="<?php echo $photo->photo->urls->url[0]->_content; ?>" title="<?php echo $photo->photo->title->_content; ?>">
<img src="http://farm<?php echo $photo->photo->farm;?>.static.flickr.com/<?php echo $photo->photo->server; ?>/<?php echo $photo->photo->id; ?>_<?php echo $photo->photo->secret; ?>_m.jpg" class="flickrpreview" alt="Flickr image preview" /></a>
<p><a href="<?php echo $photo->photo->urls->url[0]->_content; ?>" class="flickrlink" title="Shot URL"><?php echo $photo->photo->title->_content; ?></a>
<span class="datetaken"><?php echo date("j M", $photo->photo->dates->posted); ?></span></p>
</div>
<?php } $counter++;
} ?>
</div>
<?php } else { ?>
<div class="social_icon error">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Flickr-Social.png" alt="Flickr icon" />
</div>
<p>We're sorry, we seem to be experiencing some technical difficulties with Flickr. Hopefully we'll be back shortly</p>
<?php } ?>
</div>
<div class="theblog">
<?php if($blogfeed) {
$x = new SimpleXmlElement($blogfeed);
$posts = $x->channel->item;
$counter = 1; ?>
<div class="social_icon">
<a href="<?php echo $x->channel->link; ?>" title="<?php echo $x->channel->description; ?>">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/WP-Social.png" alt="Blog icon" /></a>
</div>
<?php foreach($posts as $post) {
if($counter == 1) { ?>
<p class="blogitem"><?php echo $post->title; ?><br/>
- <a href="<?php echo $post->guid; ?>" title="Permalink to <?php echo $post->title; ?>">Permalink</a></p>
<?php $counter++;
}
}
} else { ?>
<div class="social_icon error">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/WP-Social.png" alt="Blog icon" />
</div>
<p>We're sorry, we seem to be experiencing some technical difficulties with the blog feed. Hopefully we'll be back shortly</p>
<?php } ?>
</div>
<div class="googleplus">
<?php if($gplus_data) {
$gplus_data = $gplus_data[1][0]; ?>
<div class="social_icon">
<a href="http://plus.google.com/<?php echo $gplus_data[0][16]; ?>/posts" title="Google+ Profile">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Google-Social.png" alt="Google+ Icon" /></a>
</div>
<?php $counter = 1;
foreach($gplus_data as $plus) {
if($counter == 1) { ?>
<p class="pluses"><?php echo $plus[20]; ?><br/>
- <a href="https://plus.google.com/<?php echo $plus[21]; ?>" title="Permalink to <?php echo $plus[20]; ?>">Permalink</a></p>
<?php }
$counter++;
}
} else { ?>
<div class="social_icon error">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Google-Social.png" alt="Google+ Icon" />
</div>
<p>We're sorry, we seem to be experiencing some technical difficulties with Google+. Hopefully we'll be back shortly</p>
<?php } ?>
</div>
<div class="scripted">
<?php if($scriptedfeed) {
$x = new SimpleXmlElement($scriptedfeed);
$scriptedposts = $x->channel->item;
$counter = 1; ?>
<div class="social_icon">
<a href="<?php echo $x->channel->link; ?>" title="<?php echo $x->channel->description; ?>">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Tumblr-Social.png" alt="Blog icon" /></a>
</div>
<?php foreach($scriptedposts as $scripted) {
if($counter == 1) {
$description = explode(": ", $scripted->description);
$description[1] = str_replace('<p>', '', $description[1]);
$description[1] = str_replace('</p>', '', $description[1]);
?>
<p class="scripteditem"><span class="scriptedtitle"><?php echo $description[0]; ?></span> - <?php echo $description[1]; ?><br/>
- <a href="<?php echo $scripted->guid; ?>" title="Permalink to <?php echo $scripted->title; ?> on Sc.ripted">Permalink</a></p>
<?php $counter++;
}
}
} else { ?>
<div class="social_icon error">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Tumblr-Social.png" alt="Blog icon" />
</div>
<p>We're sorry, we seem to be experiencing some technical difficulties with the Sc.ripted feed. Hopefully we'll be back shortly</p>
<?php } ?>
</div>
<div class="dribbble">
<?php if($shots) { ?>
<div class="social_icon">
<a href="<?php echo $shots->shots[0]->player->url; ?>" title="Dribbble Profile">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Dribbble-Social.png" alt="Dribbble Avatar" /></a>
</div>
<!--The Lemon would be dunking on you-->
<div class="points">
<?php
$myshots = $shots->shots;
$counter = 1;
foreach($myshots as $shot) { ?>
<?php if($counter < 4) { ?>
<div class="shots <?php if($counter == 3) echo 'lastshot'; ?>">
<a href="<?php echo $shot->short_url; ?>" title="<?php echo $shot->title; ?>">
<img src="<?php echo $shot->image_teaser_url; ?>" class="shotpreview" alt="Shot preview" /></a>
<p><a href="<?php echo $shot->short_url; ?>" class="shotlink" title="Shot URL"><?php echo $shot->title; ?></a></p>
</div>
<?php } $counter++;
} ?>
</div>
<?php } else { ?>
<div class="social_icon">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/Dribbble-Social.png" alt="Dribbble Avatar" />
</div>
<p>We're sorry, we seem to be experiencing some technical difficulties with Dribbble. Hopefully we'll be back shortly</p>
<?php } ?>
</div>
<div class="lastfm">
<?php if($lastfm) {
$x = new SimpleXmlElement($lastfm);
$songs = $x->channel->item;
$counter = 1; ?>
<div class="social_icon">
<a href="<?php echo $x->channel->link; ?>" title="<?php echo $x->channel->title; ?>">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/LastFM-Social.png" alt="Last.fm icon" /></a>
</div>
<?php foreach($songs as $song) {
if($counter == 1) { ?>
<p class="songlisting">Listened to: <?php echo $song->title; ?><br/>
- <a href="<?php echo $song->link; ?>" title="Permalink to <?php echo $song->title; ?>">Permalink</a></p>
<?php $counter++;
}
}
} else { ?>
<div class="social_icon error">
<img src="<?php bloginfo('template_url'); ?>/images/socialicons/LastFM-Social.png" alt="Last.fm icon" />
</div>
<p>We're sorry, we seem to be experiencing some technical difficulties with Last.fm. Hopefully we'll be back shortly</p>
<?php } ?>
</div>
Hopefully this is also pretty self exclamatory. I either started looping through the data, or further manipulated the data with SimpleXMLElement for easier looping through the RSS feeds. A lot of the loops above include a counter because they didn’t offer a limit parameter and we only needed the latest post, or in the cases of Dribbble and Flickr, the latest 3 pictures. Each icon on the left of Grace’s socialise page links to her profile page, and then the right shows the latest message/item/pictures. I also included a general error message for each one in case there were any difficulties with retrieving the data.
But wait! You forgot to explain what’s going on with Twitter and Dribbble!
Ah yes, Twitter and Dribbble. On top of my interest in APIs, I have also recently formed interest in Object-orientated Programming in PHP, and viewed this as a chance to try my hand at it a bit more.
class Twitter {
private $twitter_api_url = 'http://api.twitter.com/1/statuses/user_timeline.json?screen_name=';
public function getTweets($twitter_username = false, $trans_name = false, $count = false) {
if(!$twitter_username) {
return 'Error, you have not provided a Twitter username.';
}
if(false === ($tweets = get_transient($trans_name))) {
$this->storeCache($twitter_username, $trans_name, $count);
}
return $this->readCache($trans_name);
}
private function readCache($trans_name = false) {
return get_transient($trans_name);
}
private function storeCache($twitter_username = false, $trans_name = false, $count = false) {
$tweets = wp_remote_get($this->twitter_api_url . $twitter_username . '&include_rts=1&count=' . $count);
$tweettext = json_decode($tweets['body']);
set_transient($trans_name, $tweettext, 60*5);
}
}
class Dribbble {
private $dribbble_api_url = 'http://api.dribbble.com/players/';
public function getShots($dribbble_username = false, $trans_name = false) {
if(!$dribbble_username) {
return 'Error, you have not provided a Dribbble username.';
}
if(false === ($shots = get_transient($trans_name))) {
$this->storeCache($dribbble_username, $trans_name);
}
return $this->readCache($trans_name);
}
private function readCache($trans_name = false) {
return get_transient($trans_name);
}
private function storeCache($dribbble_username = false, $trans_name = false) {
$shots = wp_remote_get($this->dribbble_api_url . $dribbble_username . '/shots');
$shotstext = json_decode($shots['body']);
set_transient($trans_name, $shotstext, 60*60*24*3);
}
}
The way that both Twitter and Dribbble work on the Socialise page is to initialize a new instance from the class, and then call the getTweets() and getShots() functions within the classes. Both of these functions utilize the WordPress Transients API to help with caching. So to pull the data for the user, you pass in the user ID, the name you want to use for the transients field in the Options table, and in the case of Twitter, how many tweets you want to retrieve. The classes will check to see if their is any valid cache stored. If there is none, it will make a new API call and set a new cache instance. If there is a valid cache record, it will simply pull that and not touch the API. The Twitter cache is set to expire every 5 minutes, since Grace tends to be a lot more active there than on Dribbble, which has a cache time of 3 days
Hopefully this makes sense to those with PHP experience. However, I’m sure not everyone who reads this will have a lot of experience, so if you have any questions regarding how any of it works, please ask in the comment section. I’ll do what I can to help clarify.
I know that I had fun with coding the socialise page and definitely learned new things in the process. Based on feedback from Grace and from my own twitter stream, the socialise page has been one of the most loved parts of Grace’s re-launched site, right up next to the custom 404 page with 30 Rock’s Liz Lemon.
Michael, superb post! I’m so glad you wrote about the process of developing this page, because so many people have asked how the Socialise page was created and I think this will answer a lot of questions out there.
I’ve loved every second working with you and without you the new Postscript5 would not be 1/10th of what it is 🙂
I even got you to download the first series of 30 Rock #result
Very nicely done sir! I’m just reworking my portfolio as we speak, something like this would be superb 🙂
I’m getting an error: Fatal error: Call to undefined function get_transient() in /home/***/social/ps5socialclass.php on line 7
:S
What liberating knowldgee. Give me liberty or give me death.
Just tried this out and I’m getting and error: Fatal error: Call to undefined function get_transient() in /home/***/social/ps5socialclass.php on line 7
:S
are you using this with WordPress? or just straight up? Sorry for the delay in approving the comment, it went to spam originally.
Michael, this a great post!
Thanks a lot for sharing your experience.
Keep it up.