» Publishers, Monetize your RSS feeds with FeedShow: More infos (Show/Hide Ads)
There is a lot of debate going on right now in the WordPress world about the WordPress Foundation barring all ThemeForest/CodeCanyon (Envato really) authors from speaking at WordCamp events.
I don’t want to rehash the argument here; the best place to get it is in the original post, and the comments on it.
Instead, I want to make a different point: Regardless of whether he is right or not, it’s good for WordPress that Matt makes these bold choices.
Open source projects benefit hugely from having a spearhead. In their early days most of all, it takes one or two people with drive to get the project off the ground. Then as it grows, dozens of people may contribute to each iteration of the project, but it takes an even stronger leader now to martial that effort together and drive it in the right direction.
And that problem that grows with size. If your project has 4 or 5 contributors, it’s easy to explain to each of them what you believe is best for the project, and to help them see why you believe that. You have the time for that, and the odds are that the small group who liked your style enough to join your fledgling project probably have similar opinions to you anyway.
When your project is WordPress and thousands of people contribute to it, that’s no longer possible. You can’t have 1-to-1s with everyone, and the community is so diverse that you’ll never convince everyone anyway.
Instead, you have to draw your line in the stand and stand by it. Matt has drawn his. If he wants the WordPress community to have the GPL at its heart, then he needs to be the staunchest supporter of it. Sometimes that means going further than what might be considered reasonable. That’s just part of the deal though. If he compromised more, then the emphasis on promoting the GPL would be lessened.
That takes me back to the title of this post. Matt takes a very hard line on the GPL. A lot of the time, he’ll be in the right, but not every time. The project still needs someone to consistently take that stance though.
There are plenty of examples of this. David Heinemeier Hansson created the massively popular Ruby on Rails framework, and his strong opinions annoy people all the time. Linus Torvalds is hardly one to mince words either.
Without strong leaders, who knows what will happen though? Decision by committee? One of my favorite open source projects used to be phpBB, until they took the decision that the next version should be rewritten from scratch… four years ago. A good leader would never have let that happen (Unless of course he wanted it to, in which case he’d have seen it through).
So to sum up; what I want to say is that regardless of your stance in this particular argument, it’s worth remembering it’s good that people get this fired up and they’re willing to stand their ground.
If enough of the community truly believes that the leader makes too many “wrong” decisions, then all it needs is a new opinionated leader to drive a fork. That’s what Matt did after all (Update 26/1/13: As pointed out in the comments, Matt forked it not because he didn’t like the current leader, but because he had vanished and the project needed a new leader. Sorry if this read misleadingly!). And just because the original project is already huge is no reason that a fork might not surpass it one day.
PS – For what it’s worth though, my opinion on this one is with the developers caught in the middle, which is best summed up by Pippin’s comment.
Most of all though, I think it’s mind boggling that one employee took it on themselves to pick out one individual developer, and hit them alone with this, right out of the blue. That’s one place where Matt really should get to the bottom of what went wrong.
On the 5th of March, Twitter is going to retire version 1 of its API. The replacement, version 1.1 is very similar, but with one major difference; every single call must be authenticated.
This means that come March, your existing API calls will break; including simple things like displaying tweets on your site. To fix this, you need to move to the new v1.1 API, and authenticate with Twitter.
Let’s look at how to do this in WordPress, by embedding a list of tweets on your site (Exactly how it is done in the footer here).
The Flow
- Authenticate with TwitterOAuth.
- Fetch the tweets (If our cache has expired).
- Did it fail? Use the backup.
- Did it succeed? Parse them into an array, then save to the database.
- Display.
This post will skip past most of the display and parsing logic. If you want to read up on those, check out the earlier posts: How to Use the Twitter API in WordPress and Add a Backup to Embedded Tweets in WordPress.
1 – Authenticate with TwitterOAuth
TwitterOAuth is a great free library from Abraham Williams. It makes the authentication a snap.
To get going, first download the twitteroauth folder from Github. Place this in your theme folder.
Now, create a new file called tweet-list.php, and paste the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | $numTweets = 3; // Number of tweets to display. $name = 'problogdesign'; // Username to display tweets from. $excludeReplies = true; // Leave out @replies $transName = 'list-tweets'; // Name of value in database. $cacheTime = 5; // Time in minutes between updates. $backupName = $transName . '-backup'; // Do we already have saved tweet data? If not, lets get it. if(false === ($tweets = get_transient($transName) ) ) : // Get the tweets from Twitter. include 'twitteroauth/twitteroauth.php'; $connection = new TwitterOAuth( 'xxxxxxxxxxxxxxxxxxxxxx', // Consumer key 'xxxxxxxxxxxxxxxxxxxxxx', // Consumer secret 'xxxxxxxxxxxxxxxxxxxxxx', // Access token 'xxxxxxxxxxxxxxxxxxxxxx' // Access token secret ); |
This code holds all of our configuration. The top options are fairly self-explanatory, and control the display on your site.
Let’s skip to the consumer and access keys section. These values are given to you by Twitter. The combination of these ensures to Twitter that you are the user you claim to be, but without giving your password.
This means that a Twitter app should never ask for your password. It also means that if you change your password, you won’t need to update any of your apps.
To find these details, go to https://dev.twitter.com/ and sign in.
Once in, hover on your name in the top right, and click “My Applications,” then “Create a New Application.”

Enter a unique name, description, and your site’s URL. You can leave the Callback URL empty (It would be used if your app was authenticating each of its users, but our’s will only ever use your account, so we’re going to put the details directly into the script).
Once created, you will be taken to a new screen where you can copy and paste the Consumer Key and Consumer Secret into the script.
Next, click the “Create my Access Token” button. This is a shortcut to authenticate your own account with your application.

Finally, copy the new Access Token and Access Token Secret into the script.
Fetch the Tweets
To make a Twitter API call through TwitterOAuth, you use a HTTP verb (GET, POST, or DELETE), specify the API, and then pass in the arguments as an array. You can read the full documentation here.
The API we want to use is GET statuses/user_timeline. Therefore, our code looks as follows:
1 2 3 4 5 6 7 8 9 10 11 12 | // If excluding replies, we need to fetch more than requested as the // total is fetched first, and then replies removed. $totalToFetch = ($excludeReplies) ? max(50, $numTweets * 3) : $numTweets; $fetchedTweets = $connection->get( 'statuses/user_timeline', array( 'screen_name' => $name, 'count' => $totalToFetch, 'exclude_replies' => $excludeReplies ) ); |
If you exclude replies, the total returned from the API will be less than the total your requested. The $totalToFetch logic ensures that we request enough tweets that even after removing the replies, we will have the number we wanted.
Did The Request Fail?
If the request worked, it would have returned with a HTTP status code of 200. We can simply check this, and if it is anything else, use the backed up tweets in our database (We will set this in a minute).
1 2 3 | // Did the fetch fail? if($connection->http_code != 200) : $tweets = get_option($backupName); // False if there has never been data saved. |
Did the Request Succeed?
If it all went well, we need to pull out the number of tweets we asked for, and parse the data we need from it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | else : // Fetch succeeded. // Now update the array to store just what we need. // (Done here instead of PHP doing this for every page load) $limitToDisplay = min($numTweets, count($fetchedTweets)); for($i = 0; $i user->name; $permalink = 'http://twitter.com/'. $name .'/status/'. $tweet->id_str; /* Alternative image sizes method: http://dev.twitter.com/doc/get/users/profile_image/:screen_name */ $image = $tweet->user->profile_image_url; // Message. Convert links to real links. $pattern = '/http:(\S)+/'; $replace = '<a href="${0}" target="_blank" rel="nofollow">${0}</a>'; $text = preg_replace($pattern, $replace, $tweet->text); // Need to get time in Unix format. $time = $tweet->created_at; $time = date_parse($time); $uTime = mktime($time['hour'], $time['minute'], $time['second'], $time['month'], $time['day'], $time['year']); // Now make the new array. $tweets[] = array( 'text' => $text, 'name' => $name, 'permalink' => $permalink, 'image' => $image, 'time' => $uTime ); endfor; |
Store the Result
With the data parsed, the last thing is to store it. We will make two copies:
- Cached version, which will expire and trigger an update (The transient value).
- Permanent version, which will be used if a subsequent request to Twitter fails.
1 2 3 4 5 | // Save our new transient, and update the backup. set_transient($transName, $tweets, 60 * $cacheTime); update_option($backupName, $tweets); endif; endif; |
Display
Now you have a $tweets array with the data successfully parsed into it. All that remains is to display it. We’ll make use of WordPress’ human_time_diff function to give relative times, like 5 minutes ago.
1 2 3 4 5 6 7 8 9 10 11 12 | // Now display the tweets. ?> <ul id="tweets"> <li> <p> <span class="tweet-time"> ago</span> </p> </li> </ul> |
And that’s it. As before, you can download the complete script here:
One final tip is that to test your code, you will want to invalidate your cache (So that the fetch code is run). The easiest way to do this is to add this line right under the config data:
1 | delete_transient($transName); |
Feel free to comment if you have any issues! Or let me know if there are any other API examples you’d like to see.
In the last post, we looked at the basics of CSS transitions, including how to use easing to control how your animation flows. In this post, I want to look deeper at a very powerful easing feature; cubic bezier curves.
In short, they let you define exactly what path your animation will take, and thanks to some awesome community tools, they are extremely easy to use (No maths required at all). This gives you total flexibility with your transitions.
One of the best tools is the free Cubic-Bezier.com by Lea Verou. It lets you make new curves and see them in action just by dragging the handles. We’ll use it for all of the demos in this post, and it’s well worth a bookmark!
The syntax is simple too. Let’s look at an example. This simple code can be used to animate an element left or right:
1 2 3 4 5 6 7 8 9 10 11 12 | a.slide { position: relative; transition: left 1.5s linear; } a.slide-left { left: 0; } a.slide-right { left: 580px; } |
The linear part is actually a shortcut though. What it really says is that you want the animation to follow a particular path on a graph, which we’ll look at more in a second.
For now, you could rewrite that line to use the cubic-bezier function and get the exact same animation:
1 | transition: left 1.5s cubic-bezier(0, 0, 1, 1); |
That one line can now be used in any transition, and to tweak the animation, all you need to do is change those 4 bracketed values.
What Is This Graph?
Let’s take a look at the linear graph, and see what it means.

