WordPress: How to change the text labels on login and lost password pages

Editing the text labels on the WordPress login form and the lost password form is pretty easy. There’s a handy action called gettext that all WordPress translated text for the entire site goes through (whether it’s being translated or not) that can be hooked into allowing you to change anything you like. Using this we can change any text we like almost anywhere on the site.

Change Username to Email

As a quick example, below is how you’d use the gettext action to replace Username with Email in the form labels. We’re wrapping it in the login_head action to ensure that it is only replaceed on the login page, otherwise it would do it site wide which we probably don’t want.

/**
 * Changes 'Username' to 'Email Address' on wp-admin login form
 * and the forgotten password form
 *
 * @return null
 */
function oz_login_head() {
	function oz_username_label( $translated_text, $text, $domain ) {
		if ( 'Username or E-mail:' === $text || 'Username' === $text ) {
			$translated_text = __( 'Email' , 'oz-theme' );
		}
		return $translated_text;
	}
	add_filter( 'gettext', 'oz_username_label', 20, 3 );
}
add_action( 'login_head', 'oz_login_head' );

Yes, you can also use it to replace the callout text on the lost password page. Questions? comments? Criticism? Post ’em below.

WordPress: How to move the excerpt meta box above the editor

Adding and removing new meta boxes is pretty easy in WordPress thanks to the numerous native functions. The same functions can even be used to change the position of native meta boxes, moving them up, down and into the sidebar as you please. However an often googled request is how to move the excerpt meta box above the content wysiwyg editor. A lot of themes use it to summarise the post content and some even as a subheading in the actual post, so it just seems to make more sense in the page flow having it after the title.

Surprisingly despite the numerous questions asked on Stack Overflow and the WordPress forums almost no one seems to come up with an adequate solution. The most common hack is to output a form using the ‘edit_form_after_title’ action, a method that sort of works but lacks proper WordPress meta box styling and is frankly a bit nasty.

Moving the excerpt meta

If you want to skip the explanation just copy and paste the following code to your theme’s functions.php.

/**
 * Removes the regular excerpt box. We're not getting rid
 * of it, we're just moving it above the wysiwyg editor
 *
 * @return null
 */
function oz_remove_normal_excerpt() {
	remove_meta_box( 'postexcerpt' , 'post' , 'normal' );
}
add_action( 'admin_menu' , 'oz_remove_normal_excerpt' );

/**
 * Add the excerpt meta box back in with a custom screen location
 *
 * @param  string $post_type
 * @return null
 */
function oz_add_excerpt_meta_box( $post_type ) {
	if ( in_array( $post_type, array( 'post', 'page' ) ) ) {
		add_meta_box(
			'oz_postexcerpt',
			__( 'Excerpt', 'thetab-theme' ),
			'post_excerpt_meta_box',
			$post_type,
			'after_title',
			'high'
		);
	}
}
add_action( 'add_meta_boxes', 'oz_add_excerpt_meta_box' );

/**
 * You can't actually add meta boxes after the title by default in WP so
 * we're being cheeky. We've registered our own meta box position
 * `after_title` onto which we've regiestered our new meta boxes and
 * are now calling them in the `edit_form_after_title` hook which is run
 * after the post tile box is displayed.
 *
 * @return null
 */
function oz_run_after_title_meta_boxes() {
	global $post, $wp_meta_boxes;
	# Output the `below_title` meta boxes:
	do_meta_boxes( get_current_screen(), 'after_title', $post );
}
add_action( 'edit_form_after_title', 'oz_run_after_title_meta_boxes' );

 

How does it work

Essentially it’s three steps, remove the existing postexcerpt, re-register postexcerpt with our own custom meta location, output the meta box at the new location.

1. De-register the existing postexcerpt.

/**
 * Removes the regular excerpt box. We're not getting rid
 * of it, we're just moving it above the wysiwyg editor
 *
 * @return null
 */
function oz_remove_normal_excerpt() {
	remove_meta_box( 'postexcerpt' , 'post' , 'normal' );
}
add_action( 'admin_menu' , 'oz_remove_normal_excerpt' );

 

