WordPress shortcode plugins with multiple instances

I’ve been working on the JSmol2wp plugin used in these previous posts. There have been a couple of challenges associated with putting multiple copies of the JSmol viewer applet in the same post, and in having multiple posts with viewers.

The WordPress shortcode API doesn’t provide an obvious way for a shortcode to know its position in a post. For most shortcodes, this doesn’t matter; they return content to WordPress to put in place of the shortcode. The reason it’s important for JSmol2wp (and perhaps to other plugin developers) is that JSmol2wp needs to assign each applet a unique identifier so that commands are channeled to the correct applet. Googling “wordpress shortcode multiple instances” reveals other developers having the same problem.

My solution(s)

Various versions of JSmol2wp used variations on the same solution based on the idea that knowing the integer value for which copy of the shortcode I had is valuable. In hindsight, there are two easier solutions:

  • create a unique id that does not depend on anything else, based on something like a timestamp or an md5 of various passed parameters
  • make the user encode the uniqueID (I don’t like this one, but I ended up having it available as a fallback

The solution I used is to pull the entire content of the post and search for the shortcode markup  using either string functions or regexes. The current version is clunky because I was not sure whether PCRE was causing problems for some installations.

$p = get_post();
# determine the instance if there are multiple copies
# of the shortcode in this post
# we want to do this without preg_match to work on different PHP versions
$m = explode('[jsmol', get_the_content());
array_shift($m);
foreach($m as $i => $match){
     $t = explode(']', $match);
     # there could be nested shortcodes or other shortcodes in the text
     # but trim off what is safe to trim off
     if(count($t) > 1){
        array_pop($t);
        $match = implode(']]', $t);
     }
     # odd bug requires recasting as a string to get stripos to work
     $match = (string)$match;
     # catenate the post_id to the instance to make the id unique
     # when displaying multiple posts per page
     if( ($acc == '' || stripos($match, $acc) > 0 ) &&
        ($caption == '' || stripos($match, $caption) > 0) &&
        ($fileURL == '' || stripos($match, $fileURL) > 0) &&
        ($isosurface == '' || stripos($match, $isosurface) > 0) &&
        ($id == '' || stripos($match, $id) > 0)
     ) $this->instance = $p->ID."_$i";
}

This would be better with the right regex, but my regex skills are not that good at thinking about how to handle the possibility of [ and ] inside the parameters passed by the shortcode, since these are legal characters in Jmol scripting.

Note that the unique id includes the post ID. This prevents clashes when multiple posts are displayed on a single page.

The

 # odd bug requires recasting as a string to get stripos to work
$match = (string)$match;

was to fix a problem where spaces in one of the parameters (caption) caused stripos to return false, even though var_dump showed $match was already a string.