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.

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.

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

WordPress: Auto adds slashes to $_POST, $_GET, $_REQUEST, $_COOKIE

When I first hard coded a form into a WordPress page and print_r() the results, all the quotes were escaped. Now, being a reasonably savvy developer at times, my first thought was obviously, “Oh the server must have magic quotes turned on”. So I did my due diligence and checked, but oddly it didn’t. After a lot of googling it turns out that the WordPress core automatically adds slashes to all input arrays. “Why, oh god why” you rightly ask. Well the answer is simple, WordPress has been around a long time now and has to run on a variety of different servers all with different settings. To maintain consistency and provide the best possible security they decided to force adding slashes to all input, even if the server had magic_quotes turned off, ensuring that consistency is maintained across the core code.

Why would you ever want magic quotes and what does it do?

Magic Quotes were added to PHP very early on as a one size fits all approach to help beginners write better and more secure code. What it does is automatically add slashes () to single and double quotes in any possible user facing data array, ($_POST, $_GET, $_REQUEST, $_COOKIE). Adding slashes to any data going near a database helps prevent SQL injection and makes database interaction much safer and is generally a good thing. However, PHP automatically adding slashes to data arrays has caused no end of headaches for developers over the years, namely because you often need to manipulate the data before it goes anywhere near a database, if it ever does. Most servers these days have magic quotes disabled, indeed, it’s been depreciated in PHP since 5.3.0 and will be actively removed, 5.4.0. You can read more about magic quotes on the official PHP site here.

Ok, how do I to fix it in WordPress

WordPress realises you might not want slashes automatically added and so provides a nice function called stripslashes_deep that enables you to remove them should you wish. WARNING: As mentioned above, the WordPress core relies on the all code being escaped, (in particular $_REQUEST), so don’t remove it early on in the page execution, only later after all the initial core stuff has loaded and you want to process the data.

Either way, you can use array_map with the native WordPress stripslashes_deep function to remove all the added slashes. Use the code below to do just that.


$_POST = array_map( 'stripslashes_deep', $_POST );
$_GET = array_map( 'stripslashes_deep', $_GET );
$_COOKIE = array_map( 'stripslashes_deep', $_COOKIE );
$_REQUEST = array_map( 'stripslashes_deep', $_REQUEST );

WordPress: SQL_CALC_FOUND_ROWS, why it’s slow and what to do about it

UPDATE: 25 Feb 2017 This has actually become the most popular post on my site over the last few years and recently someone was kind enough to point out that the original code, well mostly working, ignores any params that might be passed to WP_Query. So I went over it again and came up with a much better way of doing it. Check out the new code below.

If you’re running a large WordPress site with hundreds or thousands of posts, comments and tags, there’s a fair chance it’s running slowly. Sometimes this is a hosting issue, other times a poorly coded plugin or theme, but occasionally you’re doing everything right and it’s WordPress that’s letting you down.

What is SQL_CALC_FOUND_ROWS and why’s it bad?

SQL_CALC_FOUND_ROWS is an older MySql function used for returning the total amount of rows found for a database query. You need to know the total amount of rows returned by a query in order to calculate the amount of pages need for pagination. The problem is it’s quite an old function, isn’t terribly well optimised, and can be particularly inefficient and slow your database query right down. A simple google for “SQL_CALC_FOUND_ROWS” will reveal page upon page of complaints about speed and and comparisons between using it or running an secondary query instead using COUNT(*). The general consensus is that running a secondary query is often faster as COUNT(*) doesn’t bother loading the whole table.

Unfortunately there are a few places in WordPress that use SQL_CALC_FOUND_ROWS, pretty much all the big query classes do (WP_Query, WP_User_Query etc etc) and it shows no signs of going away any time soon.

How do I know if SQL_CALC_FOUND_ROWS is being slow

First thing to do is check if it is SQL_CALC_FOUND_ROWS slowing down your site. The easiest way to do this is by viewing all the database queries happening on page load and examining the execution time. Although this sounds complicated it’s actually quite easy to do. There are loads of plugins in the plugin directly that will list the your DB queries for each page load, I personally have used Query Monitor before to great success.

How to fix it, (Without Pagination)

