Revelations about filter_input

WordPress has the unfortunate legacy situation of having to force input variables to be “magic quoted”, something which has been deprecated in PHP for some time. So when you are working with data passed from the user, you have to run those input variables ($_GET, $_POST, $_REQUEST) through stripslashes(), which is really annoying. At least I\’m annoyed by it.

However, I just made a discovery. As of 5.2, PHP comes with an extension for Data Filtering, including a function filter_input function for fetching input variables and passing them through validation, sanitization, and other filters.

Well, it turns out that when you access input variables through filter_input, it bypasses wp_magic_quotes entirely, and so you’ll get raw un-backslashed data back! So instead of accessing $_GET['x'], you call filter_input( INPUT_GET, 'x' ).

Additionally, using filter_input() has the benefit of not having to add isset() and !empty() checks everywhere that you’re interacting with input variables, so as to avoid the plague of PHP notices about undefined array indexes. (Oh, hello most WordPress plugins in existence!)

And the icing on the cake for filter_input is that you get validation and sanitization.

However, there’s one big caveat about all this. The WordPress API functions like update_post_meta() and friends actually expect the input to be magic-quoted. So if you do this:

update_post_meta( get_the_ID(), 'x', 'I love \o/ WordPress!' );

If you grab that postmeta out:

$x = get_post_meta( get_the_ID(), 'x', true );

Then $x will end up being:

I love o/ WordPress!

Nooooooooooooooooooooooooooo.

For more info, see #18322: The Road to Magic Quotes Sanity.

List of Functions Removed from WordPress 3.6 RC2 since Beta 3 (and Added too)

On an ongoing client project at X-Team, since the site was to launch in the summer we took the initiative to build a new site on WordPress 3.6 trunk “knowing” that it would be released before then, and so we could utilize the new Post Formats UI and functions that went along with it. Alas. Initially we thought we could still retain the functionality that hooked into the new Post Formats UI, since the announcement was that “Posts Formats is exiting core, will live as a plugin”. So we expected that the WordPress 3.6 would be released with a companion plugin that restored the Post Formats UI removed from core in Beta 4. On June 10, I tweeted:

But all I got in response were crickets. And a month and a half later at WordCamp San Francisco, it became clear that the Post Formats UI was not going anywhere and would be completely rethought, including radical (exciting) ideas from Matt where a post would not be a format, but rather could contain multiple formats:

So it became clear that with the impending release of WordPress 3.6 and the abandonment of the Post Formats UI, that we’d have to immediately update our codebase from Beta 3 to RC2, and add our own plugin to support the Post Formats UI features that we relied on. But what API functions were removed in Beta 4 form Beta 3? I didn’t find a quick answer from the Google, but came up with a quick way to grep the codebase for the list.

So given a WordPress 3.6 Beta 3 install in a given Git repo, I did:

$ wp core update --version=3.6-rc2

Then to find the list of PHP functions removed since Beta 3, all I had to do was:

$ git diff -w -- $(git status -uno | grep '\.php$' | cut -c4-) | egrep '^-function' | sort

This produces this list of PHP Functions in WordPress 3.6 Beta 3 removed as of RC2:

