Category / Wordpress

2

Local “Lightning” first impressions

Local “Lightning” is the latest iteration of Flywheel’s popular local development environment. Local is a suite of web server components that allows you to run PHP applications on your computer – a vast improvement on the bad-old-days when we had to FTP files to a remote server to test our work. Shudder.

Flywheel is a WordPress hosting provider so Local is aimed at WordPress developers, but can theoretically be used for any PHP application regardless of where it will be hosted.

Local site
Continue reading

2

Loading missing images from a remote server

When developing on a local server it can be painful to keep images in your media library synced with your production site. Sure, you could download the missing images and attachments to your local machine every time you need to work on the site, but this approach has two problems:

  1. It is time consuming
  2. The files take up space on your local drive

I recently discovered a way to seamlessly handle missing images so that they never 404 or occupy space on your local machine. The solution is an htaccess rewrite rule that checks if an image exists locally, and if it doesn’t, attempts to load it from your production server.

Here is a sample htaccess directive for WordPress:

# Load media files from production server if they don't exist locally
<IfModule mod_rewrite.c>
  RewriteEngine on
  RewriteBase /
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{HTTP_HOST} ^localsite\.dev$
  RewriteRule ^wp-content/uploads/(.*)$ http://remotesite.com/wp-content/uploads/$1 [NC,L]
</IfModule>

Remember to change “remotesite.com” and “localsite.dev” in the example to match your own domain, and adjust your file paths as necessary. If you’re implementing this on a WordPress site make sure to place it before WordPress’ own htaccess directive.

Hat tip to @stevegrunwell for bringing this technique to my attention.

13

Clean up pasted text in WordPress

I have a love/hate relationship with the paste plugin in TinyMCE, the JavaScript WYSIWYG editor that ships with WordPress. In recent releases TinyMCE has gotten very good at sanitizing text pasted from MS Word, but it is still much more permissive than I would like.

A long standing gripe is that classes are added to every paragraph and span when I paste text into the editor:

<p class="p1">This is <span class="s1">pasted</span> text</p>

This odd behaviour is consistent across many applications – InDesign, TextEdit, Mail – so I suspect it may be occurring at an OS level. In any case, I don’t want class attributes finding their way into markup unless I put them there!

A more significant problem is that TinyMCE doesn’t filter out HTML tags from pasted content. If you copy and paste text from a website you might unknowingly bring its markup along for the ride, and end up with something like this:

<div class="row">
  <div class="col-8">
    <table>
      <tr>
        <td><p>This is pasted text</p></td>
      </tr>
    </table>
  </div>
</div>

I’m sure you can imagine how disastrous that extra markup could be on your website’s front end.

Unfortunately TinyMCE doesn’t have a configuration option to specify which tags are permitted in pasted markup. The valid_elements option allows us to define which elements will remain in the edited text when TinyMCE saves, but that is an overzealous solution. On occasion you might need to add markup via TinyMCE, and valid_elements would strip it out again when you save your post. What is needed is a way to clean up text when it is pasted into TinyMCE, but still allow the user to type markup into the editor if they choose.

The solution comes in the form of the paste_preprocess option, which allows us to specify a callback that will be executed when content is inserted into the editor. Within that callback we can strip out any tags that we don’t want, and remove class, id and any other undesirable attributes from our content.

In WordPress you can hook into TinyMCE’s configuration using the tiny_mce_before_init filter. In your theme’s functions.php file:

add_filter('tiny_mce_before_init','configure_tinymce');

/**
 * Customize TinyMCE's configuration
 *
 * @param   array
 * @return  array
 */
function configure_tinymce($in) {
  $in['paste_preprocess'] = "function(plugin, args){
    // Strip all HTML tags except those we have whitelisted
    var whitelist = 'p,span,b,strong,i,em,h3,h4,h5,h6,ul,li,ol';
    var stripped = jQuery('<div>' + args.content + '</div>');
    var els = stripped.find('*').not(whitelist);
    for (var i = els.length - 1; i >= 0; i--) {
      var e = els[i];
      jQuery(e).replaceWith(e.innerHTML);
    }
    // Strip all class and id attributes
    stripped.find('*').removeAttr('id').removeAttr('class');
    // Return the clean HTML
    args.content = stripped.html();
  }";
  return $in;
}

We use jQuery to populate the DOM with the pasted content, then traverse the DOM and remove tags and classes we don’t want. Finally we extract the cleaned HTML markup and pass it back to the editor. This approach may seem convoluted, but it is far less error prone than using regular expressions to sanitize our content.

All you need to do is modify the whitelist variable, which contains a list of tags to allow past your filter.