2. Add the postexcerpt box back in but using a custom meta box location, (not normal, advanced or side)

/**
 * Add the excerpt meta box back in with a custom screen location
 *
 * @param  string $post_type
 * @return null
 */
function oz_add_excerpt_meta_box( $post_type ) {
	if ( in_array( $post_type, array( 'post', 'page' ) ) ) {
		add_meta_box(
			'oz_postexcerpt',
			__( 'Excerpt', 'thetab-theme' ),
			'post_excerpt_meta_box',
			$post_type,
			'after_title',
			'high'
		);
	}
}
add_action( 'add_meta_boxes', 'oz_add_excerpt_meta_box' );

3. Using the `edit_form_after_title` action run the do_meta_boxes() function calling our custom location, `after_title`.

/**
 * You can't actually add meta boxes after the title by default in WP so
 * we're being cheeky. We've registered our own meta box position
 * `after_title` onto which we've registered our new meta boxes and
 * are now calling them in the `edit_form_after_title` hook which is run
 * after the post tile box is displayed.
 *
 * @return null
 */
function oz_run_after_title_meta_boxes() {
	global $post, $wp_meta_boxes;
	# Output the `below_title` meta boxes:
	do_meta_boxes( get_current_screen(), 'after_title', $post );
}
add_action( 'edit_form_after_title', 'oz_run_after_title_meta_boxes' );

And that’s it! Your excerpt should now be sitting pretty under the title box. Any comments let me know below.

Capistrano: Deploying multiple sites to the same server

I work for a digital agency that tends to deal with multi-market sites a lot. Most of the sites aren’t particularly high traffic and sit quite happily together on a large EC2 instance with nginx. However, a persistent problem for us was deployment. In our day to day work we generally use Capistrano to deploy sites to both staging and production (very few of our projects warrant build servers) and for the most part it works quite happily, except for when we need to deploy the same repository multiple times to a single server.

For example, one of the sites we manage currently exists in 10 markets and is planning on adding more. They are all served from different domains using separate databases but use the same repository and are hosted on the same server, (config files differ of course). Unfortunately, and after a lot of research, we discovered there is no easy way to do this in Capistrano, indeed it’s talked about quite a lot but there doesn’t seem to be an accepted solution,  so we put our thinking caps on and came up with one.

The goal

We decided that in an ideal world we’d like to be able to achieve the following:

  • Deploy all sites in an environment at the same time, (the most important).
  • Deploy a single site in an environment, (we may want to update just one site, no point in re-deploying all of them).

So not only can we deploy all of them but we still retain the ability to re-deploy an individual one should the need arise. Simple!!

The Solution

Firstly create your Capistrano setup as normal. I don’t really want to get into how to do that here but there are loads of guides available on google, this one for example.

Once Capistrano is installed, setup and configured ensure that the config/staging.rb and config/production.rb (or whatever environments you use) files exist. The content isn’t that important (note: it will be ignored) but they do have to be there. Now for each market/site/locale you wish to deploy for create new environment files for with the naming convention “{environment},{identifier}.rb”. So for example we have:

  • staging.rb
  • staging.us.rb
  • staging.fr.rb
  • production.rb
  • production.us.rb
  • production.fr.rb

In each file you set the stage, branch, deploy_to, role, server and anything else specific to that , anything you wish to be globally set can be put in your config/deploy.rb file.

set :stage, :staging

role :web, %w{deploy@xx.xx.xx.xxx}

ask :branch, 'develop'

set :deploy_to, "/var/www/uk.site.com"

server 'xx.xx.xx.xxx', user: 'deploy', roles: %w{web}

Now at the bottom of your config/deploy.rb file add the following task:

namespace :deploy_all do
  task :deploy do
    on roles(:all) do

      files = Dir.glob("config/deploy/#{fetch(:stage)}.*.rb")
      files.each do | file |
        file = file.sub('config/deploy/', '').sub('.rb', '')
        info "Deploying #{file} to #{:stage}"
        system("cap #{file} deploy")
      end

    end
  end
