Getting Started
To get going, you’re going to need two things at minimum. First and foremost, I’m not going to go into detail about how OAuth works, so for that we’ll use Abraham’s Oauth Library. If you don’t use composer, click the Manual Installation tab, and download it from his GitHub. Secondly, you’ll need a Twitter app. For this DIY, we’re going to be bending the REST API to do our bidding, and for that, you need an app. If you want to wing it and think you’ll be okay without instructions, here’s a handy link to get you there. If you’re not entirely sure how to register an app with Twitter, follow this blog post on iag.me which shows you how to register a Twitter app.
Once you make your app, go to ‘Keys and Access Tokens’ and note the following (you’ll need them in the code coming up):
- Consumer Key
- Consumer Secret
- Access Token
- Access Token Secret
On to the Code!!!
For the purposes of this tutorial, we’re going to use a simple singleton class. We know that for a definite we need a template tag to display the count. One other thing to keep in mind is Twitter’s rate limit; each API call has its own limits, so for this we’re going to use the GET search/tweets endpoint, which has a rate limit of 180 calls per fifteen minutes. Due to this rate limit, you want to make sure to cache the resulting count; for this I’m using transients, however, if you have a persistent cache like WP Engine, you may want to use wp_cache_get/set functions instead. So here’s our scaffolding:
<?php class Twitter_Counts { /** * @var Twitter_Counts null */ public static $instance = null; private function __construct() { // Fancy stuff. } public static function get_instance() { if ( is_null( self::$instance ) ) { self::$instance = new self; } return self::$instance; } public function tweet_count( $post_id ) { } } function Twitter_Counts() { return Twitter_Counts::get_instance(); } function display_tweet_counts( $post_id = 0 ) { if ( empty( $post_id ) ) { $post_id = get_the_ID(); } $cache_key = md5( 'twitter_counts_' . $post_id ); $count = get_transient( $cache_key ); if ( false == $count ) { $tc = Twitter_Counts(); // ... do stuff } return $count; }
Now that the scaffolding is setup, we need to start talking to Twitter with the OAuth library you downloaded. So setting it up is insanely easy (which is why I love this library):
require_once 'twitteroauth/autoload.php'; use Abraham\TwitterOAuth\TwitterOAuth; class Twitter_Counts { /** * @var Twitter_Counts null */ public static $instance = null; private $consumer_key = ''; private $consumer_secret = ''; private $access_token = ''; private $access_secret = ''; private function __construct() { // Fancy stuff. } public static function get_instance() { if ( is_null( self::$instance ) ) { self::$instance = new self; } return self::$instance; } public function tweet_count( $post_id ) { $oauth = new TwitterOAuth( $this->consumer_key, $this->consumer_secret, $this->access_token, $this->access_secret ); } }
If you are using Composer, you can ignore the first two lines. For me, I downloaded the library into a twitteroauth folder. Below that, you’ll see that there are new private variables. Since these are basically like passwords, it’s best if they’re inaccessible to anyone but the main class (although of course your requirements may be different and you’ll have to accommodate for that accordingly). Here is where those app values you copied from Twitter will come in handy; you’ll need to fill in these variables.
Line 29 is where the values are used. This literally does the OAuth handshake for you, and now all we have to do is make the request we want and process the results.
Getting the Data
Using the OAuth library makes it simple to do get requests. If you want to know all the parameters for the endpoint we’re using, you’ll need to consult the official search/tweets endpoint documentation. For now, we only need to worry about q, count, and include_entities.
Since we’re using the search endpoint, we need to search something unique to the page we’re looking at, or wanting counts for, that would be included in the tweet. Can’t get much more unique than the URL, right? We also want to return as many results as possible, this will help us in possibly going around the rate limit (unless you have a page with a million likes). For this, we set count to 100. Finally, we want to make sure to include Entities, since from what I can tell, those include the original URL prior to it being converted to the t.co shortener.
The code should look something like this:
public function tweet_count( $post_id ) { $defaults = array( 'q' => get_permalink( $post_id ), 'count' => 100, 'include_entities' => true, ); $oauth = new TwitterOAuth( $this->consumer_key, $this->consumer_secret, $this->access_token, $this->access_secret ); $statuses = $oauth->get( 'search/tweets', $defaults ); }
So what about counts?
Looking at the results on the official documentation you’ll see that you get back a JSON object. A quite large one in fact, but don’t let that scare you, in the end, it’s all data, and we tell it what to do! So what do we do? Well, since the JSON data is keyed, you’ll see the main key we’re concerned with, statuses. Lastly we should also check if the property is available after the transformation by using an isset check.
Having as many checks as necessary prevents your debug log filling up. Alternatively, if you want to log these errors, you can do so in a much nicer manner. For that, you should read my other post on Debugging WordPress Tips and Snippets.
Now that we got those checks out of the way, it’s a simple as running count() over the statuses. The code goes like so:
public function tweet_count( $post_id ) { $defaults = array( 'q' => get_permalink( $post_id ), 'count' => 100, 'include_entities' => true, ); $oauth = new TwitterOAuth( $this->consumer_key, $this->consumer_secret, $this->access_token, $this->access_secret ); $statuses = $oauth->get( 'search/tweets', $defaults ); if ( ! $statuses ) { return false; } if ( ! isset( $statuses->statuses ) ) { error_log( __LINE__ ); return false; } return count( $statuses->statuses ); }
The Finish Line
Now we have to wrap up–this is the simple part! Here we need to update our display_tweet_counts() template tag to actually use our tweet counting method. Since our count method can return a boolean value (true/false) we want to check for that and set the count to zero if there was a problem. Otherwise, we want to use the actual value.
So here’s the full code:
require_once 'twitteroauth/autoload.php'; use Abraham\TwitterOAuth\TwitterOAuth; class Twitter_Counts { /** * @var Twitter_Counts null */ public static $instance = null; // You'll need to fill these in with your own data. private $consumer_key = ''; private $consumer_secret = ''; private $access_token = ''; private $access_secret = ''; private function __construct() { // Fancy stuff. } public static function get_instance() { if ( is_null( self::$instance ) ) { self::$instance = new self; } return self::$instance; } public function tweet_count( $post_id ) { $defaults = array( 'q' => get_permalink( $post_id ), 'count' => 100, 'include_entities' => true, ); $oauth = new TwitterOAuth( $this->consumer_key, $this->consumer_secret, $this->access_token, $this->access_secret ); $statuses = $oauth->get( 'search/tweets', $defaults ); if ( ! $statuses ) { return false; } if ( ! isset( $statuses->statuses ) ) { return false; } return count( $statuses->statuses ); } } function Twitter_Counts() { return Twitter_Counts::get_instance(); } function display_tweet_counts( $post_id = 0 ) { if ( empty( $post_id ) ) { $post_id = get_the_ID(); } $cache_key = md5( 'twitter_counts_' . $post_id ); $count = get_transient( $cache_key ); if ( false == $count ) { $tc = Twitter_Counts(); $result = $tc->tweet_count( $post_id ); $count = false == $result ? 0 : $result; set_transient( $cache_key, $count, 1 * HOUR_IN_SECONDS ); } return $count; }
What about pages with 100+ shares?
That comes in part two, so stay tuned! In part two, we’re going to get into recursion, and how to walk over the results page-by-page. Keep an eye out for the second installment, and let me know if you have any questions!