Category Archives: Wordpress Development

A Case for WordPress

I recently made the case for using the open-source WordPress CMS instead of a custom hand-rolled CMS to a manager. What follows is the email (with names and companies changed) that was sent. I thought that it might be useful to other people who are trying to convince their organization to take the leap to WordPress, Drupal, or any other open-source CMS. It’s also worth noting that I’m on very friendly terms with my manager, hence the informality and straight-forwardness. As for the Django references, we develop most of our data-driven sites in Python/Django.

Hey [Manager],

As you already know, I completely loathe the [Custom] CMS. My recent and ongoing experiences with the [Client] website have only served to reinforce these feelings. Unbridled hate rarely serves any useful purpose though, so I’ve decided to see what I can do to make the problem better. My way of doing that is by making a case for using WordPress on large, non-trivial, content driven websites. As of right now WordPress has been lasso’d into small-size websites at [Company], but I believe it is capable of far more then that. What follows is a breakdown of all the feedback, questions, and concerns that were raised while I was gathering information. I’ve provided solutions to almost all of the problems, and its worth noting that some of the problems with WordPress are not unique to WordPress. I also want to make it clear that I’m not trying to get rid of the [Custom] CMS, especially since we have a lot of clients with a lot of money invested in it. I’m simply making a case for using WordPress on large sites instead of the [Custom] CMS.

Content Creation / Management

