Ask Mark Anything

People ask me a lot of questions. About WordPress and web development for sure, but also about other topics. I’ve decided to try a little experiment: a public way to ask me questions. Zach Holman from GitHub had the idea to use a GitHub issue tracker for this very purpose, and I think it looks like a splendid idea.

Benefits:

  • Allows for more in-depth discussions than Twitter (but you can still talk to me on Twitter for quick questions).
  • Is public (as opposed to e-mail).
  • Forces me to deal with questions.

Now, note that this doesn’t mean I want you to treat me like your personal Google-searcher or WordPress code grepper! But if you think there is a WordPress (or other) topic that I am uniquely qualified to address, just ask.

Don’t use template_redirect to load an alternative template file

template_redirect is a popular WordPress hook, for good reason. When it runs, WordPress has made its main query. All objects have been instantiated, but no output has been sent to the browser. It is your last stop to hook in and redirect the user somewhere else, and the best place to do so if you need full knowledge of the queried objects. But what it is not good for is loading an alternative template.

I see code like this a lot:

add_action( 'template_redirect', 'my_callback' );

function my_callback() {
  if ( some_condition() ) {
    include( SOME_PATH . '/some-custom-file.php' );
    exit();
  }
}

The problem with this code is that anything hooked in to template_redirect after this code isn’t going to run! This can break sites and lead to very odd bugs. If you want to load an alternative template, there’s a filter hook for that: template_include.

add_filter( 'template_include', 'my_callback' );

function my_callback( $original_template ) {
  if ( some_condition() ) {
    return SOME_PATH . '/some-custom-file.php';
  } else {
    return $original_template;
  }
}

Same effect, but doesn’t interfere with other plugin or theme code! This distinction should be easy to remember:

  • template_redirect is for redirects.
  • template_include is for includes.

Six Apart Suspends Movable Type Open Source Project

Six Apart announced that they are suspending the free and open source version of Movable Type. Here’s what I had to say about them revealing the free and open source version of Movable Type, back in 2007.

Note that this also allows Six Apart at any time in the future to say “As of today, we are no longer releasing a GPL version of Movable Type.” And that would require that someone fork the code in order to proceed with development. WordPress can’t easily do that, as it is not owned by a single legal entity.

What a GPL’d Movable Type means for WordPress

When I wrote that, I honestly didn’t think it was going to happen. I was just saying that it was an option that was open to them. But here they’ve gone and done it. What a bizarre saga this has turned out to be. “Life is funny”, remarks Anil Dash, former Six Apart Chief Evangelist.

Will this start a conversation about copyright assignment on open source projects, or is this a non-event? If a project has a strong open source development community, an attempt to close it down should result in a fork of the project. As it happens, Movable Type Open Source was already forked. Several prominent Movable Type people created Open Melody back in 2009. Don’t bother clicking that link. As of this writing the site is non-operational. The Open Melody Twitter account just recorded its first activity in over two years. A retweet of this:

I guess we’ll find out!

Fragment Caching in WordPress

Fragment caching is useful for caching HTML snippets that are expensive to generate and exist in multiple places on your site. It’s like full page HTML caching, but more granular, and it speeds up dynamic views.

I’ve been using this fragment caching class for a few years now. I optimized it around ease of implementation. I wanted, as much as possible, to be able to identify a slow HTML-outputting block of code, and just wrap this code around it without having to refactor anything about the code inside.

Implementation is pretty easy, and you can reference the comment at the start of the code for that. The only thing to consider is that any variables that alter the output need to be build into the key. It should also be noted that this code assumes you have a persistent object cache backend.

WordPress 3.6: shortcode_atts_{$shortcode} filter

Since WordPress 3.6 is in beta, I thought I’d use this nearly-abandoned blog (hey, been busy working on WordPress!) to talk about some of the cool stuff for developers. For the first installment, check out the new shortcode_atts_{$shortcode} filter. The shortcode_atts() function now accepts a third parameter — the name of the shortcode — which enables the running of this filter. All of the core shortcodes now pass this parameter.