-function _local_storage_notice() {
-function _post_formats_fix_empty_title( $data, $postarr ) {
-function _post_formats_generate_title( $content, $post_format = '' ) {
-function _post_formats_title( $title, $post_id = 0 ) {
-function _wp_post_revision_meta_keys() {
-function _wp_preview_meta_filter( $value, $object_id, $meta_key, $single ) {
-function add_chat_detection_format( $name, $newline_regex, $delimiter_regex ) {
-function attachment_url_to_postid( $url ) {
-function confirm_another_blog_signup($domain, $path, $blog_title, $user_name, \
                                      $user_email = '', $meta = '') {
-function edit_form_image_editor() {
-function esc_sql( $sql ) {
-function get_attached_audio( $post_id = 0 ) {
-function get_attached_image_srcs( $post_id = 0 ) {
-function get_attached_images( $post_id = 0 ) {
-function get_attached_media( $type, $post_id = 0 ) {
-function get_attached_video( $post_id = 0 ) {
-function get_content_audio( &$content, $html = true, $remove = false ) {
-function get_content_chat( &$content, $remove = false ) {
-function get_content_galleries( &$content, $html = true, $remove = false, $limit = 0 ) {
-function get_content_image( &$content, $html = true, $remove = false ) {
-function get_content_images( &$content, $html = true, $remove = false, $limit = 0 ) {
-function get_content_media( $type, &$content, $html = true, $remove = false, $limit = 0 ) {
-function get_content_quote( &$content, $remove = false, $replace = '' ) {
-function get_content_url( &$content, $remove = false ) {
-function get_content_video( &$content, $html = true, $remove = false ) {
-function get_embedded_audio( &$content, $remove = false ) {
-function get_embedded_media( $type, &$content, $remove = false, $limit = 0 ) {
-function get_embedded_video( &$content, $remove = false ) {
-function get_paged_content( $content = '', $paged = 0 ) {
-function get_post_format_content_class( $format ) {
-function get_post_format_meta( $post_id = 0 ) {
-function get_post_galleries( $post_id = 0, $html = true ) {
-function get_post_galleries_images( $post_id = 0 ) {
-function get_post_gallery( $post_id = 0, $html = true ) {
-function get_post_gallery_images( $post_id = 0 ) {
-function get_the_password_form() {
-function get_the_post_format_chat( $id = 0 ) {
-function get_the_post_format_image( $attached_size = 'full', &$post = null ) {
-function get_the_post_format_media( $type, &$post = null, $limit = 0 ) {
-function get_the_post_format_quote( &$post = null ) {
-function get_the_post_format_url( $id = 0 ) {
-function get_the_remaining_content( $more_link_text = null, $strip_teaser = false ) {
-function img_html_to_post_id( $html, &$matched_html = null ) {
-function paginate_content( $content ) {
-function post_format_content_class( $format ) {
-function post_formats_compat( $content, $id = 0 ) {
-function post_submit_meta_box($post) {
-function the_post_format_audio() {
-function the_post_format_chat() {
-function the_post_format_gallery() {
-function the_post_format_image( $attached_size = 'full' ) {
-function the_post_format_quote() {
-function the_post_format_url() {
-function the_post_format_video() {
-function the_remaining_content( $more_link_text = null, $strip_teaser = false ) {
-function wp_ajax_revisions_data() {
-function wp_ajax_show_post_format_ui() {
-function wp_text_diff_with_count( $left_string, $right_string, $args = null ) {

So now I can just look at our themes and plugins to see if we use any of these functions, and then create a Beta 3 compatibility plugin to implement them.


And out of curiousity, here is a list of the PHP functions added to WordPress 3.6 since Beta 3:

$ (git diff -w -- $(git status -uno | grep '\.php$' | cut -c4-) | egrep '^\+function' | cut -c2-; cat $(git status -uall --porcelain | grep '^??' | grep '\.php$' | cut -c4-) | egrep '^function') | sort

function _canonical_charset( $charset ) {
function _local_storage_notice() {
function confirm_another_blog_signup( $domain, $path, $blog_title, $user_name, \
                                      $user_email = '', $meta = array() ) {
function edit_form_image_editor( $post ) {
function esc_sql( $data ) {
function get_attached_media( $type, $post = 0 ) {
function get_media_embedded_in_content( $content, $types = null ) {
function get_post_galleries( $post, $html = true ) {
function get_post_galleries_images( $post = 0 ) {
function get_post_gallery( $post = 0, $html = true ) {
function get_post_gallery_images( $post = 0 ) {
function get_the_password_form( $post = 0 ) {
function get_url_in_content( $content ) {
function post_submit_meta_box($post, $args = array() ) {
function twentyeleven_admin_enqueue_scripts( $hook_suffix ) {
function twentyeleven_admin_header_image() { ?>
function twentyeleven_admin_header_style() {
function twentyeleven_auto_excerpt_more( $more ) {
function twentyeleven_body_classes( $classes ) {
function twentyeleven_color_schemes() {
function twentyeleven_comment( $comment, $args, $depth ) {
function twentyeleven_content_nav( $html_id ) {
function twentyeleven_continue_reading_link() {
function twentyeleven_custom_excerpt_more( $output ) {
function twentyeleven_customize_preview_js() {
function twentyeleven_customize_register( $wp_customize ) {
function twentyeleven_enqueue_color_scheme() {
function twentyeleven_excerpt_length( $length ) {
function twentyeleven_footer_sidebar_class() {
function twentyeleven_get_default_link_color( $color_scheme = null ) {
function twentyeleven_get_default_theme_options() {
function twentyeleven_get_first_url() {
function twentyeleven_get_gallery_images() {
function twentyeleven_get_theme_options() {
function twentyeleven_header_style() {
function twentyeleven_layout_classes( $existing_classes ) {
function twentyeleven_layouts() {
function twentyeleven_option_page_capability( $capability ) {
function twentyeleven_page_menu_args( $args ) {
function twentyeleven_posted_on() {
function twentyeleven_print_link_color_style() {
function twentyeleven_settings_field_color_scheme() {
function twentyeleven_settings_field_layout() {
function twentyeleven_settings_field_link_color() {
function twentyeleven_setup() {
function twentyeleven_theme_options_add_page() {
function twentyeleven_theme_options_help() {
function twentyeleven_theme_options_init() {
function twentyeleven_theme_options_render_page() {
function twentyeleven_theme_options_validate( $input ) {
function twentyeleven_url_grabber() {
function twentyeleven_widgets_init() {
function twentyten_admin_header_style() {
function twentyten_auto_excerpt_more( $more ) {
function twentyten_comment( $comment, $args, $depth ) {
function twentyten_continue_reading_link() {
function twentyten_custom_excerpt_more( $output ) {
function twentyten_excerpt_length( $length ) {
function twentyten_get_gallery_images() {
function twentyten_page_menu_args( $args ) {
function twentyten_posted_in() {
function twentyten_posted_on() {
function twentyten_remove_gallery_css( $css ) {
function twentyten_remove_recent_comments_style() {
function twentyten_setup() {
function twentyten_widgets_init() {
function twentythirteen_admin_header_image() {
function twentythirteen_admin_header_style() {
function twentythirteen_body_class( $classes ) {
function twentythirteen_content_width() {
function twentythirteen_custom_header_fonts() {
function twentythirteen_custom_header_setup() {
function twentythirteen_customize() {
function twentythirteen_customize_preview_js() {
function twentythirteen_customize_register( $wp_customize ) {
function twentythirteen_entry_date( $echo = true ) {
function twentythirteen_entry_meta() {
function twentythirteen_fonts_url() {
function twentythirteen_get_link_url() {
function twentythirteen_header_style() {
function twentythirteen_paging_nav() {
function twentythirteen_post_nav() {
function twentythirteen_preview() {
function twentythirteen_scripts_styles() {
function twentythirteen_setup() {
function twentythirteen_switch_theme() {
function twentythirteen_the_attached_image() {
function twentythirteen_upgrade_notice() {
function twentythirteen_widgets_init() {
function twentythirteen_wp_title( $title, $sep ) {
function twentytwelve_admin_header_image() {
function twentytwelve_admin_header_style() {
function twentytwelve_body_class( $classes ) {
function twentytwelve_comment( $comment, $args, $depth ) {
function twentytwelve_content_nav( $html_id ) {
function twentytwelve_content_width() {
function twentytwelve_custom_header_fonts() {
function twentytwelve_custom_header_setup() {
function twentytwelve_customize_preview_js() {
function twentytwelve_customize_register( $wp_customize ) {
function twentytwelve_entry_meta() {
function twentytwelve_get_font_url() {
function twentytwelve_header_style() {
function twentytwelve_mce_css( $mce_css ) {
function twentytwelve_page_menu_args( $args ) {
function twentytwelve_scripts_styles() {
function twentytwelve_setup() {
function twentytwelve_widgets_init() {
function twentytwelve_wp_title( $title, $sep ) {
function wp_ajax_get_revision_diffs() {
function wp_get_revision_ui_diff( $post, $compare_from, $compare_to ) {
function wp_http_validate_url( $url ) {
function wp_prepare_revisions_for_js( $post, $selected_revision_id, $from = null ) {
function wp_refresh_post_nonces( $response, $data, $screen_id ) {
function wp_style_add_data( $handle, $key, $value ) {

Do not change the default timezone from UTC in WordPress

I discovered something a bit surprising about WordPress related to timezones: WordPress explicitly sets and expects the default timezone to be UTC (in settings.php) and the date/time functions sometimes rely on the fact that the default timezone is UTC. For instance if you do date_default_timezone_set(get_option('timezone_string')) and then later try to get a GMT timestamp from get_post_time() or get_post_modified_time(), it will fail to give you the right date.

So from calling $timestamp = get_post_time('U', true /*GMT*/), if we step up into core:

function get_post_time( $d = 'U', $gmt = false, $post = null, $translate = false ) { // returns timestamp
	$post = get_post($post);

	if ( $gmt )
		$time = $post->post_date_gmt;
	else
		$time = $post->post_date;

	$time = mysql2date($d, $time, $translate);
	return apply_filters('get_post_time', $time, $d, $gmt);
}

You can see that it is going to pass post_date_gmt into mysql2date() but that this date/time (e.g. 2013-02-19 18:22:42) does not include with it any timezone information (a sad fact about the WordPress posts schema). So then mysql2date() is passed this timezone-free date/time:

function mysql2date( $format, $date, $translate = true ) {
	if ( empty( $date ) )
		return false;

	if ( 'G' == $format )
		return strtotime( $date . ' +0000' );

	$i = strtotime( $date );

	if ( 'U' == $format )
		return $i;

	if ( $translate )
		return date_i18n( $format, $i );
	else
		return date( $format, $i );
}

And the strtotime() is going to interpret that timezone-agnostic date as the time in the current timezone! So even though the original date/time is coming from $post->post_date_gmt, it is getting passed into strtotime() with a default timezone not being UTC/GMT, and so the end resulting date/time returned is not GMT as originally requested.

If you find your code does this, the quick solution to the problem is to just reset the default timezone to UTC once you’ve finished working in the other timezone:

date_default_timezone_set(get_option('timezone_string'));
// do some stuff
date_default_timezone_set('UTC');

But a more elegant solution would be to move to using DateTime objects and specify the DateTimeZone for each.

Anyway, this is something to be aware of, as I had to pull out a few hairs (and I don’t intend to go bald).

See also: Default timezone hardcoded as UTC? (Stack Exchange)