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

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 );

jQuery UI tabs reset CSS

The other day I had need for a lightweight JavaScript tabs implementation. The site was already setup with jQuery so the natural choice was, of course, to load jQuery UI and use tabs. After a quick setup, the problem I ran into was that I needed the jQuery UI theme for the tabs layout, but didn’t want all the associated styling.

How to reset it

After fairly quick google I came across this wonderful blog post that had already done it. Just add it to your CSS after including the jQuery UI theme (I used Lightness), and it’ll reset it to plain tabs which you can then style yourself.

/*
* Reset the jQuery Tabs if you're using a Theme
*/

/* Resets the contain background and outer borders */
#button_generation .ui-widget-content { border: none; background: none; }
#button_generation .ui-widget { font-family: inherit; font-size: inherit; }

/* Resets all border radius */
#button_generation .ui-corner-all,
#button_generation .ui-corner-top,
#button_generation .ui-corner-bottom,
#button_generation .ui-corner-left,
#button_generation .ui-corner-right,
#button_generation .ui-corner-br,
#button_generation .ui-corner-bl,
#button_generation .ui-corner-tr,
#button_generation .ui-corner-tl { -webkit-border-radius: 0px; -moz-border-radius: 0px; border-radius: 0px; }

/* Resets widget header tabs */
#button_generation .ui-widget-header { border: none; background: none; font-weight: normal; color: #000000; }
#button_generation .ui-widget-header li { border: 0px; padding: 0px; margin: 0px; }
#button_generation .ui-widget-header li a,
#button_generation .ui-widget-header li a:link,
#button_generation .ui-widget-header li a:visited,
#button_generation .ui-widget-header li a,
#button_generation .ui-widget-header li a:link,
#button_generation .ui-widget-header li a:visited { border: none; background: none; font-weight: normal; color: #000000;, margin: 0px; padding: 0px; }

/* Resets the panel */
#button_generation .ui-tabs-panel { padding: 0px; }

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.