The x-axis represents the time of your animation. The y-axis represents how far it is between its starting state, and its ending state.
The x-axis is easier to understand; the far left is the very start of your animation. As you go right, time progresses towards the ending time.
To grasp the y-axis, think of the slide-to-the-right animation. At the very bottom of the y-axis, our button hasn’t moved, it’s still on the left of the screen. Half way up the y-axis though, it’s now half way between the left and right side of the screen, and at the top of the axis, it is now at the far right.
The Ease Graph
Ease is the default easing style used, and it has the following graph:

I’ve drawn a red line representing a linear animation on the graph, to help highlight the difference. Reading from left-to-right (start to finish) on the graph, you can see that:
- The
easeanimation dips under thelinearline slightly at the beginning, so it starts off a little slower. - It then rises more steeply to be much well above the linear line, so the middle of the animation is quite fast.
- Finally, the line flattens out again at the top, so the animation slows down gracefully towards the end.
The values we pass to cubic-bezier are the co-ordinates of the pink and green circles on each graph (In the first, the green circle is hidden under the white corner at the top right of the graph).
The specific points used for ease are:
Therefore, the code to draw this graph is:
1 | transition: left 1.5s cubic-bezier(.25, .1, .25, 1); |
Custom Animations
Now that you understand what these graphs tell you, let’s make some custom animations.
Say you liked the ease animation, but you wanted to make it a little slower again at the start. You would take the first (pink) point, and drag it to the right. You are saying that rather than getting to this stage after 25% of the animation, I now want it to take 40% of the animation’s total time.
1 | transition: left 1.5s cubic-bezier(.4, .1, .25, 1); |
Now let’s go more extreme. Let’s say you wanted the middle of the animation to be really, really fast, but the start and end to be more gradual.
1 | transition: left 1.5s cubic-bezier(1, 0, 0, 1); |
Or of course, you could make the start go faster and the end go slower.
1 | transition: left 1.5s cubic-bezier(0.1, 0.25, 0.25, 1); |
How about a really slow start with a very fast end?
1 | transition: left 1.5s cubic-bezier(1, 0, 1, 1); |
That should be plenty of regular examples for now. There are endless combinations you can set up, so let’s move on to another nifty trick; going beyond the ends of the graph.
Going Past The Start and End
All of our animations so far have started at 0 on the y-axis, and ended at 1. You don’t have to stick to these limits though. It is entirely possible to have your animation go past its supposed end point, and then circle back to it.
In this example, our button start off slow, build up speed, slide right past its goal, and then reverse back into place.
1 | transition: left 1.5s cubic-bezier(0.4, 0.1, 0.55, 1.3); |
These sorts of animations can feel much more natural. As an example, think of scrolling webpages on your phone/tablet. When you flick down to the very bottom or the very top, the page goes right past the top and bottom and then glides back into place.
If you really wanted, you can also do the opposite and have your animation go backwards in the beginning.
1 | transition: left 1.5s cubic-bezier(0.4, -0.45, 1, 0.9); |
There are 2 natural limitations to these techniques though:
- The time can’t be adjusted in this way. It will always be whatever length you specified.
- For some CSS properties, going above/below the y-axis boundaries will have no effect, e.g. opacity. The element can’t be any less visible than invisible…
The best thing to do is to experiment. The syntax is incredibly simple. Set up your animation using the cubic-bezier for the default ease function. Once it’s working, tweak the values to see what looks best.