For as long as I can remember, [Other Manager] always raised a red flag with WordPress because it was inferior in content creation to the CMS. In vanilla form this is arguably true, however WordPress has an amazing community of plugin developers surrounding it who have solved all of these problems.

  • Pods – I have a love-hate relationship with pods. I like that they provide nearly infinite flexibility to the content creator, but that comes with the price of nearly infinite complexity. While I disagree with giving the content-creator that much control over how content is displayed, I can see the usefulness of it. To solve that problem, there are many visual composition plugins available for WordPress. My favorite is Visual Composer (http://vc.wpbakery.com/). Extending Visual Composer (akin to adding “module functions”) is as simple as adding a hook in your WordPress widget.
  • Categories / Taxonomies for Media (assets) – WordPress has brilliant support for categories and custom taxonomies out of the box. Beyond that, it also has support for tags. One thing that doesn’t work out of the box is media categories. Luckily there is a large handful of community plugins that easily solve this problem for us.
  • Media – The media library in WordPress is extremely slick, ajax-y (asynchronous uploads, etc), and easily extendable. Multiple thumbnails and image sizes are generated out of the box. Adding new image sizes is easy too.
  • Videos – WordPress supports videos out of the box. Adding Youtube/Hulu/Vimeo videos is as easy as adding one of the numerous plugins. There are even plugins out there that interface with FFMPEG to get you a nice selection of thumbnails to choose from.
  • Form Builder – The new hotness of [Custom] CMS is Form Builder. WordPress has a large amount of awesome form builders available, and most of them are cheap and come with support. Gravity Forms(http://www.gravityforms.com) is my favorite. Although Ninja forms is looking like a valid alternative.
  • User Management – WordPress comes with excellent user management out of the box. It has 4 default roles (Administrator, Editor, Contributor, Subscriber), but no way to change what those roles are capable of. They generally have pretty sane defaults, but sometimes you want to tweak things. User Role Editor(http://wordpress.org/plugins/user-role-editor) provides an easy way to modify group and user permissions. If you need to add new roles, there are plugins for that as well.
  • Content Deployment – We’ve often struggled with how to deploy new content to sites. Generally you don’t want to create the content on live, because it will need to be hidden. There is a straight ballin’ plugin for WordPress called Ramp(http://crowdfavorite.com/wordpress/ramp/) that solves this problem. Seriously, check this out. It will blow your mind.

General Notes

I have a handful of notes that don’t fit well into Content Creation / Management or Development, so here we go.

  • Update Cycle – WordPress has a release cycle (http://en.wikipedia.org/wiki/WordPress#Releases). It has feature releases, bug releases, and HOLY HELL THATS A BIG RELEASE releases. WordPress updates are vetted by an insanely large community of professional developers who catch bugs/regressions and fix them before release.
  • CMS “Upgrades” – Somehow its become normal and accepted at [Company] that a CMS Upgrade costs north of $25,000, takes a month, and almost always ends up braking some part of the site that wasn’t broken before. WordPress upgrades take 10 seconds, don’t break changes, and have deprecation warnings just in case you’re using a method/hook that won’t be supported in a future release. We might make some money off of [Custom] CMS upgrades, but my experience has been that they end up costing more time/effort/good will then they are worth. Having a one-click upgrade option allows us to keep our sites secure, up to date, and gets us back to making things awesome.
  • Community – WordPress.org has 1,998 base themes that can be used and extended via theme inheritance. It also has over 26,000 plugins that extend WordPress’ functionality. This doesn’t account for the numerous premium plugins that are available throughout the internet. Help is always just a Google search away. If that doesn’t work, Stack Overflow most certainly will. Having the backing of a community while you are developing is invaluable. Did I mention the WordPress IRC channel?
  • Documentation – One of the most frustrating things about the [Custom] CMS isn’t the lack of code documentation, but the lack of code usage documentation. WordPress has a ludicrous amount of documentation on pretty much anything you can think of. It stays up to date with best practices and is updated for every release. If the WordPress.org documentation isn’t good enough, the source code is very well documented. If the source doesn’t suit your fancy, there is probably a blogger that wrote about your problem.
  • Hiring – Hiring is hard in this economic climate. Selling a PHP developer to come work for [Company] with [Custom] CMS is even harder. If the person is a WordPress expert, all of that domain knowledge stagnates and becomes useless here. It also makes the on-boarding process take 2-4 months. And even then, they still aren’t all that useful in the [Custom] CMS. Having WordPress as a core tool allows us to hire people who are already experts. The on-boarding becomes a few weeks, and everyone is happy. It also makes it a lot less fatal if we lose a developer, because it’s possible to hire another one that already has all the domain knowledge.
  • Focus – Using WordPress is similar to using Django in that it gets out of your way at lets you focus on what’s important: making awesome stuff. You don’t have to piddle around with random regressions, bugs, or odds and ends. Those are already worked out for you. You get to focus on making custom functionality the best you can. Not having to mess around with bugs and regressions allows you to focus on quality instead of just “getting it done”.
  • Security – WordPress can be a security nightmare. With an install footprint of over 60 million websites (as of 2012), everybody is trying to hack it. Luckily most plugin authors and the WordPress security team are always right on top of it. There is a plugin called WP Security Scan(http://wordpress.org/plugins/wp-security-scan/) that takes care of making sure all the directories have the proper permissions, and in general gives good advice to harden your WordPress installation. Taking 10 minutes a week to update all your WordPress sites is also the best way to prevent security issues.
  • Who? – WordPress.com has a nice list of notable WordPress users (http://en.wordpress.com/notable-users/). The list includes, CNN, NYTimes, Forbes, Reuters, Sony, Jay-Z, Rolling Stones, GM, and others.
  • Cost – We keep upgrading the CMS. It needs it, but it cost lots of money. WordPress and the open-source community keep things up-to-date for us, so the cost is nil.

Development

In the past, a lot of misinformation has been spread about WordPress and how developing with it is awful. A lot of that comes from lack of knowledge, so I thought I’d address a lot of the pain points that people have experienced.

  • Plugin Creation – [Other developer] brought up the point that WordPress plugins are created in a procedural fashion, and are therefore a huge PITA to work with. As it turns out, the correct way to make WordPress plugins is via OOP. This NetTuts tutorial covers most of the basics (http://net.tutsplus.com/tutorials/wordpress/create-wordpress-plugins-with-oop-techniques/).
  • Content Deployment – WordPress hard-codes URLs into the database content. This is obviously a bad idea for many reasons. To rectify, if you move content from one server to another, you just need to execute 3 sql queries as part of your deployment. Usage of Ramp (mentioned above) solves this issue for 99% of use cases.
  • Versioning – WordPress shouldn’t be versioned. Any plugin you install from WordPress.org should not be versioned. If you are making a custom theme, that should get it’s own repository (hey, look at Stash and all it’s repos we can create). If you are making a custom plugin, that should also get it’s own repo. This promotes re-usability and good coding practices. Deployment of theme/plugin updates is as easy as checking out the latest version or git-pulling.
  • Deployment – Initial deployment of a WordPress site is just like moving a staging site to the live server for the [Custom] CMS. After that, deploying code updates to custom plugins and themes is just a git pull/checkout of a tag. This could easily be streamlined with the use of Fabric or Capistrano.
  • Modules / Custom Post Types – The [Custom] CMS has a pretty nice way of creating basic custom content via modules and the scaffolding. WordPress has custom post types, and several different plugins for creating them on the fly. My current favorite is Advanced Custom Fields (http://www.advancedcustomfields.com/)
  • Theme Inheritance – As mentioned before, WordPress has LOTS of themes. Nearly any one of them can be used as a base theme, saving front-enders a ton of time getting base CSS and templates set up.
  • ORM – WordPress has a weak ORM. Hopefully you can use WordPress’ features enough that you can write minimal SQL. On the bright side, you’ll have some nicely optimized queries ;) This is also why you should use the right tool for the job. If you have to interact with the database so much that you need an ORM, you should probably be using a framework.
  • Testing – WordPress core is fully unit and integration tested. The majority of premium plugins and popular plugins are tested as well. Plugins can easily be tested using PHPUnit, and their interfaces can be tested using Behat.
  • Developing as a Team – WIth a sane Git strategy (custom plugins and themes versioned only) developing as a team is a non-issue. In the past, having the whole project under version control caused serious issues when there was more then one developer.
  • Database Migrations – Just as with [Custom] CMS, if you have complex migration that destroys data you’ll need to handle the migration yourself. http://codex.wordpress.org/Creating_Tables_with_Plugins has more info. If the migration isn’t destructive, you can use the wp_delta method to handle it for you.
  • Environment Abstraction – WordPress does a great job of not caring about what version of PHP you’re on. So long as you are PHP >= 5.2.4 and MySQL >= 5.0.0, you’re all set

I know we’ve talked in the past about vetting a Django CMS, but I think that using WordPress is the best choice. We already have people who know it, the front-enders know how to develop for it, the community is massive, and learning it is easy. I look forward to talking with you about this.

Caching WordPress Data with the Transients API

If you’re a plugin or theme developer, there may come a time when you need to execute a long running operation. It doesn’t need to be anything complicated, but something as simple as fetching a Twitter feed can take a significant amount of time. When you come across these types of situations, it’s handy to be able to store the data on your own server and then fetch a new copy of it every X hours. This is called caching, and WordPress convieniently comes packages with an excellent caching API called Transients .
Wordpress Transients API

The Transients API is surprisingly simple to use. In fact, it’s very much like using set_option and get_option except with an expiration time. If you aren’t familiar with caching at all, here’s the general workflow:

  1. If the data exists in the cache and isn’t expired, get it.
  2. If the data doesn’t exist in the cache or is expired, perform the necessary actions to get the data.
  3. Store the data in the cache if it doesn’t already exist or is expired.
  4. Continue from here using data.

When attempting to use the Transients API for caching, there are three functions that you need to be aware of: set_transient, get_transient, and delete_transient.

  • set_transient($identifier, $data, $expiration_in_minutes): This function stores your data into the database. The identifier is a string that uniquely identifies your data. Your data can be any sort of complex object, so long as it is serializable. The expiration is how long your want your data to be valid (ex: 12 hours would be 60*60*12).
  • get_transient($identifier): This retrieves your data. If the data doesn’t exist or the expiration time has passed, false is returned. Otherwise, the same data you stored will be returned.
  • delete_transient($identifier): This will delete your data before it’s expiration time. This is handy if you are storing post-dependent data because you can hook it into the save action so that every time you save a post, your cached data is cleared.

Now that we’ve covered the basics, how about a quick example?

if (false === ( $my_data = get_transient('super_expensive_operation_data') ) ) {
     $my_data = do_stuff();
     set_transient('super_expensive_operation_data', $my_data, 60*60*12);
}
 
echo $my_data;
 
function do_stuff() {
     $x = 0;
     for($i = 0; $i != 999999999; $i++) {
          $x = $x * $i;
     }
     return $x;
}

The example is pretty straight forward. We first check to see if there is a cached copy of the data, if not, we fetch the data from the “do_stuff” function, and store it in the database. Simple, right?

One of the benefits of using the Transients API (aside from speeding your site up) is that plugins like WP Super Cache or WP Total Cache will auto-magically cache your data into memcached if you have it set up. For you, this means an even faster site! If you have any questions about caching techniques or the Transients API, leave a comment and I’d be happy to help.

WordPress Development as a Team

At my day job I’m really the only person that knows how to write WordPress plugins, so when I write one it’s usually sand-boxed on my machine where nobody can touch it. However, in a side endeavor I’m part of we have a team of 3 people developing on one plugin. As I’m the most experienced plugin developer amongst our team, I was tasked with coming up with a development style and plugin architecture that would work for us.

Development Style

Everyone will be running a local copy of WordPress and making their changes to the plugin locally. The plugin itself will be under version control using Git, and developers will push/pull changes from either a self-hosted Git server or Git Hub. Database schema will be tracked in a file called schema.sql. When someone makes a change to the schema, it goes into that file at the bottom with a comment about why the schema changed. We’ll being jQuery as our Javascript framework of choice, and we’ll be writing all of our Javascript in CoffeeScript (see my previous entries).

Plugin Architecture

The more difficult aspect of developing this plugin as a team is the sheer size of the plugin. Realistically this could probably be split into about 6 different plugins by functionality, but we want to keep everything together in one tidy package. To illustrate the architecture, I made a quick drawing.

The first layer of the plugin is essentially a wrapper. It initializes the ORM that we are using to access the database (we are using a separate database for this plugin’s data), and includes the wrapper class. The wrapper class is where developers drop their sub-plugin include file and instantiate it’s main object. For instance, for each sub plugin there will probably be two classes instantiated in the wrapper. One being admin related functionality, and the other being for front-end display functionality. My thinking with this architecture was that we could all work on separate sub-plugins without crossing paths too frequently. This also allows us to separate the different functionality areas of the plugin in a logical manner. The other benefit to architecting the plugin like this is that it will be very easy to port to a different architecture in the future. I’m well aware that WordPress probably isn’t the best tool for the job, but it is the best tool for the team with the deadline that we have.

Thoughts

While thinking about WordPress Plugin Architecture, I cruised the source code of a lot of plugins and it seems that everyone goes about it in a different way. If you’ve ever developed a large-scale plugin with a team, how did you go about doing it? Did you run in to any problems that you didn’t foresee at the beginning of the process?

PHP Alternative Syntax

Did you know that PHP has an alternative syntax structure?  Up until about two years ago, I didn’t either.  It wasn’t until I started poking around in the WordPress core that I saw it.  Intrigued, I popped over to PHP.net and read an entry on it.  In a nutshell, the alternative syntax can go far in making your PHP code much easier to read.  The control structures if, while, for, foreach, and switch can all be expressed in the alternative syntax form.  In general, I prefer to use the alternative syntax when mixing PHP in with HTML, and the standard syntax when writing pure PHP.

If Example

// This....
if($myString == "foo"):
    echo "bar";
else:
    echo "no-foo-bar";
endif;
 
//...is equal to this.
if($myString == "food") {
    echo "bar";
} else {
    echo "no-foo-bar";
}

ForEach Example

$names = array("bob", "tom", "john");
 
//This....
foreach($names as $name):
    echo "Your name is: {$name}";
endforeach;
 
//...is equal to this.
foreach($names as $name) {
    echo "Your name is: {$name}";
}

Resources

If you’re looking for more resources on PHP’s alternative syntax, check out the documentation here, or the WordPress source code for examples.

WordPress Data Validation Functions

As a developer of WordPress plugins or themes, you need to be aware of and use data validation.  What is data validation you ask?  It’s when you make sure that the data you fetched (POST, GET, database call, external source) is the type of data that you expected.  For instance, let’s say you have a user enter a number between 1-10.  They enter the letter ‘A’.  The process of determining whether the input is an integer between 1-10 is data validation.

So what kind of data functionality does WordPress (and PHP in general) offer?  Lots!

  • intval($value) – This will cast any value as an integer.  Particularly useful for casting floating point numbers.
  • absint($value) – Returns the absolute value of a number.  For those of you with no math background, that means it will return a whole number given any floating point number.  (Ex.  absint(3.3) = 3)
  • wp_kses()This function will strip a string of any HTML tags that are not allowed.  It also makes sure that any HTML entities that are in the string are normal.
  • esc_html($string) – This will escape any HTML characters in a string.  This is handy for storing blogs of HTML in a database.
  • esc_js($string) – Escapes any javascript that it is given.  Mostly this means it escapes single and double quotes.
  • urlencode($string) – This function encodes any string you put in to it as a url-safe value.
  • $wpdb->prepare() – This function is used prepare SQL statements for database inserts.  I wrote an article about using the WPDB class with your plugin that you should check out.
  • validate_file(..) - This  is useful to validate that a file exists, and also to help prevent directory traversal attacks.
  • wp_redirect() - This is the safest was to do redirects.  Instead of using header(Location: ..), this will only allow redirects to white listed domains instead domains.
  • balanceTags($string) – If you’re allowing your users to comment on something with html tags, this function will try to make sure that the tags are balanced.
  • is_email($email) – Validates whether an email address is valid or not.

As you can see, WordPress (and PHP) include a nice array of data validation functions.  Make sure you use them as often as possible, because a large number of web based attacks could be prevented if people validated data.

WordPress Dashboard Widget Tutorial

Historically, I’ve never been a huge fan of the WordPress Dashboard.  Not because I don’t think it’s a good idea, but mostly because I don’t use it.  Before I turned this into a WordPress / PHP development blog, I posted infrequently and never saw the point of having a dashboard view.  Now that I have traffic, and hope to get more (*crosses fingers*), I’m spending a lot of time there.  Since I’m spending so much time on the dashboard, I thought I might make a bit more useful with a widget.

Note:  The full (working) version of this Dashboard Widget is available here.

Reddit RSS WordPress Dashboard Widget

Step 1:  Decide What You’re Going To Create

The first step in this tutorial is deciding what to create.  I’m personally a HUGE fan of Reddit, but often enough I’m unable to read it because I’m busy managing this blog.  So, why not bring Reddit to me?  This tutorial will show you every step of how to create a WordPress Dashboard Widget that displays Reddit’s RSS feed.

Step 2:  Write The Display Code

As most readers here know, WordPress is well on it’s way to being a full-fledged CMS.  As with any good CMS, extending any part should be easy.  It’s no different with the WordPress Dashboard.  Using the wp_add_dashboard_widget function, you can hook into the WordPress Dashboard and drop in your own widgets.  But first, we must write the widget itself.

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
function reddit_rss_dashboard_widget_function() {
     $rss = fetch_feed( "http://www.reddit.com/.rss" );
 
     if ( is_wp_error($rss) ) {
          if ( is_admin() || current_user_can('manage_options') ) {
               echo '<p>';
               printf(__('<strong>RSS Error</strong>: %s'), $rss->get_error_message());
               echo '</p>';
          }
     return;
}
 
if ( !$rss->get_item_quantity() ) {
     echo '<p>Apparently, there is nothing happening on Reddit!</p>';
     $rss->__destruct();
     unset($rss);
     return;
}
 
echo "<ul>\n";
 
if ( !isset($items) )
     $items = 10;
 
     foreach ( $rss->get_items(0, $items) as $item ) {
          $publisher = '';
          $site_link = '';
          $link = '';
          $content = '';
          $date = '';
          $link = esc_url( strip_tags( $item->get_link() ) );
 
          $content = $item->get_content();
          $content = wp_html_excerpt($content, 250) . ' ...';
 
         echo "<li><a href='$link'>$link</a> - $content</li>\n";
}
 
echo "</ul>\n";
$rss->__destruct();
unset($rss);
}

Most of the above code is pretty easy to follow.  But here’s  breakdown of what happens if you’re a bit confused.

  1. We use the fetch_feed function to get the RSS feed from the supplied URL.  In this case, it’s Reddit’s RSS feed.
  2. Check to see if there have been any errors (is_wp_error).
  3. Check to see if there was anything on the feed. ($rss->get_item_quantity).
  4. Loop through the fee printing out list items.

Step 3:  Add The Widget To The Dashboard

In this step, you need to call wp_add_dashboard_widget from a function.

1
2
3
function reddit_rss_add_dashboard_widget() {
     wp_add_dashboard_widget('reddit_rss_dashboard_widget', 'Reddit RSS', 'reddit_rss_dashboard_widget_function');
}

The first argument in this function is the unique id that you give your widget.  The second argument is the widget’s name.  And the 3rd argument is the display function (which we just wrote).

Step 4:  Initialize Your Widget

The fourth and final step to creating a WordPress Dashboard Widget is to use the add_action function to add it to the setup.

1
add_action('wp_dashboard_setup', 'reddit_rss_add_dashboard_widget');

Step 5:  Clean Up

Once you have everything written, save it all in a file called reddit_dashboard_widget.php.  From there, zip it up and upload it to your WordPress install as a plugin.  Once activated, you’ll have Reddit’s RSS feed as a Dashboard Widget.  It will show up towards the bottom, so you’ll need to re-order it to your liking.

Final Thoughts

As I brought together this dashboard widget as a proof of concept, I got to thinking about how it could be improved.  One way would be to use more Ajax to fetch the updated feed.  Or if the RSS feed doesn’t update frequently enough, we could scrape the page ever n minutes to get updated content.  Also, at the suggestion of a reader, I’m going to start including full (working) examples with each tutorial.  Enjoy!

Download the WordPress Reddit Dashboard Widget Here.

WordPress Shortcode API Tutorial

One neat feature of WordPress plugins is shortcode.  Shortcode enables a user to put something like “[myplugin]” in their posts or pages and have it display content from their plugin.  Making WordPress shortcode work for you isn’t terribly difficult, but without some help it can be confusing.  In this tutorial, I’ll show you how to use shortcode in your plugins.

Wordpress Shortcode API Tutorial

Shortcode Example

Let’s say you want to print out the current time anywhere in your post where you put “[time]”.  Easy enough!  Open up your theme’s functions.php and enter the following.

function time_func($atts) {
     $thetime = time();
     return date("g:i A",$thetime);
}
add_shortcode('time', 'time_func');

So now, “[time]” will be replaced with the current server time formatted like hour:minute AM/PM.  But what if you aren’t sure what format you want the time to come out in?  Well, you just need to add an attribute.  I’m going to be using PHP’s date() function formatting in this example.

function time_func($atts) {
     extract(shortcode_atts(array(
          'timeFormat' =&gt; 'g:i A'
          ), $atts));
     $thetime = time();
     return date($timeFormat, $thetime);
}
add_shortcode('time', 'time_func');

What the above code does is add the possibility of an attribute to your shortcode.  So you could use it like “[time timeFormat=’d.m.Y’]”, which return today’s date.  In the extract function, you can specify the default value (which I set as hour:minute AM/PM).  You can add as many attributes as you need to your shortcode.

For more information, please see the WordPress Shortcode API.

WordPress 3.1 Features

While the release of WordPress 3.1 is still a few months away, a lot of people have begun to wonder what new features might be added and what bugs may be fixed.  The WordPress developers have finally held a meeting and came up with a plan of what they would like to include in the 3.1 release.

Features

  • Internal Linking  – This is by far the most exciting feature to get in to WordPress 3.1.  What this will allow you to do is easily link to pages within your site.  Instead of having to go to the page, copy the URL, and then paste it into an external link, you’ll be able to click the “Internal Link” button and choose from your posts.
  • Bug Fixes - As always, there will be numerous bug fixes included in this release of WordPress.
  • Post Templates – In many a CMS, you are able to create a bunch of different templates by which you can display your content.  WordPress has started down this path a little bit with custom post types, but if you wanted to display a normal post multiple ways you were sort of out of luck.  In WordPress 3.1, designers and developers will be able to create multiple templates and users will be able to choose how their post is displayed.
  • Theme Admin UI – This upgraded will make the way you browse and administer your WordPress themes much easier.  The current system is a bit clunky, and they hope to do away with it completely and replace it with an improved version.
  • Ajax Admin – It’s no secret that WordPress doesn’t use Ajax much on the back end.  It looks like they developers have finally decided to change this with 3.1.  This update should hopefully smooth out the pagination when browsing your posts in the admin.  It should also provide advanced search and sorting functionality.
  • The Admin Bar - WordPress.com users have a neat feature called the “Admin Bar”.  What this does is connect the back and front end of the site through an easy to use bar that appears at the top of the screen for logged in users.  From the discussions, it looks like this may have to be enabled (similar to featured images), but it is still a welcome feature to most.

Release Schedule

  • October 15, 2010 – Feature freeze.
  • November 15, 2010 – Beta period starts.
  • December 15,2010 – Release.

Wordpress 3.1 Features