end

task :deploy_all => 'deploy_all:deploy'

This globs the deploy directory looking for config files depending on the environment variable passed, it then executes Capistrano for each file found. You can now deploy every site with a single command:

cap staging deploy_all

Or to deploy an individual site do:

cap staging.us deploy

Things to improve

This is were we’ve got to so far though we admit it isn’t perfect. Given time we’d quite like to add the following improvements:

  1. A slightly better way of managing the Capistrano settings so they’re a bit DRY-er not repeated in each config file, but I haven’t dug into it any further.
  2. This method is also annoyingly synchronous. I’m fairly sure that with a bit of time and Ruby knowledge the each loop could be re-written to make asynchronous calls, though this may convolute the terminal output somewhat.

Create an NPM lock file for your packages with shrinkwrap

The other day I published a post on how the `–save-dev` flag in NPM is often used incorrectly. Hopefully some people will read it and somewhere a DevOps engineer will be able to lead a happy and peaceful life.

Alas though, it’s not quite all roses in DevOps NPM world. You see there’s still a problem with versioning. As with any good package manager, in NPM you can specify a version or range of versions that is acceptable to be installed for a package. For example:

“gulp-compass”: “~1.2.3” – will install the highest 1.2.x version available

“grunt-ruby-sass”: “^1.1.1” – will install the latest version 1.x.x version available.

Which is all fine and dandy. However,  as we know things change between versions, both major and minor, a lesson that was painfully reenforced yesterday. Developer A had added “gulp-“ruby-sass” at the start of the project and configured it as normal. Several days later Developer B jumped on the project and, ran `gulp` and the world imploded. 40 frustrating minutes later it turns out that gulp-ruby-sass had changed their input syntax and Developer B was running a slightly newer version.

Enter NPM Shrinkwrap

Any modern PHP developer uses Composer and will be able to tell you that when run it creates a composer.lock file. This lock file saves the exact version of each package and the packages’ packages installed so that whenever `composer install` is run in the future the exact same version of each package is installed. Of course if `composer update` is run the packages are resolved from the composer.json file and any new package versions (within the specified version range) are installed, in much the same way as NPM, and a new lock file is automatically created.

It turns out that NPM has the exact same functionality, they just keep very quiet about it and don’t do it automatically like composer or bundler (a Ruby Gem package manager). It’s called shrinkwrap. Once you’ve installed your NPM packages you can run `npm shrinkwrap` and it will create an npm-shrinkwrap.json file. Then in the future whenever `npm install` is run it will resolve the exact package versions (and your packages’ package versions etc etc) found in the shrinkwrap file, meaning no more discrepancies in versions used between developers. Just like composer, if you need to update just run `npm update && npm shrinkwrap`.

Bear in mind that shrinkwrap is intended for production use so won’t save devDependencies by default, if you want it to you can pass the `–dev` flag to it.

 

tldr: You can lock down your exact npm package versions by using ‘npm’shrinkwrap’

Stop using NPM wrong –save-dev

You see it all the time. There you are trying to install an innocent NPM plugin when BHAM, it happens, a DevOps engineer dies…

Ok so maybe I’m exaggerating a tiny bit but ‘gets very cross‘ is certainly accurate. What I’m talking about is the ‘–save-dev’ flag when adding a new NPM package to a project. For some reason this has now become the de facto standard and it’s, (mostly), wrong.

NPM install Ruby sass

The popular gulp-ruby-sass using –save-dev

NPM Install Grunt Compass

grunt-contrib-compass also recomending –save-dev

So what’s the problem with –save-dev?

Well nothing in theory. Like almost all other package managers NPM can distinguish between packages that should be installed in a production environment and packages that should be installed in a development environment. The obvious reason for this is that you’re probably not going to need all your javascript testing frameworks etc in production. However, unless you’re still FTPing CSS files up to the production server (bad), the chances are you’re also going to want gulp-ruby-sass or whatever else is in your default grunt task on the production server as well.