Transitions (basic animations) are one of the most popular additions in CSS3, and one of the easiest to implement for big gains on your site.
A transition is simply an animation from one set of CSS properties to another. So for example; whilst before you may have had links with blue text, which suddenly turned orange when you hovered on them, you would now replace that sudden jump with a more graceful animation.
In this post, we’ll look at the basic syntax, step through some examples, and finally take a brief look at current browser support.
Syntax
The process is very simple. Let’s say you wanted to set up the blue->orange link example:
- Setup CSS for the link to be blue.
- Setup CSS for the link to be orange.
- Add a transition.
The end result would be something like this (Don’t worry about the transition syntax right now. I just want to show you how little code this really is):
1 2 3 4 5 6 7 8 9 10 11 12 | a:link, a:visited { color: blue; -webkit-transition: color 0.5s; -moz-transition: color 0.5s; -o-transition: color 0.5s; transition: color 0.5s; } a:hover, a:active { color: orange; } |
Now, let’s look at the syntax. The first thing to point out is that as in the example above, you will need to use browser prefixes (-o- for Opera, -moz- for Firefox etc.). This is a nuisance, but a minor one as the actual values you give to each one are identical at least.
The transition property takes up to 4 arguments:
- Property to animate – You can see a list of all supported properties here.
- Length of transition.
- Delay before starting.
- Easing. We’ll look at this more in a minute, but think of it as the “style” of animation.
So now looking back at the first example, you can see that I specified to animate the color property for 0.5 seconds. The browser then defaulted the delay to 0 (i.e. start immediately), and used its default easing.
Now let’s look at a few more examples of this in use. For each one, I’ll put the code here, and you can see the demo on this page.
1 – A Simple Fade
1 2 3 4 5 6 7 8 9 10 11 12 | a.fade { background: #0068b8; -webkit-transition: background-color 0.8s; -moz-transition: background-color 0.8s; -o-transition: background-color 0.8s; transition: background-color 0.8s; } a.fade:hover, a.fade:active { background-color: #05406d; } |
2 – Slide Left and Right
I use some JavaScript first in this example, to add/remove slide-left and slide-right classes to the link.[1]
1 2 3 4 5 6 7 8 9 10 11 | $('.slide').on('click', function() { slideLeftRight($(this)); }); function slideLeftRight(ele) { if(ele.hasClass('slide-left')) { ele.removeClass('slide-left').addClass('slide-right'); } else { ele.removeClass('slide-right').addClass('slide-left'); } } |
The CSS is just as simple as before though:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | a.slide { position: relative; -webkit-transition: left 1.5s; -moz-transition: left 1.5s; -o-transition: left 1.5s; transition: left 1.5s; } a.slide-left { left: 0; } a.slide-right { left: 580px; } |
3 – Multiple Properties at Once (Zooming)
In the previous examples, we have always specified a particular property to animate. You don’t have to do it this way though. Instead, you can specify a comma-separated list of properties, or better yet, type all, and the browser will animate anything it can.
In this example, we use the all keyword to animate the width, height and line-height all at once.
1 2 3 4 5 6 7 8 9 10 11 12 | a.zoom { -webkit-transition: all 0.8s; -moz-transition: all 0.8s; -o-transition: all 0.8s; transition: all 0.8s; } a.zoom:hover, a.zoom:active { width: 200px; height: 50px; line-height: 50px; } |
4 – Stretch Search Box on Focus
The last example is to enlarge the search box for typing when a user clicks into it. This effect has become increasingly popular since being included in the TwentyEleven default theme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | .search-field { width: 108px; border: 1px solid #ccc; background: #f5f5f5; padding: 5px; -webkit-transition: width 0.8s; -moz-transition: width 0.8s; -o-transition: width 0.8s; transition: width 0.8s; } .search-field:focus { width: 200px; } |
Easing
Now, let’s return to the easing property, as it can be very powerful. Earlier, I said it is like the style of the animation, which is true. More specifically though, it controls your rate of animating.
Consider an animation which moves an object from left to right. It could make the entire animation at the same pace (e.g. it moves 100px every 0.2 seconds, whether it’s the first 0.2 or the last), or it could accelerate rapidly at the start and slow towards the end, or start off slow and pick up speed as it goes.
There are a lot of possibilities, but the easiest way to understand them is to see them in action. The last example I’ve put together uses the slide left/right animation again, but with a different easing option on each button.
Click the “click here” link under section 5 of the examples, and you will see the 5 buttons animate. All of them use the same animation, for the same length of time, but with the easing function labelled on their button.
You can specify this with your other properties, e.g.
1 | transition: left 1.5s ease-out; |
A more flexible approach however can be to use extra CSS classes, e.g.
1 2 3 4 5 6 7 8 9 10 11 12 13 | a.ease-in { -webkit-transition-timing-function: ease-in; -moz-transition-timing-function: ease-in; -o-transition-timing-function: ease-in; transition-timing-function: ease-in; } a.ease-out { -webkit-transition-timing-function: ease-out; -moz-transition-timing-function: ease-out; -o-transition-timing-function: ease-out; transition-timing-function: ease-out; } |
Now to change any animated elements easing, I simply give it the ease-in or ease-out CSS classes.
Browser Support
Browser support for CSS transitions these days is great! Chrome has supported it from version 1.0, Firefox from version 4.0, Safari from 3.2, and Opera from 10.5.[2]
As you would expect though, Internet Explorer is lagging behind. They are fully supported in IE10 (And without any vendor prefix, which is a nice touch), but not in any previous version. Given that IE10 currently only runs on Windows 8, this means it will be a long time before the majority of IE users see your transitions.
Still, don’t let that stop you. The other browsers have great support for these, they’re an extremely simple upgrade to make to your site, and it all degrades gracefully for IE users anyway, so there’s no real loss!
[1] – If you were to replace all of your jQuery animations with CSS ones, you may find that you can rewrite the functions here in plain old JavaScript and not load that library at all.
[2] – Stats from Mozilla Developer Network.

In this post, I will show you a simple way to add a distraction-free “Reading Mode” to your blog. You can see the end result on this Reading Mode demo site (Click the highlighted “Reading Mode” link).
The purpose of adding a feature like this is to enable a visitor to remove all the clutter of your site, and focus solely on the post itself.
In an ideal world; there would never be a need for such a feature. In reality though, sites have numerous other goals to achieve, such as brand building, serving ads, promoting other content etc. In this way, you can compromise between the two. Do what you need when the user first arrives, but get out of the way when they decide what to read.
This script is also available as a free plugin on Github (Direct download link). No configuration needed, just upload and hit activate. Or feel free to fork and improve it.
The Method
The trick is simple. We will use Jack Moore’s awesome Colorbox plugin for jQuery to “pop out” the post and fade out the rest of the page when needed. Click the “Reading Mode” link here to see how this looks.
All we need to do is set some options for the plugin, and insert our link. We will create the link with JavaScript so that it won’t appear to search engines or RSS readers as part of your post’s content.
1 – Get the Files
The first step is to download the latest Colorbox files from here. When you open up the zip folder, you will get a folder structure like this:

The 5 example folders contain different styles that you can use for your Colorbox popup. Load up the index.html file in each folder and click a link to see how it looks.
- Copy the jquery.colorbox-min.js file from the ‘colorbox‘ folder into your theme’s /js/ folder (Create one if it doesn’t exist).
- Choose one of the example styles, and copy the colorbox.css file into your theme’s directory.
- In the same example folder, copy all of the /images/ files into your theme’s images directory.
As a sidenote; you may prefer to create a ‘colorbox’ directory in your theme’s directory instead, and put all of these files there. That’s what I would do personally, but for this tutorial, I’m following how the default TwentyEleven theme organizes itself.
2 – Queue the Files
Now, you have all the files you need for the script. The next step is to load them into your page. To do this, open your theme’s functions.php file (Create a file with that name if none exists) and paste the following before the closing ?> tag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /** * Queue Reading Mode scripts and styles. */ function pbd_rm_scripts() { if(is_single() ) : wp_enqueue_script( 'colorbox', get_bloginfo('template_directory') . '/js/jquery.colorbox-min.js', array('jquery'), '1.3.19', true ); wp_enqueue_script( 'pbd-reading-mode', get_bloginfo('template_directory') . '/js/pbd-reading-mode.js', array('jquery', 'colorbox'), '0.1', true ); wp_enqueue_style( 'colorbox', get_bloginfo('template_directory') . '/colorbox.css', array(), '1.3.19' ); endif; } add_action('wp_enqueue_scripts', 'pbd_rm_scripts'); |
This simply tells the theme about each of your files; the two Colorbox files and the reading-mode JavaScript file we’re going to create next. They will all be loaded in the site now, but only on single-post pages.
3 – Wrap Your Post Content in a Div
For Colorbox to know what counts as your post content, we need to have one element that contains that post, and nothing else. Some themes will already have this, but the class names vary from theme to theme. To make things simpler, we’re going to use a WordPress filter to add our own div around the_content.
Carrying on in functions.php, paste the following:
1 2 3 4 5 6 7 8 9 10 11 | /** * On single posts, it wraps the content in div. */ function pbd_rm_content_filter($content) { if(is_single() ) { $content = '<div class="rm-content">'. $content .'</div>'; } return $content; } add_filter('the_content', 'pbd_rm_content_filter'); |
This filter runs every time your theme displays a post’s content. If the user is on a single post page, then our code will wrap a div with class .rm-content around it.
4 – Create the Link
With everything in place, you’re ready to set up your “Reading Mode” link. In your theme’s /js/ directory, create a new file named ‘pbd-reading-mode.js’, and paste the following into it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | jQuery(document).ready(function($) { // Set this to the CSS selector of the element that wraps your post content. // e.g. .entry or .entry-content var selector = '.rm-content'; // The HTML for your "View This in Reading Mode" link. var html = '<p class="rm-button">View this post in <a href="#" class="reading-mode" rel="nofollow">Reading Mode</a>.</p>'; $(selector) .prepend(html) .find('.reading-mode') .colorbox({ innerHeight: "80%", innerWidth: 700, inline: true, href: selector }); }); |
On line 4, you can set the selector for the element that wraps the post (No need if you’ve used the filter from step 3), and on line 7, you can customize the HTML for the “Reading Mode” link.
Line 10 adds that HTML to our content wrapping element (i.e. it creates the link). On line 12, we initiate Colorbox with options for its size, and specify its content as the wrapping element. You can read more about the possible options here on Colorbox’s site.
5 – Style It
At this point, you can save your work and try it out. The “View in Reading Mode” link should appear at the top of your posts, and clicking it should load the post content in a popup.
The last step is to add a little CSS to make that popup as legible as possible. Feel free to experiment with this, but here is what I have used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /** * Reading Mode */ #cboxLoadedContent > div { text-align: left; padding: 20px; font: 16px/24px Georgia, serif; width: 90%; } #cboxLoadedContent > div .rm-button { display: none; } #cboxLoadedContent > div p { margin-bottom: 24px; } #cboxLoadedContent > div h2 { font-size: 22px; margin-bottom: 24px; } #cboxLoadedContent > div h3 { font-size: 18px; margin-bottom: 24px; } #cboxLoadedContent > div h4 { font-size: 16px; margin-bottom: 24px; } |
Some notes on the CSS:
- I use #cboxLoadedContent > div to match the content wrapping element. This should make it a little more flexible than specifying a CSS classname.
- Line 11 is used to stop the “Reading Mode” link from appearing inside the popup.
- If you want to use your theme’s existing post styles, you can skip the filter and instead specify an element like .entry-content in the JavaScript. The element itself is moved into the popup, so all of its styles will then apply to the popup as well.
And that’s all there is to it. Not a lot of code, for a useful little feature. Let me know what you think. Is this sort of thing a good idea, or better avoided?

As bloggers, we spend a lot of time working on a range of different tasks. This includes things like checking stats, monitoring ad revenue, and interacting on social media.
That leaves us with a lot of sites to log into. But by adding a few WordPress plugins, you can pull some of this data back into the WordPress dashboard, which saves you time by giving a quick snapshot overview of everything.
Analytics 360

Analytics 360 brings Google Analytics and Mailchimp (Newsletter service) data together, and displays them on a subpage under the WordPress dashboard. This helps in accessing analytics tools like visualizing site traffic and managing mailing lists easier even without leaving WordPress.
A Google Analytics account is required, but you do not need to be a Mailchimp user.
My Gmail
My Gmail is another simple dashboard plugin which displays all the unread mails from your Gmail account without any need to load Gmail.
However, this plugin works only over HTTPS, so you will need to have purchased an SSL certificate.
Full Comments on Dashboard

The Recent Comments Box in WordPress facilitates managing and replying to comments easily, but it just shows the comment excerpt and the user has to load another page to view the full comment.
This limitation can be overcome with the Full Comments on Dashboard plugin. It extends the default widget to show the full comment, allowing you to reply properly.
Full Comments on Dashboard Download.
Google Adsense Summary
This is another important plugin which lets the user to check Adsense earnings for a stipulated time. After the successful installation of the plugin, the user should enter his username and password in the settings page and the earnings from his/her account are displayed anytime.
Google Adsense Summary Download.
Feedburner Stats

Feed Stats is a simple statistical tool for viewing and managing FeedBurner statistics. Under the Dashboard, you will find a “Feed Stats” link with graphs based on your recent FeedBurner data.
The plugin displays your FeedBurner statistics for things like subscribers on your blog, which the feed readers are being used, and which of the posts are most popular with subscribers.
Twitter Tools
The Twitter Tools plugin makes it easy for the WordPress user to manage his Twitter posts. This plugin helps to Tweet directly from the dashboard itself without opening another browser tab.
Using this plugin, the user can automatically Tweet about new posts, create a collection of his Tweets and publish on the users blogs, Tweet from any page on the blog, etc.
Plugin Central

Finally, Plugin Central is a plugin for managing all other plugins for WordPress. This lets you easily install plugins by naming them, or linking to a zip folder, as well as adding multiple plugins at once, and updating all of your existing plugins directly from the dashboard.
And that concludes our list. Not all of these plugins will be for you, but if you spend a lot of time on some of the services above, then give it a go and see if it saves you some time!
If you need more ideas, check out the dashboard tag on the WP.org Plugin Directory.
If you have any other suggestions for great dashboard plugins, let me know in the comments!

I want to share a fantastic little tool that I’ve only recently come across; Live.js, written by Martin Kool.
This JavaScript file automatically checks for changes to your CSS and JavaScript files, and refreshes them.
That means you can have your text editor in one half of the screen, and a web browser in the other. When you save changes in the editor, the updates are reflected immediately in your browser. It really helps to speed up your development time.
A lot of text editors offer HTML previews and such, but they only work for plain .html pages. The great thing about Live.js is that it works on any webpage, including a working WordPress theme.
Live.js for WordPress Plugin
I have put together a (very!) simple plugin for WordPress to make things that little bit easier. You can download it here (Rename the folder to “pbd-livejs” once you unzip it), or view it on Github.
It automatically adds Live.js to your theme, but only when you are logged in and working locally. You should never use this script on a live site, and the plugin ensures you won’t.
It also keeps the script out of your theme code, which is good because it’s purely for development. There is no need for it to be present in the files on your server.
One final note is that although Live.js also supports sensing HTML changes, I have disabled this. With it enabled, your webpage essentially “flashes” as it loads every second or two.
Let me know if you try it out, or if you’ve used Live.js before already!

The following is a guest post by Andy Walpole.
There are many parts of the WordPress API which are fantastic but there are also other parts which, I would argue, are lacking.
The Settings API was introduced in version 2.7 to allow the semi-automation of form creation. All credible Content Management Systems and frameworks have their own set of functions or classes for the same purpose. Drupal has a multitude of hooks which can be leveraged, while CodeIgniter uses a combination of the Form Validation Class and the Form Helper .
When creating a WordPress plugin recently I wanted to create a dynamic form to insert data into a field in the option database table. I decided to create a class for this purpose with the intention of creating reusable code for future projects. For ideas, I closely followed previous tutorials written by Alison Kleinschmidt and Sarah Neuber .
I quickly ran into a brickwall. My main issue that I have with the Settings API is that it’s unintuitive and too basic. It is such an abstraction away from the meat and bones of PHP / HTML form creation and validation that it confuses the professional coder rather than assists them.
As a coding exercise I decided to take a different approach to WordPress form creation using OOP and one that had a more familiar user interface. The code I have written so far can be examined on a Git Gist that I have created. There are some excellent features of the Settings API, such as inserting new form fields into existing core admin pages; and this code doesn’t attempt to replicate that.
This purpose of this article is to provide some feedback on the successes and difficulties of my endeavourer.
There are some key aspects in understanding the option API access class. Firstly, I placed it into three files reflecting an MVC architecture. Admittedly, the script itself doesn’t tightly fit this segregation but it is a commonly used and understood way of organising code. In the view file there are key WordPress hooks such as add_action() and add_options_page(); in the controller class are the purpose built form field creation methods and in the model class are validation, sanitisation, security and database functions.
Another aspect is that I have used namespaces and late static bindings that arrived with PHP 5.3. It is a convention amongst WordPress developers to cater for servers that have versions of PHP lower than 5.3. I don’t follow that convention. If your hosting company is running versions of PHP which are three years behind or more then take you custom elsewhere because they are not providing to you a satisfactory service.
Completed Code
This is a lengthy tutorial, and walks you through exactly how the script works. Instead of manually copying and pasting all of the code chunks to assemble the file though, it will be simpler just to copy the finished code from here.
1 – Instantiate the Class
$aForm = array( 'option_name' => 'a_url_here', // has to be alphanumeric and underscores only 'page_title' => 'A page title here', // Main page title 'page_url' => 'a-url-here', // URL 'dynamic_output' => FALSE); // Should the form be generated on more input new \OptionView\Form_View($aForm); |
The above should be reasonably self-explanatory. In the $aform array are placed four different key values: the name of the option database field, the page title, the page URL and a boolean value – I’ll explain this in detail later but for now it’s set to false.
The create_html_cov() method is called by WordPress add_options_hook(). In here is the code that envelopes the form:
extract(self::$form); $form = '<div class="wrap">'; $form .= screen_icon(); $form .= "<h2>{$page_title}</h2>"; $form .= '<div id="result">'; echo $form; if (isset($_POST['submit'])) { $error = array(); // validation here if (empty($error)) { $this->update_option($form); } else { echo $this->failure_message($error); } // end if error } // end if isset submit echo '</div>'; echo '</div>'; |
Anybody who codes PHP will be familiar with the above. After form submission any error messages are added to an array and then displayed. At the top is a div with a class of wrap. This is essential so that the admin CSS is used. Note as well that the page title originally made in the $aForm array is now been called from the static $form attribute. How? The values were used in the method below which in turn was called in the view constructor. The values within config_settings() are used throughout the script.
/** * Form_Controller::config_settings() * Main array for important values throughout the class * @param string $option_name * @param string $page_title * @param string $page_url * @param boolean $dynamic_output * @return array */ protected function config_settings($option_name, $page_title, $page_url, $dynamic_output = FALSE) { // put together the output array $output['option_name'] = $option_name; // name of option database field $output['page_title'] = $page_title; // name of page $output['page_url'] = $page_url; // url of page $output['dynamic_output'] = $dynamic_output; return $output; } |
2 – Creating the Form Fields
It is first necessary to create a form array:
$form = array( 'method' => 'post', 'action' => '#result', 'enctype' => FALSE, 'description' => 'Add a description here'); |
The description will go inbetween the legend form tags at the top of the form. The rest are basic attribute form values.
When invoking the create_form() method the form array has to be the first value:
$this->create_form($form); |
From here more arrays have to be created for individual form fields. Note how closely the PHP fits the HTML form attributes – something that the user interface of the Settings API is distant from.
$textOne = array( "input" => "text", // input type "name" => "textOne", // name attribute "desc" => "This is a text field", // for use in input label "maxlength" => "200", // max attribute "value" => "YES", // value attribute "select" => FALSE // array only for the select input ); $textTwo = array( "input" => "text", "name" => "textTwo", "desc" => "This is another text field:", "maxlength" => "250", "value" => "YES", "select" => FALSE); |
The input can be text, textfield, radio, select or checkbox. The desc key is used in the form field label. Values for text or textfields should be “YES” only and by doing so instructs the class to create a sticky form field. Select is only used for drop-down fields, radio buttons and checkboxes (more of which later). Even if an option is not used there stills needs to be “false” or “null” stated.
Now the two arrays are placed in create_form():
$this->create_form($form, $textOne, $textTwo); |
The form has now been created.
3 – Validation and Sanitisation
The following method is used for sanitizing data. If, for instance, it is desirable to remove backslashes or trim white space from the ends of strings, then it is possible to do so like this:
$this->sanitize($form, 'trim_post'); $this->sanitize($form, 'stripslashes'); |
That’s straight forward enough. Lets take a look at the code behind this in the model class:
/** * Form_Model::sanitize() * * @param string $handle * @param array $form_output * @return array */ protected function sanitize(&$form_output, $handle) { switch ($handle) { case 'sanitize_post': array_walk_recursive($form_output, array($this, 'sanitize_post')); break; case 'trim_post': array_walk_recursive($form_output, array($this, 'trim_post')); break; case 'strip_tags_post': array_walk_recursive($form_output, array($this, 'strip_tags_post')); break; case 'empty_value': array_walk_recursive($form_output, array($this, 'empty_value')); break; case 'stripslashes': array_walk_recursive($form_output, array($this, 'stripslashes')); break; default: die("The value you ended into the sanitize() method is not recognised: $handle"); break; } // end switch } /** * Form_Model::trim_post() * * @param string $att * @param string $single * @param array $form_output * @return array */ function trim_post(&$form_output, $att = NULL, $single = NULL) { if ($single == NULL) { if (is_array($form_output)) { array_walk_recursive($form_output, 'trim'); } else { $form_output = trim($form_output); } } else { extract(static::$form); foreach ($form_output[$option_name] as $thisKey => $result) { if (preg_match("/$att/i", $thisKey)) { if (is_string($thisKey)) { $form_output[$option_name][$thisKey] = trim($result); } } } return $form_output; } } |
As demonstrated by the trim_post() method it is easy to widen the sanitisation features of the code. Just copy and paste the method, change the name and change trim to another function such as strip_tags() or htmlspecialchars(). Then add a new section to the switch statement in sanitize().
Notice that $this->sanitize($form, ‘trim_post’) trims all input data. It is possible to just sanitise one field by bypassing sanitize() and call its child method directly:
$this->strip_tags_post($form, 'feedName', true); $this->trim_post($form, 'feedName', true); $this->stripslashes($form, 'feedName', true); |
Necessary parameters above are the form array, the name of the attribute and a boolean value of true.
Validation is similar to sanitization. Here are two examples of using validation methods:
/** * Form_Model::validate_email() * * @param string $att * @return boolean */ protected function validate_email($form_output, $att) { extract(static::$form); if (is_array($form_output) && is_string($att)) { foreach ($form_output[$option_name] as $thisKey => $result) { if (preg_match("/$att/i", $thisKey)) { if ($result !== "") { if (!filter_var($result, FILTER_VALIDATE_EMAIL)) { return false; } } // end if } // end if } // end foreach } else { die("Make sure that the inputs for validate_url() is an array and a string"); } } |
To extend the different types of validation it is, like sanitization, a straightforward process of copying the code, renaming it and changing the filter_var().
One other useful validation method is duplicate_entries():
if ($this->duplicate_entries($form) == false) { $error[] = "Please make sure that all input values are unique"; } |
This will ensure that no one inputted value is identical.
4 – Select Drop-Down Lists, Radio Buttons & Checkboxes.
The creating and validating of these form fields is slightly different.
Firstly, when you create radio buttons or checkboxes you must do so by specifying the total number of each in the select value of the field creation array:
$aRadioButton = array( "input" => "radio", "name" => "radioButtonName", "desc" => "A radio button", "maxlength" => FALSE, "value" => "mac", "select" => 3); $aCheckBox = array( "input" => "radio", "name" => "aCheckBoxName", "desc" => "First checkbox", "maxlength" => FALSE, "value" => "mushrooms", "select" => 3); |
The above would specify that there are three checkboxes in the form and three radio buttons (obviously, it is necessary to create separate arrays for all individual fields).
As is standard in HTML, the radio buttons must have the same name attribute. In this script checkboxes must have individual name attributes. I’m working on a solution to creating checkboxes with the same name attributes! Notice as well that, unlike for text or textareas, the value is no longer “YES” but a value unique to all fields.
Previously, checking for empty submitted values was done so through the empty_value() method
// Check whether there are any empty form values: if ($this->empty_value($form) === FALSE) { $error[] = "Please don't leave any input values empty"; } |
Radio buttons and checkboxes have their own methods:
if ($this->empty_checkboxes($form, 2) === FALSE) { $error[] = "Please check at least two checkboxes"; } if ($this->empty_radio_butts($form) === FALSE) { $error[] = "Please make sure that you check one of the radio buttons"; } |
In empty_checkboxes() the second digit is the minimum number of checkboxes that the user needs to click. If none is specified then the default is one.
When creating a drop-down list it is necessary to add any array of values to the select key:
$cities = array( 'Shanghai', 'Karachi', 'Mumbai', 'Beijing', 'Moscow', 'Sao Paulo', 'Tianjin', 'Guangzhou', 'Delhi', 'Seoul', 'Shenzhen', 'Jakarta', 'Tokyo', 'Mexico City', 'Istanbul'); $select = array( "input" => "select", "name" => "selectName", "desc" => "Select here select here", "maxlength" => FALSE, "value" => TRUE, "select" => $cities); |
Putting together the above examples, the code for a form with multiple different types of input would look like this:
function create_html_cov() { // essential. extract(self::$form); $form = '<div class="wrap">'; $form .= screen_icon(); $form .= "<h2>{$page_title}</h2>"; $form .= '<p>This is the admin section for Affiliate Hoover plugin</p>'; $form .= '<div id="result">'; echo $form; if (isset($_POST['submit'])) { $error = array(); // ESSENTIAL! Do not leave this out. Needs to come first $form = $this->security_check($_POST); // Sanitization $this->sanitize($form, 'trim_post'); $this->sanitize($form, 'stripslashes'); // Validation if ($this->validate_email($form, 'feedName') === FALSE) { $error[] = "Please make sure that the email addresses are correct"; } if ($this->validate_url($form, 'urlName') === FALSE) { $error[] = "URL is not right"; } // Check whether there are any empty form values for text or textarea fields: if ($this->empty_value($form) === FALSE) { $error[] = "Please don't leave any input values empty"; } // don't allow empty checkboxes if ($this->empty_checkboxes($form, 2) === FALSE) { $error[] = "Please check at least two checkboxes"; } // don't allow empty radio buttons if ($this->empty_radio_butts($form) === FALSE) { $error[] = "Please make sure that you check one of the radio buttons"; } // Make sure that none of the form values are duplicates if ($this->duplicate_entries($form) === FALSE) { $error[] = "Please make sure that all input values are unique"; } if (empty($error)) { $this->update_option($form); } else { echo $this->failure_message($error); } // end if error } // end if isset submitForm echo '</div>'; // Create the form here: $aTextField = array( "input" => "text", // input type "name" => "textOne", // name attribute "desc" => "This is a text field", // for use in input label "maxlength" => "200", // max attribute "value" => "YES", // value attribute "select" => FALSE // array only for the select input ); $aTextArea = array( "input" => "textarea", "name" => "aTextArea", "desc" => "This is a textarea:", "maxlength" => FALSE, "value" => "YES", "select" => FALSE); $aRadioButtonOne = array( "input" => "radio", "name" => "radioButtonName", "desc" => "A radio button one", "maxlength" => FALSE, "value" => "mac", "select" => 3); $aRadioButtonTwo = array( "input" => "radio", "name" => "radioButtonName", "desc" => "A radio button two", "maxlength" => FALSE, "value" => "linux", "select" => 3); $aRadioButtonThree = array( "input" => "radio", "name" => "radioButtonName", "desc" => "A radio button three", "maxlength" => FALSE, "value" => "pc", "select" => 3); $aCheckBoxOne = array( "input" => "checkbox", "name" => "aCheckBoxOne", "desc" => "A radio button one", "maxlength" => FALSE, "value" => "mushrooms", "select" => 3); $aCheckBoxTwo = array( "input" => "checkbox", "name" => "aCheckBoxTwo", "desc" => "First checkbox", "maxlength" => FALSE, "value" => "pizza", "select" => 3); $aCheckBoxThree = array( "input" => "checkbox", "name" => "aCheckBoxThree", "desc" => "First checkbox", "maxlength" => FALSE, "value" => "chicken", "select" => 3); $form = array( 'method' => 'post', 'action' => '#result', 'enctype' => 'multipart/form-data', 'description' => 'Add a new form underneath'); $this->create_form($form, $aTextField, $aTextArea, $aRadioButtonOne, $aRadioButtonTwo, $aRadioButtonThree, $aCheckBoxOne, $aCheckBoxTwo, $aCheckBoxThree); echo '</div><!-- end of wrap div -->'; } |
The above will create a form that looks like this:
5 – Dynamic Forms
Creating a form layout that enables the easy adding, deleting or editing of multiple form blocks with one submit button is almost impossible with PHP alone. The best solution would be AJAX but this is primarily intended as a server-side coding exercise.
To create a dynamic form with my Option API access class then the only value to change is the dynamic output booleanwhen instantiating the class.
It is now possible to create a form with different sections that can be independently deleted or edited.
An example being:
Conclusion
The code I linked to in the Git Gist is still in its development stage. There are a number of issues. Firstly, it is not possible to create checkboxes with the same name attribute – they have to be separate. Secondly, it is only possible to create one set of radio buttons per form section.
Attempting to create a dynamic form like I have done so in this Option API access class create an entire new level of code complexity. Because a server-side script like PHP has only limited use of the DOM, it is necessary to find solutions based on loops of the form arrays and a lot of maths. This is an aspect of the code that I will continue working on.
But, positive parts of the Option API access class are that it takes care of all tables HTML and CSS peculiar to the WordPress admin section; it is relatively easy to create and validate new forms with a multitude of fields, and all security aspects of the WordPress backend like wp_verify_nonce() are incorporated into the form submission process.

The following is a guest post by Austin Gunter of WPEngine.com.
If you’re looking to boost performance for a content-heavy WordPress installation, adding a cache like Varnish is a great way to boost your site’s performance.
NB – This is an advanced topic, and only relevant if you have full control over your server (e.g. you’re on a VPS). It does not apply to regular webhosting.
What is Varnish?
Varnish Cache is a web app accelerator, or a caching HTTP reverse proxy. Install it in front of any server that speaks HTTP and configure it to cache the contents, and the Varnish community claims that it speeds up delivery by a factor of 300 – 1000x.
In a nutshell, caching a webpage means storing a copy of that website’s content for future visitors to see. Varnish can cache pages of your WordPress site so that your server doesn’t need to call the database each time a visitor visits your site. This reduces server load because the stored copy means the webserver doesn’t have to go find the same images and content for each visitor.
Varnish caches page data in virtual memory, so your site will load much faster, providing a killer SEO boost. Google found that every additional 0.5s load time meant 20% fewer site visitors (Source). Reducing the page load time can dramatically increase your visitors and boost your search rankings at the same time.
Varnish themselves have put together a great video for explaining what it does, as simply as possible:
Installing Varnish
Varnish is free software that you can install this afternoon. It runs on Linux, and primarily FreeBSD, but can work on other platforms as well. Once installed, you can customize how incoming requests will be handled with the Varnish Configuration Language (VCL).
This flexibility means that each Varnish install can and should be designed with a particular site in mind.
You’ll want to begin with a basic configuration of Varnish, and then begin testing small changes as you get the hang of how it works with your particular site, and how the functions work. There are several different subroutines that tell Varnish how to respond to requests going in and out, to errors, and so on.
I’ll start with a basic set up, and then cover basic VCL functions that you’ll be tweaking as you go.
Step by Step
Setting up Varnish is pretty simple. I’m going to assume you’re using Apache on a Debian-based system. It does work on other systems too though.
Start in the command line with this:
apt-get install varnish |
First, you want to configure Apache to listen on port 8080 of the localhost interface. Varnish can then listen on port 80 (Where your visitors connect). In /etc/apache2/ports.conf, edit the following settings:
NameVirtualHost 127.0.0.1:8080 Listen 127.0.0.1:8080 |
To get Varnish to start (it won’t by default), edit the following in /etc/default/varnish
START=yes DAEMON_OPTS=”-a EXTERNAL_IP_ADDRESS:80 \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G” |
Replace EXTERNAL_IP_ADDRESS with the IP of your external IP address. It can also be an internal address if your server is setup behind a load balancer, or something like NGINX. This setting controls what IP address and port you want Varnish to bind to and listen on.
Now edit /etc/varnish/default.vcl, which should already exist with lots of it commented out. Start by changing the default backend.
backend default { .host = “127.0.0.1”;</p> .port = “8080”;</p> } |
Now Varnish knows that Apache is listening on port 8080 of the localhost interface, and you can start using the functions. The majority of the work will be with vcl_recv and vcl_fetch, and if you don’t call an action in this subroutine and Varnish reaches the end, it will execute the built-in code from default.vcl.
Note: you never want to cache wp_admin, wp_login, or similar pages.
Here’s how it works – the 4 basic subroutines in your Varnish config which you need to handle requests are:
sub vcl_recv |
This is called at the beginning of a request and tells varnish what to do with that particular request: whether or not to serve it, how to serve it, and which backend to use.
Varnish receives a request from your browser, vcl_recv and chooses to do one of 3 things with it: vcl_hash, vcl_pass, and vcl_pipe (More details on those in a minute!). You can change the request if you like, altering cookies or removing the request header.
sub vcl_fetch |
vcl_fetch is called after a document has been successfully retrieved from the backend. Use this to alter the response headers, trigger ESI processing, or try alternate backend servers if the request failed.
The request object, req, is still available, and there is also a backend response, beresp, which contains the HTTP headers from the backend.
sub vcl_hash |
You can call hash_data on the data you want to add to the hash. This subroutine may terminate with calling return() with one of the keywords, hash or proceed.
sub vcl_deliver |
Call this before a cached object is delivered to the client. This may terminate with deliver, error code, or restart. Deliver, delivers the object to the client. Error will return the specified error code to the client and abandon the request. Restart will restart the transaction and increase the restart counter. If the number of restarts exceed the max_restarts, varnish emits a guru meditation error.
Actions
There are a number of actions you can take inside each subroutine as you customize Varnish:
pass
Pass the request and subsequent response to and from the backend server, not cached. pass can be called in both vcl_recv and vcl_fetch.
lookup
Called from vcl_recv to deliver content from cache even if the request indicates that the request should be passed. You can’t call lookup from vcl_fetch.
pipe
From vcl_recv, pipe short-circuits client and backend connections and Varnish will just sits there passing the data back and forth, logging the data, so logs will be incomplete. Beware that with HTTP 1.1 a client can send several requests on the same connection and so you should instruct Varnish to add a “Connection: close” header before actually calling pipe.
deliver
Deliver the cached object to the client. Usually called in vcl_fetch.
esi
ESI-process the fetched document.
The Varnish community has a very detailed tutorial on using VCL, and the functions that you can perform on your site, which I won’t go into here.
Example Configurations
Hopefully this post has given you a good introduction to Varnish, but the best way to truly start playing with it is to see some sample configuration files.
The Varnish website has a great collection sample configurations, which can make a perfect starting point for building your own.
More specifically, Mattias Geniar has put up sample fetch and receive configs for WordPress on Github.
Suffice to say that Varnish is very customizable and can do wonders for WordPress. A great example would be a Membership site. You can customize Varnish to cache content based on a user’s permissions, on their membership level, or even if they’re just visiting your site.
Facebook uses Varnish to great avail, and no doubt they have customized it to hell and back, but it goes to show you how efficient and flexible Varnish is.
Plugins
Update (13 Apr 2012): Thanks to Alexander and Pothi for letting us know about an updated Varnish plugin for WP 3.0+ and one on WP.org also for purging your cache (Also, check out both of these guy’s blogs for more Varnish and WP performance tips!)
The varnish plugin will purge the cache when content is added or edited, and it works for multisite. The plugin does include a sample config file for your Varnish backend, however, keep in mind that when you’ve customized it for one particular site, those customizations may or may not play nicely with a different site.
Have you had any luck installing Varnish for your WordPress? What works and doesn’t? Feel free to share some of your ideas in the comments!
Update (24 Jul 2012): Jason Ormand has put together a cool Bash script to make configuring an NGINX server with Varnish and WordPress even faster.

It’s a relatively simple process to add some flair to your blog comments these days. I want to look at 6 of the best free tools and give you a quick overview of the benefits of each.
Let’s start with one you’ve likely heard of before…
Disqus
Disqus uses JavaScript to replace your WordPress comments with their own system. You lose the total design flexibility that vanilla WordPress comments offer, but you gain a large feature set, including:
- Real-time Comments – New comments appear on the page without needing to refresh. Not a major benefit for smaller sites, but very good if your posts tend to get a flurry of comments in the first few hours of publishing.
- Social Media Integration – Users can comment via Facebook or Twitter, and share to those sites as well if they choose.
- Global Profiles – A user’s Disqus profile shows their comments across all Disqus sites. The idea is that a Disqus user will be more likely to comment on a Disqus blog because of this, and it makes for a fast way to find out more about a commenter.
- Plenty of other features, like inline comment replies or “subscribe to updates.”
Another good thing to bear in mind with Disqus is that they are very active. Disqus has been around for several years now, and it still innovates and updates regularly. They even have a mystery Disqus 2012 project to be released soon.
And incase you’re wondering, your comments can all be imported to Disqus, and exported back to WordPress if you choose to leave Disqus. You won’t be hostage to their system.
WordPress Install Instructions for Disqus
Livefyre
Livefyre is very similar to Disqus, and as far as I can tell, they offer almost exactly the same feature set. Real-time comments, social media, user profiles, subscription etc. are all included as standard.
Livefyre is a newer contender in this market though, but personally I think they have an edge over Disqus in 2 ways:
- Simpler Installation – Disqus isn’t hard to install, but it does have JavaScript issues often enough (For anyone who has ever used it, hands up if your comment counts vanished?). I haven’t used Livefyre on as many sites yet, but it hasn’t given me any hassle.
- Sleeker Design – This is just a matter of taste, but I like the softer design of Livefyre’s comments more. It’s not a big difference, but every little helps, and even their website has a better design.
WordPress Install Instructions for Livefyre
IntenseDebate
IntenseDebate is similar again to the 2 above (The last tool in this style, I promise!). The reason I mention it is because it has one very big draw for WordPress users; it’s owned by Automattic, the creators of WordPress.
The feature-set is generally quite similar to the others (Though real-time commenting is noticeably absent), and their design is very similar as well.
Whilst there is nothing that I can say is “wrong” with IntenseDebate, it’s hard to recommend them overly either. Their site and script don’t feel as sleek as Livefyre, and their community no longer seems as vibrant as Disqus’.
Oh, and the bright green “180p” badges look awful… (Look down the sidebar of their blog to see what I mean).
Begin IntenseDebate Install Process
Facebook Comments
The Facebook Comments script is exactly what you would expect; it replaces your own comments with a Facebook wall. Users must be signed in to their Facebook accounts, and then what they see is identical to Facebook.com.
There are two big advantages to this approach:
- Publicity – Users are commenting with their Facebook profiles, so with a little luck, their friends will see the comment and look at your article as well.
- Anti-spam – Every commenter must use their Facebook account, so spammers have a much harder time bulk-spamming blogs.
The strongest feature is also the strongest drawback. Some people (like me) keep their accounts completely separate to their work or online profiles, and so, you will miss out on those people’s contributions.
There is a plugin for adding these comments to WordPress, but it seems to be broken with WordPress 3.3. For a developer though, the installation is similar to any other Facebook script. You can get the code here.
(If you have trouble with the install or can’t find a reliable guide online, let me know and I’ll put together a walkthrough).
Jetpack
Jetpack is essentially a set of WordPress plugins, which is a topic I’m about to cover in the final section. What sets Jetpack apart is its ease of use. You install one package, and cherry pick the features for you want.
In the case of commenting, that means 3 things:
- Subscriptions – Users can be emailed when follow-up comments are left after their reply. This is a great way to keep conversations going.
- Gravatar Hovercards – When you mouse over a commenter’s name, this will pull up a little bubble with their image and a quick bio with a link to their full profile on Gravatar.
- Sharing – Jetpack makes it easy to add social media sharing links anywhere on your page, including around the comments area.
Install Guide for WordPress Jetpack
Other WordPress Plugins
There are hundreds out there, but I want to focus on just 4 of them:
- AJAX Edit Comments - This allows users to edit their comments for up to 15 minutes after submitting them. It is a good way to enable users to fix typos, and avoid “double-posting” if they want to add another line. It is particularly popular on coding blogs, where awesome commenters might contribute code snippets of their own but find they get messed up when displayed.
- CommentLuv – CommentLuv gets the latest blog post from a commenter’s RSS feed, and automatically adds a link to it after their comment. This is a great way to thank visitors for taking the time to post a comment.
- GD Star Rating – Not the simplest of plugins to configure, but GD Star Rating has a wealth of options that can enable star ratings (Or thumbs) on WordPress comments (And posts).
- WordPress reCAPTCHA - Spam can be a nightmare on WordPress blogs, but this plugin makes it simple to add Google’s captcha (“Type the letters…”) tool to your blog.
Which Do You Use?
I’ve always used regular WordPress comments with some plugins here on Pro Blog Design, because I like the flexibility of creating your own comments design. The topic of this blog is another reason; it’s good for us to show something unique.
More and more though, I’m working with clients who are choosing the scripts above (Particularly Disqus and Livefyre). These scripts are very impressive, and can even be a good way to speed up the development time of a new site.
I think the Facebook widget is only a great bet if you specifically try to encourage users to interact with you via Facebook (e.g. your site takes signups via Facebook Connect, or you need as much social sharing of your app as possible etc.).
I’d love to hear what approach you’ve taken on your site, and why you went down that road!

Update (20 January 2013): There is a newer version of this script available now: Authenticate Your Twitter API Calls Before March.
Around this time last year, I showed you how to embed tweets in WordPress using the Twitter API.
Today, I want to improve on that script by adding a backup to it. Twitter is often down or unresponsive, and when it is, the previous script fails and displays nothing.
With a backup, we store an extra copy of each set of tweets that we find. When Twitter is down, we can then use this backup to display tweets as normal.
WordPress “Transients” vs “Options”
We will use both of these, so it’s best to explain quickly what they are. An “option” is a value that is stored permanently in your database. There is an option for your site title, the slogan etc.
A transient is very similar to an option, but it also has an expiry time. After this time, the transient is deleted. For more information, read using the Transients API.
The transient is perfect for caching tweets. We can store the results from Twitter, and when the transient expires, fetch new ones.
The option however is perfect for the backup, because we never want it to expire (We will just write over it each time we update it).
The Code
The majority of this code is from the previous article. For that reason, I will only explain the new additions. To understand how we process each tweet and display it, check out the original post.
And if you’d rather just skip on, you can grab the fully completed code here.
Find where in your theme you would like the tweets to appear (e.g. sidebar.php), and paste the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php /* * JSON list of tweets using: * http://dev.twitter.com/doc/get/statuses/user_timeline * Cached using WP transient API. * http://www.problogdesign.com/wordpress/add-a-backup-to-embedded-tweets-in-wordpress/ */ // Configuration. $numTweets = 3; $name = 'problogdesign'; $cacheTime = 5; // Time in minutes between updates. $exclude_replies = false; // Leave out @replies? $transName = 'list-tweets'; // Name of value in database. $backupName = $transName . '-backup'; // Name of backup value in database. |
These are your configuration options. How many tweets do you want to display? From which user? And how often should we fetch new updates? (It’s fine to go to 1 minute even, but it means one of your users will have a slow page load every minute, rather than one every 5 minutes).
The only change from before is that I’ve added the option to exclude @replies from your results, and we now have 2 names for values in the database. Let’s carry on.
The next section of code is the bulk of the script. Essentially, it fetches the data from Twitter’s API, picks out the parts we want, formats them, and saves them as an array called $tweets. The original post explains it all one step at a time.
You can also read about this specific Twitter API call here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | // Do we already have saved tweet data? If not, lets get it. if(false === ($tweets = get_transient($transName) ) ) : // Get the tweets from Twitter. $response = wp_remote_get("http://api.twitter.com/1/statuses/user_timeline.json?screen_name=$name&count=$numTweets&exclude_replies=$exclude_replies"); // If we didn't find tweets, use the previously stored values. if( !is_wp_error($response) && $response['response']['code'] == 200) : // Get tweets into an array. $tweets_json = json_decode($response['body'], true); // Now update the array to store just what we need. // (Done here instead of PHP doing this for every page load) foreach ($tweets_json as $tweet) : // Core info. $name = $tweet['user']['name']; $permalink = 'http://twitter.com/#!/'. $name .'/status/'. $tweet['id_str']; /* Alternative image sizes method: http://dev.twitter.com/doc/get/users/profile_image/:screen_name */ $image = $tweet['user']['profile_image_url']; // Message. Convert links to real links. $pattern = '/http:(\S)+/'; $replace = '<a href="${0}" target="_blank" rel="nofollow">${0}</a>'; $text = preg_replace($pattern, $replace, $tweet['text']); // Need to get time in Unix format. $time = $tweet['created_at']; $time = date_parse($time); $uTime = mktime($time['hour'], $time['minute'], $time['second'], $time['month'], $time['day'], $time['year']); // Now make the new array. $tweets[] = array( 'text' => $text, 'name' => $name, 'permalink' => $permalink, 'image' => $image, 'time' => $uTime ); endforeach; // Save our new transient, and update the backup. set_transient($transName, $tweets, 60 * $cacheTime); update_option($backupName, $tweets); |
The change to notice comes on line 8. Here, we check if an error occurred or Twitter failed to give us back our tweets. We check the latter by looking at the HTTP status code returned. You can read more about how Twitter uses status codes here, but the short of it is that anything other than 200 means something went wrong.
We only process the page returned if it passes these two checks.
The other change is on the very last line, where we save the transient as normal, but also save the permanent “option”. This updates our backup with the most recent set of results.
Moving on, all we need to do if the fetch fails is populate the $tweets array with the value from our permanent backup.
1 2 3 | else : // i.e. Fetching new tweets failed. $tweets = get_option($backupName); // False if there has never been data saved. endif; |
Now we can display the tweets as before. To check that we do have tweets to display, we check if $tweets is false (If a backup has never been made, get_option() will have returned false to it).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Now display the tweets, if we can.
if($tweets) : ?>
<ul id="tweets">
<?php foreach($tweets as $t) : ?>
<li>
<img src="<?php echo $t['image']; ?>" width="48" height="48" alt="" />
<div class="tweet-inner">
<p>
<?php echo $t['name'] . ': '. $t['text']; ?>
<span class="tweet-time"><?php echo human_time_diff($t['time'], current_time('timestamp')); ?> ago</span>
</p>
</div><!-- /tweet-inner -->
</li>
<?php endforeach; ?>
</ul>
<p><a href="http://twitter.com/#!/<?php echo $name; ?>">[ Follow us on Twitter ]</a></p>
<?php else : ?>
<p>No tweets found.</p>
<?php endif; ?> |
At the end, I’ve added a simple error message, rather than displaying nothing. This will only appear if fetching tweets fails, and a backup has never been made.
And that’s it done. If you used the previous script, this can be slotted in over it and all you need to do is update your details in the configuration section at the top. Now your tweet list should be that bit more reliable.
The full code can be found here.

As 2012 begins, I want to look back over the past year to see what can be learnt from it. Learning from your mistakes is less glamorous than a list of grand New Year’s Resolutions (Though I do like those too), but often more effective at ensuring the next year is even better than the last.
With that in mind, what follows are some of the lessons I have picked out from my own 2011. I would love to hear if these resonate with you.
When looking back at your own year, I’d encourage you not to look for when things went “wrong” as such, but simply to think of how things may have gone differently. That’s what I’ve done. If I were to go back, I may well do some of these things the same, but considering the alternatives is a great way to learn.
Plan For the Follow-Through
When working on a large project, launch day is the natural target. All of your work is dedicated to releasing the product.
That is obviously crucial, but equally important is planning in advance to build on the momentum from your big release. That could mean anything from analyzing its success, planning the next release, or simply capitalizing on the good PR.
I learned this lesson from the relaunch of this site in May. We had been working on it on and off for almost a year, so it had been a huge project, but in the push to launch, we built up a backlog of other work.
I worked on this and wrote a number of articles following the launch, but not enough. The backlog took priority because as far as schedules were concerned, the relaunch project was complete. The follow-through should have been a clearly defined of the project.
Short-Term is More Tempting than Long-Term
How many times have you heard a freelancer say they can’t find the time to work on their site or a personal project? Even though they really enjoy it?
All freelancers hit this issue, and I was no different. I think the reason for this is extremely simple; short term benefits have a clearer and more obvious benefit than long term ones. In my case, PliablePress has been hurt by this. Improving the framework brings me incremental benefits over time, but taking on a large client project pays off right away.
The trick here I believe is a balancing act. If you spend all of your time on short-term goals, you’ll never get past where you are right now. But similarly, if you only work on long-term plans, you may not survive that long. Don’t let one overcrowd the other.
Focus Beats Multitasking
This is a common enough productivity tip that you have likely heard before. Only work on one thing at a time. It makes sense, and it will come as no surprise to you that closing down Twitter and Skype etc. will help you work better.
The bigger lesson is to organize yourself each day so you know which projects you will be working on. You don’t need a step-by-step breakdown of the tasks (Though some people swear by that), a simple list of the top x projects that need your attention today will do.
This is something I see in my work again and again. The days in which I have a clear list of projects to work on, and I tackle them one by one, are my most productive days. Sometimes you can even spend a whole day on just one project, which is fantastic. Focus only on the next task, not all of the tasks.
Sidenote – When making your list, try to avoid adding any murky tasks that don’t have clear completion objectives, e.g. research can go on indefinitely so before you start, make sure you’re aware of how you will know when you’re done.
New Does Not Necessarily Equal Worthwhile
In the tech world, we all love the speed at which things change. The impulse is to try as many new products and services as we can, because who knows what the next game changer will be?
And therein lies the problem. It could be anything, and there are new things coming at you every day. You can’t try them all, and the attempt is a waste of your time. I think a better approach is to let the community try them out for you, and join in when a winner seems to be emerging.
A good example of this is social networks. There are a million different variations out there and new ones all the time. Most won’t catch on, and the time put into them is lost to you. I joined Quora after consistently reading good responses on it for months, but I’m not on Pinterest, Path or any of the other current “hot” networks yet. I’ll check back again in a few months.
The exception is tools that really add value to your business (Webapps you use, development software etc.). Again, I wouldn’t try every single one as it comes out, but every few months, take a look around. It’s fun too, playing with new toys is hard to count as work!
Join The Communities You Value
The web is full of little communities, and you could take part in them from morning to night. On a productivity note, you might try to cut down on this when you have deadlines to meet.
It’s possible to go too far with this though. Once you’ve read a lot on a particular site, and especially after you’ve started enjoying the comment discussions, you can get a lot more out of taking part as well.
I used to post thousands of comments in forums when I was younger, and then again on blogs as I started Pro Blog Design. That’s been somewhat lessened in 2011, so I’m looking forward to starting again. In particular, I’ve massively enjoyed reading Hacker News and Stack Overflow, so it’s about time I joined up.
Planning is Both Impossible and Essential
A slight paradox, but it’s true. It’s impossible to plan for everything that may happen, even over just a short period of time. But with no plan, things inevitably fall into chaos.
I like to think I’m quite organized, and I always have a good idea of how the next 3-4 weeks will flow. I don’t think any of my “ideas” have ever gone 100% to plan though.
Things change. Something will crash, a client will take longer with their feedback, an awesome new project will come up etc.
You need to plan enough to help things run smoothly, but be flexible enough to cope with the inevitable curve balls that come at you. I’m still not sure of the exact formula for this, but it seems that the more experience you have of it, the better your plans and estimates become.
Fight to Work On What You Love
When you run a small business, you’re responsible for everything. I have the pleasure of working with a number of other people throughout the year here, but no matter what, the final responsibility is still mine.
In practice, what this means is that the tasks you can’t easily find someone else to take care of, you do yourself.
That’s fine in the short term, and often even beneficial (Knowing you have to submit your accounts yourself keeps your finances very organized throughout the year!), but it isn’t a long term solution.
You didn’t start a business to work on things you don’t enjoy. There will always be some jobs you just have to do, but when the ones you don’t enjoy are the majority, you need to fix it.
I’m quite happy with where I am in regards to this now; the trick for me was angling towards certain types of projects. Great clients with interesting projects means that more or less everything is fun!
The Truest Test of a Business is When You Leave It
Again, this is something you’ve likely read before, and it’s spot on. In September, I was away for around 3 weeks, and it likely affected every part of the business.
I planned in advance as much as I could, but every change has a knock-on effect. By prioritizing the projects that could most likely be finished before I left, I was leaving others to be done largely after I returned. I was also lining up new projects to be worked on while I was away, but I was still needed at various points to give my thoughts.
In other words, the month before and the month after the trip were crazy. There is no easy fix to this, but removing the dependency on you is definitely a big area to work on if you want your business to grow.
Conclusion
Those were the biggest new “lessons” I would take from my 2011. I’ve deliberately chosen from when things could have gone better, not from when they went great. I’d be happy to share those as well if you like, but learning from mistakes is often the more revealing analysis so they weren’t included here.
In return, I’d love to hear about your year. What have you learnt from it and what will you do differently this year?
Before I finish, I want to apologize for how quiet the past months have been. They’ve been some of the busiest months I’ve ever had, but I’ve missed blogging greatly. January will be a quiet month as well, sorry, but after the 29th, I will be back with a vengeance. I’ve learnt a lot, so there’s a lot I want to share!
If you reached this far in the post, thank you for still being a reader after all this time.
Happy New Year everyone!
Main photo by razvan.caliman.
Recently, one of our clients needed to let visitors create WordPress posts (Or custom post types of course) from the website itself.
In this post, I’m going to walk through some of the WordPress functions I used to achieve this. We’ll go over creating the post itself, adding categories/tags, and adding metadata.
(We’ll skip the stage where you build a form, validate what users enter etc. and just focus on the WordPress functions instead)
Create the Post
The method to create the core post is simple. Essentially, you build a $post object, and then insert it with wp_insert_post().
You can use our $post reference to speed this up. Let’s take a look at some examples:
1 2 3 4 5 6 | $new_post = array( 'post_title' => 'Our New Post', 'post_content' => 'Our filler text for the post.' ); $post_id = wp_insert_post($new_post); |
That would create a new post, with the title “Our New Post”, and the filler text we’ve entered.
If wp_insert_post() is successful, it returns the ID of the new post it has created (Which we assign to $post_id here). If it fails, it will return 0.
By default, the status is set to “Draft”, so let’s tweak it to publish the post instantly:
1 2 3 4 5 6 7 | $new_post = array( 'post_title' => 'Our New Post', 'post_content' => 'Our filler text for the post.', 'post_status' => 'publish' ); $post_id = wp_insert_post($new_post); |
You can set just about any value from the $post variable, e.g.
1 2 3 4 5 6 7 8 9 10 | $new_post = array( 'post_title' => 'Our New Post', 'post_content' => 'Our filler text for the post.', 'post_status' => 'publish', 'post_type' => 'page', 'post_author' => 2, 'post_date' => '2011-09-01 15:10:30' ); $post_id = wp_insert_post($new_post); |
Our post is now a page, written by user #2, and scheduled to be published on the 1st of September.
Categories, Tags and Custom Taxonomies
Let’s move on to categories and tags. You can either set these as you make the rest of the post, or add them separately later. Let’s start by seeing how it is done in wp_insert_post().
For categories, you pass in an array containing the IDs of the categories you want to add, e.g. for categories 9 and 29:
1 2 3 4 5 6 7 8 | $new_post = array( 'post_title' => 'Our New Post', 'post_content' => 'Our filler text for the post.', 'post_status' => 'publish', 'post_category' => array(9, 29) ); $post_id = wp_insert_post($new_post); |
Tags are a little easier. Instead of passing in the IDs, you can use the names of the tags themselves, e.g.
1 2 3 4 5 6 7 8 | $new_post = array( 'post_title' => 'Our New Post', 'post_content' => 'Our filler text for the post.', 'post_status' => 'publish', 'tags_input' => array('tag1', 'Filler Tag') ); $post_id = wp_insert_post($new_post); |
You can even set the terms of a custom taxonomy. This uses the “tax_input” parameter and expect you to give it an array where each key is a custom taxonomy, and each value is an array of tags in that taxonomy.
Update (8 Aug 2011): Rarst has left a comment saying that “tax_input” will only work if initiated by a logged in user with the permission to edit that taxonomy. If this affects you, read on below for a different solution that will work anywhere!
For example, let’s say we have a taxonomy called “custom_tax”, and the two tags we want to add to the post are “Dolor” and “Ipsum”.
1 2 3 4 5 6 7 8 9 10 | $new_post = array( 'post_title' => 'Our New Post', 'post_content' => 'Our filler text for the post.', 'post_status' => 'publish', 'tax_input' => array( 'custom_tax' => array('Dolor', 'Ipsum') ) ); $post_id = wp_insert_post($new_post); |
I mentioned that you could also add your tags later on. To do that, use the wp_set_object_terms() function (There are similar functions to this one, specifically for tags etc. but this covers it all for you).
The format is: wp_set_object_terms( post ID , array of tag IDs , taxonomy , append true/false ).
The last value is important. If you set true, then your new tags will be added to any existing ones. If you set false, then any existing tags will be replaced with your new ones.
Here are some examples of adding categories, tags, and a custom taxonomy this way:
1 2 3 | wp_set_object_terms( $post_id, array(27, 28, 29), 'category', true); wp_set_object_terms( $post_id, array('Example', 'Lorem Ipsum'), 'post_tag', true); wp_set_object_terms( $post_id, array('Lorem', 'Sit'), 'custom_tax', true); |
Metadata
If you’ve ever added custom meta data to a post before, you know what to do already.
Let’s say we want to add a custom field with the name “summary” and the value “Quick Summary!”:
1 | add_post_meta( $post_id, 'summary', 'Quick summary!', true); |
That’s quite self-explanatory. The ‘true’ at the end refers to whether this field is unique or not. If we set this to false, and a summary already exists, then another custom field also titled ‘summary’ would be added to the post.
By setting it to true, this won’t happen. WordPress checks for the ‘summary’ field first, and if it exists, it does nothing (You would then need to use update_post_meta() instead).
One special note to make is that if you want to set a page template, you do this with the meta functions as well. WordPress stores the name of the template file to use in a custom field titled ‘_wp_page_template’
1 | update_post_meta( $post_id, '_wp_page_template', 'onecolumn-page.php'); |
Between those functions, you should be able to set up any post data you need. Feel free to post in the comments if you have any questions, or I’d love to hear how you’ve allowed users to add content to a WordPress site!

This is one of the most common questions I get asked; how do you make a floating box with share links?
Today, we’re going to see just how simple it can be. We will:
- Set up the current trinity of search (Facebook, Twitter, +1).
- See how to make sure they share the right URL.
- Align the box to the bottom left of the user’s browser.
- Hide the box if the user’s browser is too small (So it never overlaps our content).
You can see the demo site here (Try resizing your browser smaller).
1 – The URL and Text to Share
The first step involves a little WordPress code. In footer.php in your theme, scroll down to the









































One way to check is to use tools like 

(And yes, I know we’re still using the chicklets here, but they’ll be gone for good in a few more days so just ignore that for now!)