This filter passes three things:

  1. $out — the output array of shortcode attributes
  2. $pairs — the array of accepted parameters and their defaults.
  3. $atts — the input array of shortcode attributes

Let’s look at what we can do with this. One thing is that you can dynamically set or override shortcode values. You an also define new ones, and transpose them into accepted ones. Let’s look at the “gallery” shortcode for example. What if there was a gallery of images that you would reuse. Instead of picking the images each time, you could have a plugin that gives that set of attachment IDs a shortcut name. Then you could do [gallery foo=my-gallery-name], which the plugin would convert to a list of IDs. Or, you could enforce a certain number of gallery columns on the fly. Let someone set it theme-wide, without having them go back and change their shortcodes.

What other uses can you think of?

Now, if you’re a plugin or theme author who provides their own shortcodes, you should immediately start providing this third parameter to your shortcode_atts() calls (since it is an extra parameter, you can do this without a WordPress version check). Maybe it’ll reduce the number of times people need to fork your code just to add an option to your shortcode!

WP Help 1.0

My WP Help plugin just got a huge update. Version 1.0 is really worth checking out. I’m quite proud of it.

WP Help

WP Help is a plugin for creating documentation for display within the WordPress admin. Many WordPress installs are customized, and it’s really helpful to have a centralized resource for documenting those features. You can create documents about creating content, editing content, moderating comments, or whatever you want! If you have clients who can’t seem to remember how to do X, you should install WP Help and document it for them. WP Help is powered by WordPress Custom Post Types, so you create content using the full WordPress editor.

Document Syncing

Oh yeah. This is the feature you’ve all been waiting for. If you have a standard set of help documents you want to use on multiple sites, this lets you do that. Create the documents, grab the (secret) sync URL for that site, and then plug that URL in to other sites. Those other sites will automatically pull down those documents, and keep them up-to-date (even handling new documents, deleted documents, renamed documents, and re-parented documents). Any internal links in the original document will be rewritten to be local to the destination WP Help install. So go ahead and use the WP internal linking functionality on your source site and know that those links will work on all the destination sites!

Menu Placement

The menu item for the help documents can now be placed in one of four locations:

  1. As a Dashboard submenu
  2. Top level, above the Dashboard
  3. Top level, below the Dashboard
  4. Top level, at the bottom

You get a live preview of this (yeah, I know, super fancy).

Menu Name

The menu name (and page title) can be changed. Just doubleclick the page title, edit it, and hit return. Boom. Again, this has a live preview.

Topics List Name

Likewise the topics list header can be renamed. Doubleclick, edit, return. Live preview.

Edit Links & Management Links

If you can edit help documents, you’ll get an edit link. When editing, there is a handy “Manage” link to jump you back to the documents management interface. Navigating in general has been improved quite a bit.

Dashboard Widget

There is now a simple dashboard widget, listing all of your help documents.

Better Default Access

A lot of you said that you have Contributor-level users who need documentation just like authors do. So now they can view documentation by default (there’s a hook if you want to change the capability required to view help documents).

Lots of Little Tweaks

There are numerous little tweaks to improve your experience. Check out out!

Roadmap

Things I’m considering:

  • Restricting individual documents to users with a certain level of access
  • Multiple sync sources

Any other ideas?

“The writer should be the one to click it”

The beauty of Web publishing, and the specific beauty of TechCrunch, is that the only barrier between bloggers’ overflowing brains and readers’ racing hearts is a publish button. And the writer should be the one to click it.

[...]

Over the course of our conversation, Tsotsis told me she viewed the act of writing the post as a sort of meta-comment on The New York Times’ antiquated editorial structure: “Look what I can do with my dumb little WordPress blog. You guys can’t do this. Watch,” she remembered thinking.

Andrew Beaujon @ Poynter