“But I run `git pull && npm install` on my server and it works fine, what are you on about?” you ask bewildered. Well, firstly congratulations for not using FTP, however what you should be doing is ‘npm install –production’. By default npm installs all packages including the dev ones, however, if you pass the ‘–production’ flag it will only install the production packages.

You can see where the problems start to come in. We now run nearly all of our deployments with Capistrano which we have setup to pass the ‘–production’ flag in by default. If a developer has installed a package with ‘–save-dev’ that needs to be run on the server the entire deployment fails, (gracefully, of course) and some poor DevOps engineer, (sometimes me), has to spend hours trying to work out why the default grunt task isn’t completing only to realise that it’s because the NPM package it’s trying to use isn’t on the server.

So next time you’re adding an NPM package take a moment and decide if it’s going to be required in production or not. If it is, don’t use ‘–save-dev’.

tldr: Don’t use ‘–save-dev’ to add NPM packages unless you’re absolutely sure that they’re not required in a production enviroment

Useful WordPress DataBase statements – Part 1 – Tags

If you happened to glance at my previous post about the media_sideload_image function you’ll know that recently I had to manually import a load of content into a WordPress install. A lot of this was done with manually MySQL queries as oppose to using the built in WordPress functions for performance reasons. Once we’d done the actual data importing we wanted to tidy up the database and the content a bit, below are a few MySql queries we ran that you may find useful at some point.

n.b.Backup your database before running any sort of queries

Reset WP Posts & Tags count

After we’d imported all our posts, tags, and linked them up we noticed that the count for the amount of posts belong to a tag was out. This was because we’d imported them using custom functions. The query below will update your tag posts count.

UPDATE wp_term_taxonomy
SET count = (
  SELECT COUNT(term_taxonomy_id)
  FROM wp_term_relationships
  WHERE wp_term_relationships.term_taxonomy_id = wp_term_taxonomy.term_taxonomy_id
  GROUP BY term_taxonomy_id
);

Tags without Posts

We also found we had a lot of superfluous tags that weren’t attached to any posts that had been created by the content managers. It would be much cleaner if these were removed.

View tags without posts

SELECT * FROM wp_terms wt
INNER JOIN wp_term_taxonomy wtt ON wt.term_id=wtt.term_id
  WHERE wtt.taxonomy='post_tag'
  AND ( SELECT COUNT(term_taxonomy_id)
    FROM wp_term_relationships
    WHERE wp_term_relationships.term_taxonomy_id = wtt.term_taxonomy_id
    GROUP BY wtt.term_taxonomy_id ) IS NULL;

Delete tags without posts

DELETE FROM wp_terms wt
INNER JOIN wp_term_taxonomy wtt ON wt.term_id=wtt.term_id
  WHERE wtt.taxonomy='post_tag'
  AND ( SELECT COUNT(term_taxonomy_id)
    FROM wp_term_relationships
    WHERE wp_term_relationships.term_taxonomy_id = wtt.term_taxonomy_id
    GROUP BY wtt.term_taxonomy_id ) IS NULL;

Deleting & limiting WordPress post revisions

Post revisions in WordPress are great. They give you a huge amount of control over your posts & pages and allow you or your authors to edit at will without having to worry about taking backups before each update. Made a mistake? Decided you don’t like the new post? Need to recover something from last year? Simply role back to a previous version of that post and your data is all there waiting.

However, post revisions aren’t perfect. For a start they hugely increase the size of your database. Every time you make an adjustment a revision is made, effectively another copy of your post, and that has to be stored somewhere and that somewhere is in your database. Not a problem if you’re running a small blog but larger sites and prolific bloggers will soon start to feel the pinch.

Perhaps more important than the space aspect is the performance aspect. Post revisions are stored in the same universal table (`wp_posts` by default) as your posts, pages and attachments. This means it can get very large very quickly and the bigger a table the slower it is to work with.