If you’re not using any sort of pagination in your WP_Query you can just tell WordPress not to run the pagination database queries, and hence not SQL_CALC_FOUND_ROWS. All you have to do is pass the little known about no_found_rows variable to WP_Query.

To filter it out of your main posts query, add this to your functions.php file in your theme folder.

if ( ! function_exists( 'wpartisan_set_no_found_rows' ) ) :

	/**
	 * Sets the 'no_found_rows' param to true.
	 *
	 * In the WP_Query class this stops the use of SQL_CALC_FOUND_ROWS in the
	 * MySql query it generates.
	 *
	 * @param  WP_Query $wp_query The WP_Query instance. Passed by reference.
	 * @return void
	 */
	function wpartisan_set_no_found_rows( \WP_Query $wp_query ) {

		if ( $wp_query->is_main_query() ) {

			$wp_query->set( 'no_found_rows', true );

		}
	}
endif;
add_filter( 'pre_get_posts', 'wpartisan_set_no_found_rows', 10, 1 );

To filter it out of a custom query, simply pass it as an option.

$the_query = new WP_Query( array( 'no_found_rows' => TRUE) );

How to I fix it, (With Pagination)

The original code was based on a post by b0b_ on the WordPress support forums. Well it works it ignores any custom params that you may have passed into the main query. Below is a better version that should work no matter what params you add to any WP_Query.

if ( ! function_exists( 'wpartisan_set_no_found_rows' ) ) :

	/**
	 * Sets the 'no_found_rows' param to true.
	 *
	 * In the WP_Query class this stops the use of SQL_CALC_FOUND_ROWS in the
	 * MySql query it generates. It's slow so we're going to replace it with
	 * a COUNT(*) instead.
	 *
	 * @param  WP_Query $wp_query The WP_Query instance. Passed by reference.
	 * @return void
	 */
	function wpartisan_set_no_found_rows( \WP_Query $wp_query ) {
		$wp_query->set( 'no_found_rows', true );
	}
endif;
add_filter( 'pre_get_posts', 'wpartisan_set_no_found_rows', 10, 1 );

if ( ! function_exists( 'wpartisan_set_found_posts' ) ) :

	/**
	 * Workout the pagination values.
	 *
	 * Uses the query parts to run a custom count(*) query against the database
	 * then constructs and sets the pagination results for this wp_query.
	 *
	 * @param array    $clauses  Array of clauses that make up the SQL query.
	 * @param WP_Query $wp_query The WP_Query instance. Passed by reference.
	 * @return array
	 */
	function wpartisan_set_found_posts( $clauses, \WP_Query $wp_query ) {

		// Don't proceed if it's a singular page.
		if ( $wp_query->is_singular()  ) {
			return $clauses;
		}

		global $wpdb;

		// Check if they're set.
		$where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
		$join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
		$distinct = isset( $clauses[ 'distinct' ] ) ? $clauses[ 'distinct' ] : '';

		// Construct and run the query. Set the result as the 'found_posts'
		// param on the main query we want to run.
	 	$wp_query->found_posts = $wpdb->get_var( "SELECT $distinct COUNT(*) FROM {$wpdb->posts} $join WHERE 1=1 $where" );

		// Work out how many posts per page there should be.
		$posts_per_page = ( ! empty( $wp_query->query_vars['posts_per_page'] ) ? absint( $wp_query->query_vars['posts_per_page'] ) : absint( get_option( 'posts_per_page' ) ) );

		// Set the max_num_pages.
		$wp_query->max_num_pages = ceil( $wp_query->found_posts / $posts_per_page );

		// Return the $clauses so the main query can run.
		return $clauses;
	}
endif;
add_filter( 'posts_clauses', 'wpartisan_set_found_posts', 10, 2 );

The first hook just runs a function that sets ‘no_found_rows’ to true in the query to stop SQL_CALC_FOUND_ROWS from being added. The second hooks into a handy filter that runs just before WordPress runs the database query that contains all the query parts in. We reconstruct the query and run it ourselves but with a COUNT(*) and without any limits set. We when work out and set the pagination params on the WP_Query. Simples.

Now to work out how to do the same for WP_User_Query.