Verifying Intent

When a user performs an action which alters the database, let it be submitting a settings form, modifying a user’s profile details, or editing a post’s body, we want to confirm it was actually them who initiated the action. WordPress packages a nice system called nonces (“Numbers used Once”) to help you out.

Because nonces have a set expiration, and can be generated to be unique to a given action, they’re easy for WordPress to verify and difficult for the baddies to fake. Without nonces, your code can be susceptible to [cross-site request forgery](http://en.wikipedia.org/wiki/Cross-site_request_forgery).

Back when nonces were added to WordPress, Mark Jaquith [did a nice write-up](http://markjaquith.wordpress.com/2006/06/02/wordpress-203-nonces/). If the introduction is a bit confusing, you should go read that link and then check back.

### Verifying Intent: Nonces

Building upon our [capability example](checking-capabilities.md), when we generate the action link, we’ll want to use `wp_create_nonce()` to add a nonce to the link:

“`
function wpdd_frontend_delete_link() {

if ( ! current_user_can( ‘edit_others_posts’ ) )
return;

$url = add_query_arg(
array(
‘action’ => ‘wpdd_frontend_delete’,
‘post’ => get_the_ID();
‘nonce’ => wp_create_nonce( ‘wpdd_frontend_delete’ ),
),
home_url(),
);
echo “Delete“;
}
“`

The `wpdd_frontend_delete` argument we pass to the function ensures that the nonce we’re creating is unique to our action. Nonces can also be included in a form with the `wp_nonce_field()` helper function.

Then, when we’re processing a request to delete a post, we check that the nonce is what we expect it to be:

“`
if ( isset($_REQUEST[‘action’] ) && $_REQUEST[‘action’] == ‘wpdd_frontend_delete’ ) {
add_action(‘init’,’wpdd_frontend_delete_post’);
}

function wpdd_frontend_delete_post() {

if ( ! current_user_can( ‘edit_others_posts’ ) )
return;

if ( ! wp_verify_nonce( $_REQUEST[‘nonce’], ‘wpdd_frontend_delete’ ) )
return;

// Get the ID of the post.
$post_id = ( isset( $_REQUEST[‘post’] ) ? get_post( (int) $_REQUEST[‘post’] ) : false;

// No post? Oh well..
if ( empty($post_id) )
return;

// Delete post
wp_trash_post( $post_id );

// Redirect to admin page
$redirect = admin_url(‘edit.php’);
wp_safe_redirect( $redirect );
exit;
}
“`

Nonces keep WordPress secure because they verify the user intended to perform the action. Because nonces have an expiration time, and can be generated to be unique to the form or link, it’s very difficult for the baddies to forge them.

Checking Capabilities

When a user performs an action which alters the database, let it be submitting a settings form, modifying a user’s profile details, or editing post data, or accesses private data, we want to check that they have the *capability* to do so. WordPress makes this easier to do through its system of [Roles and Capabilities](https://codex.wordpress.org/Roles_and_Capabilities).

Before we go to much further, let’s review. A *role* is a collection of capabilities. WordPress creates roles like Administrator, Editor, and Author by default — they correlate very much with which permissions you’d want to assign in a publishing workflow. And a *capability* is pretty much that: a defined permission. In its default roles, WordPress uses capabilities like `manage_options`, `edit_posts`, and `switch_themes`. When a WordPress user is created, they’re assigned a role, and the collection of capabilies associated with the role defines what they can do.

### Capabilities: Verifying Permissions

As you’re writing your safe and secure code, you’ll want to pay keen attention to which roles should be permitted to perform which action. In this example, we have a helpful function which gives editors a link to trash posts from the front end of their site:

“`
function wpdd_frontend_delete_link() {
$url = add_query_arg(
array(
‘action’ => ‘wpdd_frontend_delete’,
‘post’ => get_the_ID();
),
home_url(),
);
echo “Delete“;
}
“`

This function could be used as a template tag within a theme.

Then, we have a function to handle any requests to delete posts:

“`
if ( isset( $_REQUEST[‘action’] ) && $_REQUEST[‘action’] == ‘wpdd_frontend_delete’ ) {
add_action( ‘init’,’wpdd_frontend_delete_post’ );
}

function wpdd_frontend_delete_post() {

// Get the ID of the post.
$post_id = ( isset( $_REQUEST[‘post’] ) ? get_post( (int) $_REQUEST[‘post’] ) : false;

// No post? Oh well..
if ( empty($post_id) )
return;

// Delete post
wp_trash_post( $post_id );

// Redirect to admin page
$redirect = admin_url(‘edit.php’);
wp_safe_redirect( $redirect );
exit;
}
“`

The code above allows any visitor to the site to click on the “Delete” link and trash the post. What’s wrong with that scenario?

Well… we only want editors and above to be able to click on the “Delete” link. If *anyone* can do it, chaos will ensue.

As such, we need to make sure the current user can do something only an editor or above can do before performing either action.

When generating the link, it would look something like this:

“`
function wpdd_frontend_delete_link() {

// current_user_can() checks for a specific capability
// and ‘edit_others_posts’ is associated with the editor role and above
if ( ! current_user_can( ‘edit_others_posts’ ) )
return;

$url = add_query_arg(
array(
‘action’ => ‘wpdd_frontend_delete’,
‘post’ => get_the_ID();
),
home_url(),
);
echo “Delete“;
}
“`

When performing the delete action, it would look something like this:

“`
if ( isset( $_REQUEST[‘action’] ) && $_REQUEST[‘action’] == ‘wpdd_frontend_delete’ ) {
add_action( ‘init’,’wpdd_frontend_delete_post’ );
}

function wpdd_frontend_delete_post() {

if ( ! current_user_can( ‘edit_others_posts’ ) )
return;

// Get the ID of the post.
$post_id = ( isset( $_REQUEST[‘post’] ) ? get_post( (int) $_REQUEST[‘post’] ) : false;

// No post? Oh well..
if ( empty($post_id) )
return;

// Delete post
wp_trash_post( $post_id );

// Redirect to admin page
$redirect = admin_url(‘edit.php’);
wp_safe_redirect( $redirect );
exit;
}
“`

In both generating the link and handling the request, we’re confirming the current user has the `edit_others_posts` capability before proceeding with the action. If they don’t, we don’t do anything.

Securing Input

Each time a user submits data to WordPress, or data is ingested from an external feed, or data generally comes from an external source, you should make sure it’s safe to handle. You want to make sure the data is safe for a variety of reasons; to help prevent XSS if the data is improperly escaped on output, and to ensure your code is executing how you expect are two good reasons. You can make sure this data is safe to use by validating and sanitizing.

If you learn anything from this document, pay attention to you’re handling those $_GET and $_POST variables!

Validating: Checking User Input

Validating is ensuring the data you’re receiving from a user matches what you expect to receive. Let’s take a look at an example.

Say we have an input area in our form like this:

<input type="text" id="my-zipcode" name="my-zipcode" maxlength="5" />

We’re telling the browser to only allow up to five characters of input, but there’s no limitation on what characters they can input. They could enter “11221” or “eval(“. We want to make sure we’re only processing zip codes from the form.

This is where validation plays a role. When processing the form, we’ll write code to check each field for its proper data type. If it’s not of the proper data type, we’ll discard it. For instance, to check “my-zipcode” field, we might do something like this:

$safe_zipcode = intval( $_POST['my-zipcode'] );
if ( ! $safe_zipcode )
    $safe_zipcode = '';

if ( strlen( $safe_zipcode ) > 5 )
    $safe_zipcode = substr( $safe_zipcode, 0, 5 );

update_post_meta( $post->ID, 'my_zipcode', $safe_zipcode );

 

Since the maxlength attribute on our input field is only enforced by the browser, we still need to validate the length of the input on the server. If we don’t, an attacker could cleverly submit a form with a longer value.

The intval() function casts user input as an integer, and defaults to zero if the input was a non-numeric value. We then check to see if the value ended up as zero. If it did, we’ll save an empty value to the database. Otherwise, we’ll save the properly validated zipcode.

This style of validation most closely follows WordPress’ whitelist philosophy: only allow the user to input what you’re expecting.

Sanitizing: Cleaning User Input

Sanitizing is a bit more liberal of an approach to accepting user data. We can fall back to using these methods when there’s a range of acceptable input.

For instance, if we had a form field like this:

<input type="text" id="title" name="title" />

We could sanitize the data with the sanitize_text_field() function:

$title = sanitize_text_field( $_POST['title'] );
update_post_meta( $post->ID, 'title', $title );

Behinds the scenes, sanitize_text_field() does the following:

  • Checks for invalid UTF-8.
  • Converts single characters to entity.
  • Strips all tags.
  • Remove line breaks, tabs and extra white space.
  • Strip octets.

The sanitize_*() class of helper functions are super nice for us, as they ensure we’re ending up with safe data and require minimal effort on our part:

  • sanitize_email()
  • sanitize_file_name()
  • sanitize_html_class()
  • sanitize_key()
  • sanitize_meta()
  • sanitize_mime_type()
  • sanitize_option()
  • sanitize_sql_orderby()
  • sanitize_text_field()
  • sanitize_title()
  • sanitize_title_for_query()
  • sanitize_title_with_dashes()
  • sanitize_user()
  • esc_url_raw()
  • wp_filter_post_kses()
  • wp_filter_nohtml_kses()

Conclusion

Any time you’re using potentially unsafe data, it never hurts to validate and sanitize it. Validating is confirming the data is what you expect it to be. Sanitization is a more liberal approach to cleaning your data.

Escaping Output

Every time a post title, post meta value, or some other data from the database is rendered to the user, we need to make sure it’s properly escaped. Escaping helps us prevent issues like malformed HTML or the dreaded cross-site scripting attack.

For ease of code review, it’s best to escape as late as possible. WordPress’ escaping functions have low overhead, so there’s no performance penalty to using them as many times as you need to.

Escaping: Securing Output

WordPress thankfully has a few helper functions we can use for most of what we’ll commonly need to do:

esc_html() we should use anytime our HTML element encloses a section of data we’re outputting.

<h4><?php echo esc_html( $title ); ?></h4>

esc_url() should be used on all URLs, including those in the ‘src’ and ‘href’ attributes of an HTML element.

<img src="<?php echo esc_url( $great_user_picture_url ); ?>" />

esc_js() is intended for inline Javascript.

<a href="#" onclick="<?php echo esc_js( $custom_js ); ?>">Click me</a>

esc_attr() can be used on everything else that’s printed into an HTML element’s attribute.

<span class="<?php echo esc_attr( $my_class ); ?>">

It’s important to note that most WordPress functions properly prepare the data for output, and you don’t need to escape again.

<h4><?php the_title(); ?></h4>

Conclusion

Whenever you’re rendering data from the database, you’ll want to make sure it’s properly escaped. Escaping helps prevent issues like cross-site scripting.

Always require a specific file when running WP-CLI

WP-CLI can read default options from a few configuration file types (when present):

  1. wp-cli.local.yml file inside the current working directory (or upwards).
  2. wp-cli.yml file inside the current working directory (or upwards).
  3. ~/.wp-cli/config.yml file (path can be changed by setting the WP_CLI_CONFIG_PATH environment variable).

To always require a specific file when running WP-CLI, you can include a require statement in one of the aforementioned configuration files like so:

require:
  - path/to/file.php

Learn more about WP-CLI’s config system on the WP-CLI website.

Constrain RSS feed images to a specific width

Formatting email newsletters is really hard. When using a RSS feed to deliver content to an email newsletter, you may need to ensure inline images are restricted to a specific width.

The following code snippet constrains images in RSS feed content to 600 pixel width. When an image is larger than 600px wide, it scales the image’s attributes proportionally to fit within 600px. Images already 600px wide or less are ignored.

This approach is also non-destructive. Because the approach filters the rendered output, and not the content in the database, it’s safe to change (or remove) at any future date.

“`
/**
* Constrain images in RSS feed content to 600px (or less)
*/
function hb_constrain_rss_images( $content ) {

$width_constraint = 600;
// Identify all images in the body content
if ( preg_match_all( ‘#]+((width|height)=[“\’]([^”\’]+)[“\’])[^>]+((width|height)=[“\’]([^”\’]+)[“\’])[^>]+>#i’, $content, $matches ) ) {
// Process each image
foreach( $matches[0] as $i => $match ) {
// Identify the width and height attributes on the image
$wk = ‘width’ === $matches[2][ $i ] ? 3 : 6;
$hk = 3 === $wk ? 6 : 3;
// Width is already less than constraint, which means we don’t need to constrain
if ( (int) $matches[ $wk ][ $i ] <= $width_constraint ) { continue; } // Calculate new width / height values, based on constraint $new_width = $width_constraint; $new_height = ( $new_width / $matches[ $wk ][ $i ] ) * $matches[ $hk ][ $i ]; $search_replace = array( $matches[ $wk - 2 ][ $i ] => ‘width=”‘ . $new_width . ‘”‘,
$matches[ $hk – 2 ][ $i ] => ‘height=”‘ . $new_height . ‘”‘,
);
// Replace existing image reference in the body content
$new_img = str_replace( array_keys( $search_replace ), array_values( $search_replace ), $matches[0][ $i ] );
$content = str_replace( $matches[0][ $i ], $new_img, $content );
}
}
return $content;
}
add_filter( ‘the_content_feed’, ‘hb_constrain_rss_images’ );
“`

WordPress plugin release checklist

Getting ready to release a WordPress plugin? Here’s a handy checklist you can follow.

1. Update the readme for the release

  • Include all changes in the changelog
    • List order is typically: major enhancement, minor enhancement, bug fix
    • Use active voice, present tense.
      • “Shows an admin notice when Redis is unavailable”
  • Update version numbers as appropriate
    • readme.txt
    • Plugin header in the main plugin file.
  • Compile README.md from readme.txt (which is needed for WordPress.org)
    • Run grunt readme.
    • Run grunt i18n.

2. Tag a new version on Github

  • Use the releases feature to create a new Release, with a Git tag of the appropriate version number (e.g. “v0.5.0”).
    • Double-check tag name / version number.
    • Copy and paste the changelog into the Release body.

3. Tag a new version on WordPress.org

  • Sync the codebase with WordPress.org plugin Subversion trunk
  • Create a new Subversion tag from trunk.
    • svn cp trunk tags/0.5.0
    • Commit the tag.

4. Publish blog post / execute promotion strategy as necessary.

 

Make an authenticated request to Amazon’s Product ItemLookup API with wp_remote_get()

Amazon has a helpful ItemLookup API for fetching details about a given product. Authenticating your request is a bit tricky though. It requires signing your request with a signature. Here’s an overview to how it works in WordPress.


$query_args = array(
	'AssociateTag'   => AMAZON_ASSOCIATE_TAG,
	'AWSAccessKeyId' => AMAZON_PRODUCT_API_ACCESS_KEY,
	'ItemId'         => $item_id, // Something like B0027Z8VES
	'Operation'      => 'ItemLookup',
	'ResponseGroup'  => 'Large',
	'Service'        => 'AWSECommerceService',
	'Timestamp'      => rawurlencode( date( 'Y-m-d\TH:i:s\Z' ) ),
);

// Signature body must be produced from args sorted by natural byte length.
uksort( $query_args, 'strnatcmp' );

// The first part of the signature body is the request method, domain, and URL.
$signature_body = 'GET' . PHP_EOL . 'webservices.amazon.com' . PHP_EOL . '/onca/xml' . PHP_EOL;;
foreach( $query_args as $key => $value ) {
	$signature_body .= $key . '=' . $value . '&';
}
$signature_body = rtrim( $signature_body, '&' );

// After the signature is generated, it needs to be doubly-encoded to make sure it is URL-safe.
$query_args['Signature'] = rawurlencode( base64_encode( hash_hmac( 'sha256', $signature_body, AMAZON_PRODUCT_API_ACCESS_SECRET, true ) ) );

// Make the request and do whatever you want with the response.
$response = wp_remote_get( add_query_arg( $query_args, 'http://webservices.amazon.com/onca/xml' ) );

Conditionally avoid using the persistent storage backend with WP Object Cache

One common strategy for improving WordPress’ performance is to use a service like Redis or Memcached as a persistent storage backend for the WP Object Cache. This lets you more easily cache the result of expensive data generation.

However, there are cases in which you don’t necessarily want all of your object cache to end up in the persistent storage backend. For instance, if you have hundreds of cache calls on a pageload and each call takes 1 ms of network requests, you may want to prevent some of them from occurring. Enter: non-persistent groups.

First, make sure your calls to wp_cache_add( $key, $value, $group ); and wp_cache_set( $key, $value, $group ); include the third group argument. Then, to mark a group as non-persistent, use wp_cache_add_non_persistent_groups( $group_name );.

Display a user’s email address in the WordPress login form

When you’re already logged in to your WordPress site and visit the login page, WordPress will handily auto-fill the form with your username.

2016-08-03 at 6.12 AM

However, if you’re randomly generating usernames for your users, you might want to present their email address instead of the username. Doing so requires temporarily modifying the $user_login global.