Of course the obvious solution to all this would be to store post revisions in a separate table but that isn’t likely to happen any time soon. In the mean time here are a few adjustments you can make.

1. Limit the number of revisions held

Simply add this to your config.php file, somewhere near the bottom, to limit the number of post revisions WordPress holds for each article. You can set it to any number to limit it to that amount, personally I feel 5 is a decent number without being ridiculous:

/* Limits the amount of post revisions for each article to 6 */
define( 'WP_POST_REVISIONS', 6 );

Set it to FALSE to disable revisions altogether, (doesn’t delete existing revisions):

/* Disables posts revisions */
define( 'WP_POST_REVISIONS', FALSE );

Or set it to TRUE to enable infinite revisions which is the default WordPress value:

/* Infinite revisions */
define( 'WP_POST_REVISIONS', TRUE );

2. Clean up old revisions in your database (Manually)

This will remove all post revisions from your site, sort of like a spring clean. Simply run it in your PHPMyAdmin console.


DELETE a,b,c FROM wp_posts a LEFT JOIN wp_term_relationships b ON (a.ID = b.object_id) LEFT JOIN wp_postmeta c ON (a.ID = c.post_id) WHERE a.post_type = 'revision'

WARNING: This will delete ALL post revisions in your database. If there are any you need to keep DON’T run it.

3. Clean up old revisions in your database (Automatically)

Don’t fancy running SQL statments directly? No problem. simply download and install the very popular WP-Optimize plugin and let that clean up your database for you!

You can read more about post revisions at the WordPress official documentation.

Changing the WordPress default empty trash time

Trash in WordPress is great. If you accidentally delete a post or page instead of being permanently deleted it is simply moved to the trash folder where it can be recovered if necessary. By default the trash folder auto empties everywhere 30 days but this can be changed in the wp-config.php file.

define( 'EMPTY_TRASH_DAYS', 15 );

Or, if you wish to disable auto empty all together just set it to 0

define( 'EMPTY_TRASH_DAYS', 0 );

A little extra something I didn’t know about, you can still enable the trash option for media items. This was officially deprecated in WordPress 2.9 but by adding this to your config.php file you can re-enable it:

define( 'MEDIA_TRASH', TRUE );

The only slight snag is that images moved to the trash can still be accessed on your site through their URL, probably why it was deprecated in the first place. So if you do delete something make sure it’s not linked to on your site.

WARNING: I don’t advocate use of MEDIA_TRASH unless you know what you’re doing. It has been deprecated meaning it may be removed at any time in a future update.

PHP: Validate & Check Dates

More times than I can count I’ve had to validate dates in PHP, not only check if it is in the required format but also to make sure the date actually exists. Until recently there has not been a good, native way of doing this, yes checkdate() has been around for a while but it’s particularly fussy with it’s input, what we needed was a mixture of date() where we can specify the input format and checkdate() to validate it. Enter the DateTime class. Technically available from PHP >= 5.2.0 it isn’t really worth using until >=5.3.0 due to bugs and missing features, (DateTime::createFromFormat or DateTime::getLastErrors in particular).

The traditional approach ( PHP < 5.3.0 )

