Responsible WordPress plugin development – More compatibility, less support tickets

Ernest Marcinko Tutorials, Wordpress 1 Comment

Tutorial level

intermediate

The wordpress plugin database is now filled with thousands of plugins. In my first few months of development I encountered lots of incompatibility issues, I almost recieved a support ticket after each selling. Altough you cannot make a plugin that is 100% compatible with all the other plugins in the repository, but you can and must try to make it as good as possible.

From my personal experiences I have made incompatibility groups which I always quick check before deploying a new version of a plugin:

  • Frontend
    • Javascript Incompatibility
    • CSS Incompatibility
    • Server Side incompatibility
  • Backend
    • Unnecessary script inclusion
    • Unnecessary CSS inclusion
    • Unnecessary php include()/require() calls!

That’s about it. Let me break down each point for you with possible solutions.

Frontend

What is happening on the frontend exactly? Each and every plugin loads – lots of javascript and lots of css and of course, lots of server side operations. What you want to do is minimize each and every aspect of this, because all three affect the page load time heavily.

Javascript incompatibilities

Nasty and hard to fix. There is a tremendous amount of possible problems with javascript – especially when using jQuery. See, each and every plugin is trying to use the same jQuery instance that’s loaded by default in WordPress. Many of them use custom jQuery plugins, or worse, same plugins with different versions. The problems are:

  • Different jQuery plugins with the same name overriding each other – custom made plugins
  • Same jQuery plugins but different versions overriding each other –  best example is jQuery UI
  • jQuery plugins using other jQuery plugins may run into both of the issues above – mainly custom made plugins
Solution

I’ve done in-depth research in this field, trying somehow merging jQuery to different scopes, but no safe solution there. Always use IIFE when developing your plugins, to protect them from outside:

// Anonymous function that has three arguments
function(w, d, $) {

  // You can now reference the window, document, and jQuery objects in a local scope

}(window, document, window.jQuery); // The global window, document, and jQuery objects are passed into the anonymous function

However this is not enough for 100% compatibility.

The robust layer 2 – an own jQuery

The following solution is not elegant in any ways, however it ensures 100% compatibility across jQuery. You should include this solution in your plugin, however you shouldn’t use it as default, more like an option. The solution is – including an own jQuery instance with a different name. Once again, this is a very unelegant solution and you should avoid it, but in some cases it’s inevitable.

  1. Copy the jQuery source to a file like: myjquery.js
  2. Change the bottom of the code for your desired scope
    // Expose jQuery and $ identifiers, even in
    // AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
    // and CommonJS for browser emulators (#13566)
    if ( typeof noGlobal === strundefined ) {
    	//window.jQuery = window.$ = jQuery; -> THE OLD CODE
            window.my_jQuery = window.my_$ = jQuery;
    }
    return jQuery;
    }));

What does this do? Now you have another jQuery in the window.my_jQuery variable so you can use it as:

my_jQuery('.class').on(...
my$('.class').on(...

You can now use your jQuery plugins to extend the my_jQuery/my$ scope instead if the classical jQuery/$

Pros

  • You have your very own jQuery instance to work with
  • Cannot conflict with other plugins, which use the jQuery/$ scope
  • and you can debug easier.

Cons

  • 2 jQuery code on one page
  • extra ~40kB compressed code

itcrowd-make-it-better

Some users doesn’t minde another 40-50kB of code for maximum compatibility – this is what you should keep in mind. Not forcing, but leaving the opportunity.

CSS incompatibilites

Does your plugin frontend look neat on a fresh virgin WordPress install? Good. Now how about a client using a custom template with 30 other plugins? Will it look the same? I highly doubt it. After loading a few more CSS files I guarantee that something will go wrong. Unresponsible usage of !important tags, overriding same class selectors, global definitions etc…

Reset your plugins CSS – the right ways

Using class selectors
.your_plugin_container,
.your_plugin_container * {
  -webkit-box-sizing: border-box; 
  -moz-box-sizing: border-box;    
  -ms-box-sizing: border-box;
  -o-box-sizing: border-box;
  box-sizing: border-box;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  text-transform: none;
  text-shadow: none;
  box-shadown: none;
  text-decoration: none;
  text-align: left;
}
 Involving an id
#your_plugin_container,
#your_plugin_container * {
  -webkit-box-sizing: border-box; 
  -moz-box-sizing: border-box;    
  -ms-box-sizing: border-box;
  -o-box-sizing: border-box;
  box-sizing: border-box;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  text-transform: none;
  text-shadow: none;
  box-shadown: none;
  text-decoration: none;
  text-align: left;
}

This method is far more robust then the other one. Using a unique id ensures, that none of the properties will get overwritten from other CSS files.

Avoiding common classname usage

There are many common CSS class names, that are used and defined by most of the WordPress themes, like: .post .page .content .wrap

I highly encurage you to avoid using such well known and simple selectors, it only causes confusion and will consume your precious time. Instead use unique prefixes/affixes in your CSS files:

.myplugin_container .content {
   /*  NO NO NO, DON'T DO THIS */
}

.myplugin_container .myplugin_content {
   /*  YES! IT'S HIHGLY UNLIKELY SOMEONE WILL OVERRIDE THIS CLASS */
}

See the difference? The .content selector is far too common to use, and what if another plugin or an inline CSS overrides the .content CSS declaration? You could justify it by using the !important tag, but you don’t want to do that.

Solution: Instead, define a unique content class, like .myplugin_content – it is highly unlikely that some other CSS declaration will match that.

Server Side Incompatibilities

Most common problems:

  • PHP version incompatibilities
  • File read/write permission problems
  • CURL/fopen wrapper availability
  • Functions with identical names
  • Classes with identical names

PHP version incompatibilities

Always keep yourself updated about the recent PHP changes, so if something gets deprecated you won’t get surprised. Nothing more to add here.

File read/write permissions

The most and best you can do with this issue is to check if the desired files/folders are writeable, and notice the user about possible errors. Almost anyone has ftp access and knows how to CHMOD folders/files, if not, then you can also link a resource like: http://www.siteground.com/tutorials/ftp/ftp_chmod.htm

Personally I use the following function to check for directory permissions:

function check_dir_w($path, $cons = '') {
  $writeable = is_writeable($path);
  if ($writeable===false) {
    $this->errors[] = "The <b>".$path."</b> directory is not writeable!";
    $this->consequences[] = $cons;
    $this->solutions[] = "
      Use an ftp clien to chmod (change permissions) the <b>".$path."</b> directory to 666, 755, or 777<br />
      Read <a href='http://www.siteground.com/tutorials/ftp/ftp_chmod.htm'>this siteground</a> article if you need help.
    ";
  }
  return $writeable;
}

CURL and fopen availability

In some cases you need CURL or fopen url wrapper to get contents from files/images. There is a chance however that none of these is available on the users server, so you might want to check it first:

function can_open_url($cons='') {
  if (function_exists('curl_init')){
    return true;
  } else if (ini_get('allow_url_fopen')==true) {
    return true;
  }
  $this->errors[] = "Curl and url fopen is disabled on this server!";
  $this->consequences[] = $cons;
  $this->solutions[] = "You might need to contact the server administrator to resolve this issue for you.";
  return false;
}

Functions/Classes with identical names

This might sound funny, but it happens more often than you think. For example you create a function named hex2rgb in PHP. How can you be sure, that none of the most used/well known plugins hasn’t implemented it yet, and that their implementation matches your needs and vice-versa? You can’t. This is big trouble.

Solution 1: Function/Class names with prefixes
function hex2rgb() {
   // Don't do this! Unsafe!
}

// Instead use a prefix, and check with function exists

if (!function_exists('myplugin_hex2rgb')) {
   function myplugin_hex2rgb() {
      // Much better!
   }
}
 Solution 2: A function wrapper class with static methods

Not better nor worse then the other solution. In OOP terms it’s nicer, but the overall usage is not confortable in any ways.

if (!class_exists('myPluginFunctions')) {
  class myPluginFunctions {
    public static function hex2rgb() {
      // this function is now wrapped in the myPluginFunctions class
    }
  }
}

// Usage:
$hex = myPluginFunctions::hex2rgb();

Backend

Unfortunately managing your frontend is not enough. You also need a robust backend for a succesful plugin. You don’t want to interfeere with other plugins, nor you don’t want other plugins to interfeer with yours. Not being able to configure your plugin is very frustrating for the users.

How to manage your inclusions?

First thing first: You need a good file/class structure. Each of your Classes (or set of classes) must have it’s very own distinct purpose – and should encapsulate most of the functionality to be independent from other classes.

For example: You have a class, that manages image functions – resizing, writing output files etc.. then you should know where will you use it. If you aren’t using it on the backend, then why would you include it and possibly all of the files that this class depends on everywhere on the site? Same goes for Javascript and CSS. If you dont need it, don’t load it.

By the plugins point of view we can divide WordPress to three main “statuses”:

  • Being on the frontent
  • Being on the backend
    • Own backend pages
    • Other backend pages
  • In an ajax call

When loading the resources you should always check which “status” is currently active.

/**
 * This is only a pseudo code, not functional in live WP environment
 *   
 */ 

if (on_admin_area()) {
  if ($on_own_admin_pages) {
     // Codes/includes/scripts on plugin's own admin pages
  } else {
     // Codes/includes/scripts on other pages
  }
}

if (on_frontend()) {
  // Codes/includes/scripts on frontend
}

if (doing_ajax()) {
  // Codes/includes/scripts you need in an ajax request
}

This pseudo-code is only an example of a possible structure of checking the statuses. The actual fabricated code would be much more longer. Here are some basic possible implementations of the upper mentioned functions:

function on_admin_area() {
  return (is_admin() && !isset($_POST['action']));
}

function on_own_admin_pages() {
  $pages = = array(
    "your-plugin-dir/path/settings1.php",
    "your-plugin-dir/path/settings2.php"
    // add as many as you want
  );

  if (isset($_GET) && isset($_GET['page'])) {
      return in_array($_GET['page'] ,$pages);
  }
  return false;
} 

function on_frontend() {
  return (!is_admin() && !isset($_POST['action']));
}

function doing_ajax() {
  return isset($_POST['action']);
}

Conclusion

These are my very basic principles when creating a new plugin. In my case it’s always worth sacrificing a few hundred lines once for better compatibility and less support. These principles helped me to reduce my products support rates to below 15% from the initial 70%+ regarding compatibility issues.

Comments 1

Leave a Reply to Szépe Viktor Cancel reply

Your email address will not be published.