Whether you love or hate Alexia Tsotsis’ writing, one thing is certainly true: she’s no one’s monkey. And while a debate about the wisdom of making “Fuckers” the first word of a blog post would undoubtedly be entertaining, that’s not what I want to talk about. Tsotsis and Beaujon make two very interesting points that directly relate to WordPress and its effect on publishing.

WordPress is absolutely a tool that can (and given the state of the tools they were previously using, should) be used by Ye Olde Media Guarde. We’ve even made some steps toward supporting a basic editorial workflow with the Contributor role (which can submit posts for review, but not publish them). But using WordPress in that way is like using a computer to print a letter and then fax it to your recipient. It feels like an artificial restriction. We’ve empowered individuals to publish what they want to publish, when they want to publish. Reinstating the print media workflows of the last century very much feels like a step backwards. Writers got a taste of true publishing freedom. Readers got a taste of what it’s like to actually have a connection and conversation with a writer instead of just being delivered their words. We can’t pretend that didn’t happen.

One of WordPress’ user experience breaks from its competitors was its “Publish” button. Other software had a “Save” button and then a drop-down status selector. So you’d select “Published” from the status drop-down, and then hit “Save”. With WordPress, your post is always one quick click away from being shared with the entire world. It’s more raw, more immediate, and more emotionally satisfying. WordPress also instituted “Edit” links on the front end, which gave the same sense of immediacy to the updating process. As much as the old media has embraced blogging as part of their publishing strategy, they’ve not fully integrated its responsive and update-able nature. On the whole, old media “blog posts” feel like a place where statements of questionable truth go to die and never be corrected, clarified, or amended.

Web publishing is much, much more than print publishing with the web as a distribution network. As long as the old media treats the web as a pipe instead of an opportunity to modernize the way they publish, they’ll continue to be called “the old media”.

BRB. Leaving a voicemail with my editor so she can get this post published in tomorrow’s issue.

How WordPress Handles Dashes and Hyphens

WordPress has always valued typography. Properly “curled” quotes, fancy dashes — like this — and more.

I want to look specifically at dashes, and talk about how WordPress handles the conversion of hyphens to dashes.

First, let’s talk about the three most common forms of horizontal strokes that exist. The first is the hyphen-minus. This is the key that is probably to the right of your “0” key on your keyboard. It is often called a “minus” or a “hyphen”. It is neither of these, and yet it is both. It is a special character that is somewhere in-between a minus sign and a hyphen. As such, it can serve either purpose. I’m just going to call that a “hyphen” for the rest of this post, just to save time.

An em-dash (—) is a wide dash — the width of the letter “m” being its guiding length. Em-dashes signify a thought break, rather like parenthesis, but with a stronger implied break.

An en-dash (–) is slightly shorter — the width of the letter “n” being its guiding length. En-dash use is probably one of the least known skills in everyday punctuation. The en-dash is used for:

  • Ranges of number values: (2–4 teaspoons, from 1:00–2:30pm, ages 7–10)
  • Relationships and connections: (a JFK–Atlanta flight, Bose–Einstein condensate, the Jackson–Murray fight, the Macy–Jaquith wedding)
  • Attributive compounds: (pre–Vietnam War weapons, the ex–Vice President non–New York style pizza)

Here’s how WordPress replaces hyphens with dashes:

Foo {3 hyphens, spaced} Bar → Foo — Bar (em-dash)
Foo{3 hyphens, no space}Bar → Foo—Bar (em-dash)
Foo {2 hyphens, spaced} Bar → Foo — Bar (em-dash)

Foo{2 hyphens, no space}Bar → Foo–Bar (en-dash)
Foo {1 hyphen, spaced} Bar → Foo – Bar (en-dash)

The last replacement seems misguided, and I wish it weren’t made (or that it were an em-dash instead).

So, three hyphens always gets you an em-dash. Two hyphens gets you an em-dash if spaced apart from surrounding words. Else, you get an en-dash. If you want more control, then I suggest you do as I do, and actually start typing the correct dashes (WordPress won’t mess with them). On OS X, en-dashes are typed with Opt-{hyphen}, and em-dashes are typed with Opt-Shift-{hyphen}. In Windows, en-dashes are typed with Alt + 0150, and em-dashes are typed with Alt + 0151.