One last thing: You might notice that our entire JavaScript function is contained in a string. This might seem odd, but is necessary since WordPress stores TinyMCE’s configuration options as an array of strings.

1

A super minimal functions.php file

In my last post I talked about strategies to organize your WordPress theme’s functions.php file by following object-oriented programming principles. If you prefer to stick with a procedural coding style, here is a simple way to keep your functions.php file lean and mean.

Simply save each hooked function in a separate file, then use PHP includes to import them into functions.php:

require_once('includes/custom_excerpt_length.php');
require_once('includes/another_function.php');
require_once('includes/a_third_function.php');
// etc.

It doesn’t get much more minimal than that!

In this example our custom_excerpt_length.php file might look like this:

function custom_excerpt_length () {
  return 10;
}
add_filter('excerpt_length', 'custom_excerpt_length');
4

Organize your WordPress functions.php file

WordPress tutorials will often advise you to copy and paste code snippets directly into your theme’s functions.php file. That might seem harmless enough, but if you keep adding functionality in an arbitrary fashion your code can quickly become unmanageable. In this post I will share some strategies for keeping your functions.php file tidy and making it easier to maintain.

Keep the global space clean

One problem with the typical functions.php file – and, it could be argued, with WordPress generally – is that variables and functions are declared in the global scope. That means you risk naming conflicts with functions and variables declared elsewhere in your own code, in plugins, or in the WordPress core.

As an example, suppose you create a function named get_post_slug in functions.php. Now let’s imagine that in the future a function with the same name is introduced to the WordPress core. Your function declaration will now throw a fatal error:

Fatal error: Cannot redeclare get_post_slug()

To avoid naming collisions you could prefix your function names to make them unique – for example yourthemename_get_post_slug – but a more robust approach is to encapsulate your theme’s logic in a class.

A typical hooked function looks like this:

function custom_excerpt_length () {
  return 100;
}

add_filter('excerpt_length', 'custom_excerpt_length');

Here is that same functionality encapsulated in a class:

class YourThemeName {
  function __construct() {
    add_filter('excerpt_length', array($this, 'custom_excerpt_length'));
  }

  function custom_excerpt_length () {
    return 100;
  }
}

$yourthemename = new YourThemeName();

Because the custom_excerpt_length function is a class method it no longer has global scope, and the $yourthemename variable ensures that our theme’s logic has a single point of access. (Note: You should change YourThemeName and $yourthemename to something unique to your theme.)

You might have noticed that the second parameter passed to add_filter is an array, with $this as the first value. This tells WordPress to use a class method as a callback. You can read more about the different types of callbacks in the add_filter documentation.

Theme setup

Most themes have some setup code that needs to run straight away. Let’s add an init method to our class to handle this job:

class YourThemeName {
  function __construct() {
    add_action('after_setup_theme', array($this, 'init'));
    // Register action/filter callbacks here...
  }

  function init() {
    // All theme initialization code goes here...
    register_nav_menus(
      array(
        'header-menu' => __('Header Menu', 'yourthemename')
      )
    );
  }

  // Action/filter callbacks go here...
}

$yourthemename = new YourThemeName();

Any functionality you want hooked to the after_setup_theme action can go in the init method. In the example above we registered a navigation menu.

Utility methods

In addition to WordPress action/filter hooks your theme class can also provide utility methods, which can be accessed from templates.

Here is a simple utility method to get the ID of a category by supplying its name:

function get_category_id ($cat_name) {
  $term = get_term_by('name', $cat_name, 'category');
  return $term->term_id;
}

If you include that method in your theme’s class you can now access it from a template like so:

$yourthemename->get_category_id('category-name');

Group your functions by type

Rather than arranging callback function in a haphazard fashion, grouping them by type makes them easier to find.

As we saw earlier all of your theme’s filter/action callbacks are registered in the class constructor, and all setup logic is placed within the init method. I also like to group all my action callback functions together, my filter callback functions together and my utility methods together.

class YourThemeName {
  function __construct() {
    add_action('after_setup_theme', array($this, 'init'));
    // Register action/filter callbacks here...
  }

  function init() {
    // Theme setup code goes here...
  }

  // Action callbacks go here...

  // Filter callbacks go here...

  // Utility methods go here...
}

Comment your code

It might go without saying, but well written comments make your code easier to read and comprehend. Well structured comments can go a long way toward organizing your functions.php file.

/**
 * Get the category id from a category name
 *
 * @param string $cat_name The category name
 * @return int The category ID
 */