If you are still running an older version of PHP or are developing for WordPress, (WordPress’s minimum requirements are still 5.2.4 at the time of writing this), below is a very handy PHP date validator function I wrote. Simply pass the date you wish to check as the first value and the format it should be in as the second.

	/**
	 * Validates a string as a date in the inputted format.
	 * Version 1.0
	 *
	 * @param type $date The date to check.
	 * @param type $format The format the date should be in. Defaults to YYYY-MM-DD
	 * @return boolean
	 */
	function validate_date( $date = null, $format = 'YYYY-MM-DD' ){

		// Return FALSE if $date empty
		if( ! $date )
			return FALSE;

		// $date is trimed
		$date = trim( $date );

		// If $format empty return to default, else UPPERCASE and trim.
		$format = empty( $format ) ? 'YYYY-MM-DD' : strtoupper( $format );

		// $format is 10 char in length and contains all the required chars
		if( strlen( $format ) != 10 ||
		    strpos( $format, 'YYYY' ) === FALSE ||
		    strpos( $format, 'MM' ) === FALSE ||
		    strpos( $format, 'DD' ) === FALSE
		)
			return FALSE;

		// Get $format serparator
		$date_seperator = str_replace( array( 'Y', 'M', 'D' ) , '', $format );
		$date_seperator = $date_seperator[0];

		// Explode $format into parts
		$format_parts = explode( $date_seperator, $format );

		// Explode our date into parts
		$date_parts = explode( $date_seperator, $date );

		// Count $date_parts; Quicker than doing it inline
		$date_parts_count = count( $date_parts );

		// Cycle through $date_parts, compare the length to the equivilent $format_parts length, set to var
		for( $i = 0; $i &lt; $date_parts_count; $i++ ){

			// Check each datepart is a valid in
			if( ! filter_var( ( int ) $date_parts[ $i ], FILTER_VALIDATE_INT ) )
				return FALSE;

			// Compare $date_part length to equivilent $format_part length
			if( strlen( $date_parts[ $i ] ) != strlen( $format_parts[ $i ] )  )
				return FALSE;

			// Set our variables so we check it's a valid date later
			if( $format_parts[ $i ][0] == 'Y' ){

				$year = $date_parts[ $i ];

			} elseif( $format_parts[ $i ][0] == 'M' ){

				$month = $date_parts[ $i ];

			} elseif( $format_parts[ $i ][0] == 'D' ){

				$day = $date_parts[ $i ];

			}

		}

		// Finally, check it's a valid date
		if( checkdate( $month, $day, $year ) ){

			return TRUE;

		} else {

			return FALSE;

		}

	}

The modern approach ( PHP >= 5.3.0)

If you know you’re going to be using a more modern version of PHP you can take advantage of the very cool and now native DateTime class. Among it’s many cool new features the best is DateTime::createFromFormat() which allows you to re-format an inputted date. Yes it’s very similar to using date() & strtotime() together BUT this has all sorts of useful additions, the most important of which is error checking.

Christos Pontikis has done a great post on the best way to use the class and how to work around it’s current limitations. A copy of his final recommended solution is below. It basically involves explicitly checking the DateTime::getLastErrors() function to see if the date was valid or not.


/**
 * Check if a string is a valid date(time)
 *
 * DateTime::createFromFormat requires PHP >= 5.3
 *
 * @param string $str_dt
 * @param string $str_dateformat
 * @param string $str_timezone (If timezone is invalid, php will throw an exception)
 * @return bool
 */
function check_date( $str_dt, $str_dateformat, $str_timezone ) {

$date = DateTime::createFromFormat( $str_dateformat, $str_dt, new DateTimeZone( $str_timezone ) );
return $date && DateTime::getLastErrors()['warning_count'] == 0 && DateTime::getLastErrors()['error_count'] == 0;

}

N.B. There are still some errors with the DateTime class. Check out the User comments on the DateTime::createFromFormat for a more in depth discussion.

The best set of test data for WordPress

Whenever a new theme gets built for WordPress one of the most important but often over looked aspects is testing. Apart from the standard stuff like embedding images, page headings, tags etc there is also the more obscure stuff that even the most experience developer can forget about. Twitter embeds for example, or incorrectly sized images or sticky posts, all of which your theme should be able to handle without breaking  as they’re supported natively.

But how do I test my theme?

Enter wptest.io, possibly the most exhaustive set of test data out there currently. Created by Michael Novotny it contains everything from ridiculously large menus, in-line styling such as lists and headings, missing images, stupidly long post titles, galleries, Twitter embeds, in short basically anything somebody may at some point consider putting in a post or on their site. Just import the test data using the standard importer then go through your nice shiny theme and cry at how much is broken. I’d absolutely recommend it for anyone building or modifying a theme, just setup a demo site, import the test data and you’re away!!

Visit the main site here, check out the demo here, the github repo here and Michael Novotny’s twitter here