You can also type out the HTML entities in WordPress’ HTML mode: — and –.

WordPress Skeleton

At my “Scaling, Servers, and Deploys — Oh My!” talk (slides) at WordCamp San Francisco 2011, I talked a bit about my ideal WordPress repo setup. In the spirit of sharing, I’ve now made that skeleton setup into a GitHub repo.

What you get is a WordPress repo starter kit. WordPress is in a subdirectory (/wp/), content is in a custom directory (/content/), and uploads are mapped to /shared/content/uploads/, which is a Git-ignored location. Re-symlink as appropriate, or alter your deploy script to do the symlinking on the fly.

You get a nice clean wp-config.php with a few of my tips and tricks already implemented (like local-config.php support for local development). .htaccess is ready to go with WordPress rewrite rules for anyone running on Apache.

I don’t really expect people to use it exactly the way I have it set up (though feel free!). What’s more likely is that people will fork it, and then make it their own. For instance, you may want to add mu-plugins drop-ins that you frequently use. Have fun!

How I built “Have Baby. Need Stuff!”

Have Baby. Need Stuff! is a baby gear site that my wife and I just launched. I thought I’d share how I built it.

WordPress Core

WordPress is a Git submodule, with the content directory moved to the /content/ directory. This makes my Git repo smaller, as WordPress isn’t actually in it.

Underscores

For a theme base, I started with the Underscores starter theme by the theme team at Automattic. Underscores is not a theme itself… it’s a starting point for building your own theme.

Bootstrap

Next, I integrated Bootstrap, by Twitter, to handle the CSS base and the grid system. Bootstrap is a really powerful framework, and version 2.0 has great responsive design support, which allowed me to create a single design that scales up to big screens or down to tablet or phone screen sizes. Try resizing it in your browser to see the responsiveness in action!

The CSS for the site is authored using LESS, which plays well with Bootstrap. I’m compiling/minifying/concatenating the CSS and JS using CodeKit, an amazing Mac OS X app that makes development a breeze.

Typekit

For web fonts, it’s hard to beat Typekit.

Subtle Patterns

I needed some patterns to use on the site, but I was frustrated with the licensing terms on many pattern sites I was finding. And then I found Subtle Patterns. Gorgeous, subtle patterns, liberally licensed. And hey, their site is WordPress powered too!

Posts 2 Posts

The site has the concepts of Departments, Needs, and Products. Each Department has multiple Needs. Each Need has multiple Products. I used Scribu’s phenomenal Posts 2 Posts plugin to handle these relationships.

Here’s the basic Posts 2 Posts connection code:

<?php

function hbns_register_p2p_relationships() {
	if ( !function_exists( 'p2p_register_connection_type' ) )
		return;

	// Connect Departments to Needs
	p2p_register_connection_type( array(
		'name' => 'departments_to_needs',
		'from' => 'department',
		'to' => 'need',
		'sortable' => 'to',
		'admin_box' => 'to',
		'admin_column' => 'any',
		'cardinality' => 'one-to-many',
	) );

	// Connect Needs to Products
	p2p_register_connection_type( array(
		'name' => 'needs_to_products',
		'from' => 'need',
		'to' => 'product',
		'sortable' => 'from',
		'admin_column' => 'any',
		'admin_box' => array(
			'show' => 'any',
			'context' => 'advanced',
		),
		'cardinality' => 'many-to-many',
		'fields' => array(
			'description' => 'Description',
		),
	) );
}

add_action( 'wp_loaded', 'hbns_register_p2p_relationships' );

I created a Custom Post Type for each of Departments, Needs, and Products, and connected them all using Posts 2 Posts. The connection between a Need and a Product also contains description metadata, as seen here:

Since Posts 2 Posts was a required plugin for the site to function, I didn’t want there to be any possibility of accidental deactivation. So I wrote a quick mu-plugins drop-in to “lock” certain plugins on.

<?php
class HBNS_Always_Active_Plugins {
	static $instance;
	private $always_active_plugins;

	function __construct() {
		$this->always_active_plugins = array(
			'batcache/batcache.php',
			'posts-to-posts/posts-to-posts.php',
			'login-logo/login-logo.php',
			'manual-control/manual-control.php',
		);
		foreach ( $this->always_active_plugins as $p ) {
			add_filter( 'plugin_action_links_' . plugin_basename( $p ), array( $this, 'remove_deactivation_link' ) );
		}
		add_filter( 'option_active_plugins', array( $this, 'active_plugins' ) );
	}

	function remove_deactivation_link( $actions ) {
		unset( $actions['deactivate'] );
		return $actions;
	}

	function active_plugins( $plugins ) {
		foreach ( $this->always_active_plugins as $p ) {
			if ( !array_search( $p, $plugins ) )
				$plugins[] = $p;
		}
		return $plugins;
	}
}

new HBNS_Always_Active_Plugins;

Custom Post Types

I’m using the Products post type in a slightly odd way. You don’t ever go to a product URL. You instead go the URL for the Need that the Product fulfills, and that page lists all of the connected Products. As such, I wanted to make it so that URLs for products pointed to their Need, and I wanted to add an admin bar Edit link for the primary product on its Need page.


<?php
/*
Plugin Name: Post Links
Version: 0.1
Author: Mark Jaquith
Author URI: http://coveredwebservices.com/
*/

// Convenience methods
if(!class_exists('CWS_Plugin_v2')){class CWS_Plugin_v2{function hook($h){$p=10;$m=$this->sanitize_method($h);$b=func_get_args();unset($b[0]);foreach((array)$b as $a){if(is_int($a))$p=$a;else $m=$a;}return add_action($h,array($this,$m),$p,999);}private function sanitize_method($m){return str_replace(array('.','-'),array('_DOT_','_DASH_'),$m);}}}

// The plugin
class CWS_HBNS_Post_Links_Plugin extends CWS_Plugin_v2 {
	public static $instance;

	public function __construct() {
		self::$instance = $this;
		$this->hook( 'plugins_loaded' );
	}

	public function plugins_loaded() {
		$this->hook( 'post_type_link' );
		$this->hook( 'add_admin_bar_menus' );
	}

	public function add_admin_bar_menus() {
		$this->hook( 'admin_bar_menu', 81 );
	}

	public function admin_bar_menu( $bar ) {
		if ( is_single() && 'need' == get_queried_object()->post_type ) {
			$primary_product = new WP_Query( array(
				'connected_type' => 'needs_to_products',
				'connected_items' => get_queried_object(),
			) );
			if ( $primary_product->have_posts() ) {
				$bar->add_menu( array(
					'id' => 'edit-primary-product',
					'title' => 'Edit Primary Product',
					'href' => get_edit_post_link( $primary_product->posts[0] ),
				) );
			}
		}
	}

	public function post_type_link( $link, $post ) {
		switch ( $post->post_type ) {
			case 'product' :
				$need = new WP_Query( array(
					'connected_type' => 'needs_to_products',
					'connected_items' => $post,
				) );
				if ( $need->have_posts() )
					return get_permalink( $need->posts[0] );
				break;
		}
		return $link;
	}
}

new CWS_HBNS_Post_Links_Plugin;

For entering data about Products, I made a custom Meta Box that provided a simple interface for entering the Amazon.com link, the approximate price, and then a freeform textarea for key/value pairs and miscellaneous bullet points.

Misc

Because I’m using a Git-backed and Capistrano-deployed repo, I don’t want any local file editing. So I dropped this code in:


<?php

define( 'DISALLOW_FILE_EDIT', true );

function hbns_disable_plugin_deletion( $actions ) {
	unset( $actions['delete'] );
	return $actions;
}