function get_category_id ($cat_name) {
  $term = get_term_by('name', $cat_name, 'category');
  return $term->term_id;
}

Use a plugin for site-specific functionality

While it may be tempting to dump all your site’s custom functions into your functions.php file, it is good practice to encapsulate site-specific logic in a separate functionality plugin. In addition to long term maintenance benefits, this separation of concerns streamlines your functions.php file by offloading some of its functionality to a plugin.

There have been many good articles written about what belongs in a functions.php file and what belongs in a functionality plugin, but I think Curtis McHale sums it up nicely:

The only deciding factor in where you put your code is the life of the functionality. Will you still need to have that portfolio custom post type the next time you redesign your site? If the answer is yes then you should be building a plugin with the code. If the answer is no, then put the code in you theme functions.php.

Example functions.php file

If you employ the strategies I have outlined in this post I hope you will find that your functions.php file is better organized and easier to manage. Here is an example of how the final file might look:

/**
 * YourThemeName
 */
class YourThemeName {
  /**
   * Constructor
   */
  function __construct() {
    // Register action/filter callbacks
    add_action('after_setup_theme', array($this, 'init'));
    add_filter('excerpt_length', array($this, 'custom_excerpt_length'));
  }

  /**
   * Theme setup
   */
  function init() {
    // Register navigation menus
    register_nav_menus(
      array(
        'header-menu' => __('Header Menu', 'yourthemename')
      )
    );
  }

  /**
   * Filter callbacks
   * ----------------
   */

  /**
   * Customize post excerpt length
   *
   * @return int The new excerpt length in words
   */
  function custom_excerpt_length () {
    return 100;
  }

  /**
   * Utility methods
   * ---------------
   */

  /**
   * Get the category id from a category name
   *
   * @param string $cat_name The category name
   * @return int The category ID
   */
  function get_category_id ($cat_name) {
    $term = get_term_by('name', $cat_name, 'category');
    return $term->term_id;
  }
}

// Instantiate theme
$yourthemename = new YourThemeName();
33

Paginating an Advanced Custom Fields Repeater

Hands down my favourite WordPress plugin is Elliot Condon’s Advanced Custom Fields, and it’s made even more powerful by the Repeater Field add-on. But a Repeater can get unwieldy when it contains a large number of items, and you might find yourself wanting to paginate the results when you display them to the user. Here’s a technique for doing that.

In this example we will paginate a Repeater image gallery mapped to a custom field named image_gallery with a sub field named image which contains an image object. Our Repeater will be displayed on a page at the URL /gallery.

10 images will be displayed per page, and pagination links will allow the user to navigate between the gallery’s pages at pretty URLS such as /gallery/2/, /gallery/3/ and so on.

Continue reading

15

WordPress custom fields not saving? PHP’s max_input_vars may be to blame.

On two occasions recently I have run into a problem where WordPress custom fields were disappearing after a post was edited. The first time I noticed this odd behaviour was on a site using the Advanced Custom Fields Repeater add-on for an image gallery. Each gallery item contained an image field along with several meta data fields, and once the gallery grew significantly large new items stopped being created. All of the existing gallery items would save successfully, but newly created fields would disappear when the post was updated.

The second time I encountered disappearing data was on a WooCommerce installation using the Per Product Shipping module. This module requires shipping costs to be entered individually for each country (why, WHY?) and as a consequence each product had a large number of custom fields. Surprisingly it wasn’t the shipping costs that were disappearing, but the product attributes – size, colour and so forth. This had disastrous effects, since when the attributes disappeared the linked product variations were removed too, along with the product prices.

In both cases data would only disappear on the production web server. When I worked on the sites locally custom fields would save as expected, so I was fairly certain that the server configuration was to blame. No errors were being displayed within WordPress or in my server logs so I initially focussed on MySQL, since the bug seemed to stem from a failure to write to the WordPress database. That line of enquiry proved fruitless, but thankfully I came across a thread on the Advanced Custom Fields forum which mentioned PHP’s max_input_vars setting as a potential culprit.

max_input_vars was introduced in PHP 5.3.9, and limits the number of $_POST, $_GET or $_COOKIE input variables that PHP will accept. Further variables are truncated from the request. The default value for max_input_vars is 1000, which means that if your form has has more than 1000 inputs, any additional values will be lost. Since each custom field is an input in the WordPress editor, plugins like Advanced Custom Fields Repeater and Per-Product Shipping make it possible to exceed the 1000 input limit.

You can increase max_input_vars in your php.ini file:

max_input_vars = 3000

or in an .htaccess document:

php_value max_input_vars 3000

After doing that you should find that you can once again save a large number of custom fields.