add_action( 'plugin_action_links', 'hbns_disable_plugin_deletion' );

I was playing a lot with different Product thumbnail sizes, so Viper007Bond’s Regenerate Thumbnails plugin was invaluable, for going back and reprocessing the images I’d uploaded.

And of course, no WordPress developer should make a site without Debug Bar and Debug Bar Console.

Nginx and PHP-FPM

My server runs Nginx and PHP-FPM, in lieu of Apache and mod_php. My normal setup is to use Batcache with an APC backend to do HTML output caching, but I also have an Nginx “microcache” that caches anonymous page views for a short amount of time (5 seconds). But with this site, I wanted to cache more aggressively. Because there are no comments, the site’s content remains static unless we change it. So I cranked my microcache up to 10 minutes (I guess it’s not a microcache anymore!). But I wanted a way to purge the cache if a Product or Post was updated, without having to wait up to 10 minutes. So I modified the Nginx config to recognize a special header that would force a dynamic page load, effectively updating the cache.

Here’s the relevant part of the Nginx config:

	location ~ \.php$ {
		# Set some proxy cache stuff
		fastcgi_cache microcache_fpm;
		fastcgi_cache_key $scheme$host$request_method$request_uri;
		fastcgi_cache_valid 200 304 10m;
		fastcgi_cache_use_stale updating;
		fastcgi_max_temp_file_size 1M;

		set $no_cache_set  0;
		set $no_cache_get  0;

		if ( $http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
			set $no_cache_set 1;
			set $no_cache_get 1;
		}

		# If a request comes in with a X-Nginx-Cache-Purge: 1 header, do not grab from cache
		# But note that we will still store to cache
		# We use this to proactively update items in the cache!
		if ( $http_x_nginx_cache_purge ) {
			set $no_cache_get 1;
		}

		# For cached requests, tell client to hang on to them for 5 minutes
		if ( $no_cache_set = 0 ) {
		        expires 5m;
		}

		# fastcgi_no_cache means "Do not store this proxy response in the cache"
		fastcgi_no_cache $no_cache_set;
		# fastcgi_cache_bypass means "Do not look in the cache for this request"
		fastcgi_cache_bypass $no_cache_get;

		include        /etc/nginx/fastcgi_params;
		fastcgi_index  index.php;
		try_files      $uri =404;

		fastcgi_pass phpfpm;
	}

Now I just needed to have WordPress ping those URLs with that header to refresh them when something changed. Here’s the “meat” of that code:

<?php
	public function transition_post_status( $new, $old, $post ) {
		if ( 'publish' !== $old && 'publish' !== $new )
			return;
		$post = get_post( $post );
		$url = get_permalink( $post );

		// Purge this URL
		$this->purge( $url );

		// Purge the front page
		$this->purge( home_url( '/' ) );

		// If a Product changes, flush its Need and that Need's Department
		if ( 'product' === $post->post_type ) {
			// Flush the connected need
			$need = new WP_Query( array(
				'connected_type' => 'needs_to_products',
				'connected_items' => $post,
			) );
			if ( $need->have_posts() ) {
				$this->purge( get_permalink( $need->posts[0] ) );
				// Now this need's connected Department
				$department = new WP_Query( array(
					'connected_type' => 'departments_to_needs',
					'connected_items' => $need->posts[0],
				) );
				if ( $department->have_posts() )
					$this->purge( get_permalink( $department->posts[0] ) );
			}
		// If a Post changes, flush the main Blog page
		} elseif ( 'post' === $post->post_type ) {
			$this->purge( home_url( '/blog/' ) );
		}
	}

	private function purge( $url ) {
		wp_remote_get( $url, array( 'timeout' => 0.01, 'blocking' => false, 'headers' => array( 'X-Nginx-Cache-Purge' => '1' ) ) );
	}

Boom. Now I get the benefit of long cache times, but with the ability to have updates go live quickly when I need to. The upshot here is that while I have Batcache installed, it’s not really going to get a lot of use, as the outer Nginx caching layer should handle everything. This doesn’t just mean that the site scales (Apache Bench has it handling many thousands of requests a second with ease), but that the site is really, really fast to browse. Your experience will vary according to network and geography, of course. But for me, I’m getting 34ms HTML delivery times for pages in the Nginx cache.

Questions?

So that’s how I did it. Let me know if you have any questions!

Act now to stop Internet censorship legislation in the United States

Right now, the United States Congress is holding hearings on legislation that will have disastrous effects on free speech and the Internet. This video gives a good overview:

One of the reasons that I help make WordPress is because of my deeply held conviction that free speech is the most powerful and beneficial tool humans have ever had at their disposal. The Internet is a grand experiment that not only makes communications fast — it embraces freedom by design. This legislation is a low level attack against the system of trust upon which the Internet is based. It’s an attack on freedom of speech and on economic freedom. It’s corporate cynicism of the worst kind.

Act now, and be sure that your legislators know you oppose this legislation.

Translating WordPress Plugins and Themes: Don’t Get Clever

When you use the WordPress translation functions to make your plugin or theme translatable, you pass in a text domain as a second parameter, like so:

<?php _e( 'Some Text String', 'my-plugin-name' ); ?>

This text domain is just a unique string (usually your plugin’s WordPress.org repository slug). Well, many plugin developers see code like this:

<?php _e( 'Another Text String', 'my-plugin-name' ); ?>
<?php _e( 'Yet Another Text String', 'my-plugin-name' ); ?>
<?php _e( 'Gosh, So Many Text Strings!', 'my-plugin-name' ); ?>

And they think to themselves “hm, I sure am typing the 'my-plugin-name' string a lot. I’ll apply the DRY (Don’t Repeat Yourself) principle and throw that string into a variable or a constant!”

Stop! You’re being too clever! That won’t work!*

See, PHP isn’t the only thing that needs to parse out your translatable strings. GNU gettext also needs to parse out the strings in order to provide your blank translation file to your translators. GNU gettext is not a PHP parser. It can’t read variables or constants. It only reads strings. So your text domain strings needs to stay hardcoded as actual quoted strings. 'my-plugin-name'.

Happy coding!

* Well, it won’t break your plugin, but it could make it harder to be used with automated translation tools. And trust me, you don’t want to be manually managing your translation files… we have a better solution coming.

That’s a lot of shortcodes

I did a scan of the WordPress plugin directory today, and found the following shortcodes in use. The list is non-exhaustive, as some shortcodes are specified with variables. This is just the list of basic quoted string shortcodes that I found. There was some discussion about programmatically extracting these from plugins and publishing a directory, so prospective plugin authors know whether the shortcode they want to use might conflict with another plugin. Or, even better, they can offer compatibility between similar plugins.

Continue reading

Speaking at WordCamp SF 2011

My sessions for WordCamp San Francisco 2011 have been confirmed. This post is a quick teaser. Please let me know if you have any questions on what will be covered, or if you have any suggestions:

Scaling, Servers, and Deploys — Oh My!

Alternate title: First Thing, Second Thing, and Oxford Commas — Em Dash! :-)

If you manage professional WordPress-powered sites, this is going to be an invaluable presentation for you. If you’re a cowboy coder, this presentation is a mandatory WordPress-ordered intervention. (And if you don’t know what cowboy coding is, you probably do it, and don’t even know you need help.)

I’ve worked with a lot of WordPress installations for professional sites in my client work. They inevitably fall short in some way. What is usually the case is that the people in charge of running the installation feel that instead of them having control of the environment, they are merely present in the environment. Deployments that were meant to be scalable are often somewhat less so, in practice. Tell me if this conversation sounds familiar:

“What happens if we outgrow one server?”
“We’ll just add another one!”

That’s easy to say as a hypothetical. But what if you actually had to do it? What if a couple hundred thousand unexpected page views started coming your way and your boss/client tells you “Go ahead and add that second server we’ve been talking about… how many minutes is that going to take?” Did you think “no sweat,” or did your heart start racing just imagining the scenario?

I’m going to disclose the secret sauce. I’ll let you in on everything I’ve learned in the last seven years about how to code and architect WordPress-powered sites, how to scale up a single server, how to manage multiple servers, and how to deploy code in a way that is both responsive and prudent.

Topics will include:

  • Apache
  • nginx
  • Memcached
  • MySQL
  • APC
  • NFS
  • rsync
  • Git
  • Puppet
  • Capistrano

And more. At the end you’ll feel confident that you can run professional, fast, scalable WordPress installations that will make your job easier and your clients or boss happy.

Security Showdown

Instead of my battle-worn and (even to me) rather droll talk on WordPress security, I’m going to be doing an interactive panel with Brad Williams and Jon Cave. We’ll do live security reviews of some plugins that have been submitted ahead of time. This way, you can see WordPress security practices in action! There will be prizes and everything. Should be a bunch of fun.

WordPress local dev tips: DB & plugins

Running a WordPress site on your local machine is a great way to do development. I’ve taken advantage of this to do development while on flights (and yes, I realize that in about 5 years it’s going to seem positively quaint that there used to be flights without Internet access).

Today, I’d like to tackle two common issues when running a WordPress site locally:

  1. Handling differing database connection details
  2. Handling plugins that can’t or shouldn’t run on a localhost

My assumptions:

  • You have your site in a Git repository
  • You have a working LAMP/MAMP/WAMP/whatever setup.
  • You already know how to do a mysqldump and import that dump to your local machine

Database connection details

Your database user and password are (or should) be different on your localhost than they are on your production environment. One way to handle this is to have wp-config.php not be in version control and have everyone make their own. Boo. Stop taking things out of version control. Another solution I’ve seen is that people modify the wp-config.php and just try to be really careful not to commit their local changes. Again, boo.

Here’s how to do it. Open up your wp-config.php file.

if ( file_exists( dirname( __FILE__ ) . '/local-config.php' ) ) {
  include( dirname( __FILE__ ) . '/local-config.php' );
  define( 'WP_LOCAL_DEV', true ); // We'll talk about this later
} else {
  define( 'DB_NAME',     'production_db'       );
  define( 'DB_USER',     'production_user'     );
  define( 'DB_PASSWORD', 'production_password' );
  define( 'DB_HOST',     'production_db_host'  );
}

Ignore the WP_LOCAL_DEV define… I’ll explain that later.

Now open up your .gitignore file.

/dir-that-contains-wp-config/local-config.php

There. Now you can just create a local-config.php file and put your DB_* defines in there. And thanks to the .gitignore addition, you won’t have to worry about accidentally committing your local config.

But what if you want to override other defines locally? Well, just modify them in wp-config.php like so:

if ( !defined( 'SCRIPT_DEBUG' ) )
  define( 'SCRIPT_DEBUG', false );

And make sure they’re after the local-config.php block. Now you can override these defines as well.

Plugins that are production-only

I’m a big fan of VaultPress from Automattic. But this is not something you want to run on your localhost. Other backup plugins probably fall into this category of “production-only.”

The bad way to do this is to remember to disable this plugin after you import a DB snapshot to your localhost. Boo. Remember WP_LOCAL_DEV that we set earlier? Let’s use it.

I’ve written a quick “must-use” plugin to handle conditional plugin disabling. Get it here, and put it in the mu-plugins directory (create it in your WP content directory if you don’t already have one).

The part you modify is at the bottom:

new CWS_Disable_Plugins_When_Local_Dev( array( 'vaultpress.php' ) );

I’ve jump-started you by putting vaultpress.php in there. Add plugin filenames to that array as necessary. Don’t forget that most plugins are in a subdirectory, so they’ll be in the form plugin-name/plugin-name.php.

Now those plugins won’t be active when doing local dev (based on the presence of local-config.php).