Capturing Unlisted Shortcode Attributes

I did a project recently to develop a plugin that utilizes a shortcode. No problem at all. One of the requirements, however, for this shortcode was to allow users to enter attributes that might not be “whitelisted” (a.k.a the defaults that you would normally place for the shortcode). I thought this was pretty interesting and kind of a unique idea. I wasn’t quite sure of the point at first to be honest, but now I can see the reason behind it and will get to that a little bit further down. Let’s dive into the code! (jump to the full code example if you prefer)

The Process

We do still need to support the basic defaults here as well. These unlisted attributes will just be an addition to the predefined attributes we want. So let’s get started here by diving into the code and starting all the way at the beginning.

Class Setup

First thing we need to do is set up our shortcode functionality. I build most of my plugins using Object Oriented techniques so let’s make a class to set this up. Here is how it will look before we start filling it in.


class Shortcode_Class {

	public function __construct() {}

	public function shortcode_test( $atts ) {}

	private function get_unlisted_atts( $defaults, $values ) {}
}

Shortcode Functionality

Now that we have the basic class setup we want to add our shortcode. This will be done with the WordPress native function add_shortcode(). This function takes 2 parameters and then will
return the output. Returning the output is important here because if you just echo the output then this can cause display issues when the shortcode is used. The code to add the shortcode looks like this.

class Shortcode_Class {


	public function __construct() {

		add_shortcode( 'shortcode_test', array( $this, 'shortcode_test' ), 10, 2 );
	}

	/**
	 * Function to output our shortcode.
	 *
	 * @param $atts array The attributes added in the shortcode tag.
	 *
	 * @return string
	 */
	function shortcode_test( $atts, $content ) {

		// Setup some defaults.
		$defaults = array(
			'attribute1' => 'default1',
			'attribute2' => 'default2',
		);

		// Update our $atts array using the WordPress native shortcode_atts function to set the defaults and get rid of any unlisted attributes
		$atts = shortcode_atts( $defaults, $atts, 'shortcode_test' );

		// Now we can use our $atts array as normal
		$html = '';

		// Example output to show all of the attribute information
		foreach ( $atts as $k => $v ) {
			$html .= 'key: ' . $k . ', value: ' . $v . '<br>';
		}

		return $html;
	}


	private function get_unlisted_atts( $defaults, $values ) {}
}

This is a very basic shortcode that allows 2 attributes to be sent over. We do use the native WordPress function shortcode_atts() to pull the values and defaults into our $atts array. This will help us to filter out the unlisted attributes later on also. The output of this is just a simple loop to show the key and value of each attribute.

Now that we have our shortcode setup all we have left to do is to get the unlisted attributes so we can use them.

Getting the Unlisted Attributes

As you have already seen in the previous examples we will be filling in the get_unlisted_atts() method. What does this mean exactly? Let’s take a quick detour and explain what I mean by unlisted attributes a bit more in depth.

With a shortcode we can add attributes and then when it gets processed the code will do something with these attributes – but only if they are whitelisted. So in our code so far a shortcode like this:

[shortcode_test attribute1="red" attribute2="green"]

Will set the appropriate values for attribute1 and attribute2. Easy peasy. But what happens if a user adds something like this:

[shortcode_test attribute1="red" random_attribute="random value!"]

Well, in our code so far the random_attribute will be stripped out because we used the shortcode_atts() function which removes any extra values found that are not in our whitelisted attributes. What our goal is here is to allow these extra shortcode attributes that we may not actually know about yet.

Do you still follow me? If you are wondering what the point is then I will get to that in a bit…but first let’s add the functionality to allow these extra attributes. Here is what the function will look like:

        /**
	 * Return an array of values that are not found in the default array.
	 *
	 * @param $defaults array The defaults to check against
	 * @param $values array The values we are checking
	 *
	 * @return array
	 */
	private function get_unlisted_atts( $defaults, $values ) {

		// First make sure we have the right data before continuing
		if ( is_array( $defaults ) && is_array( $values ) ) {

			// Temporary array
			$unlisted = array();

			// Make sure we have a valid array first before looping
			if ( ! empty( $values ) ) {
				// Loop through the passed in $values
				foreach ( $values as $k => $v ) {

					// If the value is in the defaults then we just skip this entry
					if ( in_array( $k, $defaults ) ) {
						continue;
					}

					// The WordPress $atts parameter passed in for the shortcode looks one of 2 ways:
					// If the attribute has a value set like `attribute="value"` then it will in the form of array( 'attribute' => 'value' )
					// If the attribute has no value set like `attribute` then it will get an index and the attribute name will become the value in the array like this - array( [0] => 'attribute' )
					// So right here we are checking if the type of the key value ($k) is a string or an integer and then appending it to our temp array appropriately
					if ( 'string' === gettype( $k ) ) {
						$unlisted[ $k ] = $v;
					} else if ( 'integer' === gettype( $k ) ) {
						$unlisted[ $v ] = '';
					}
				}
			}

			return $unlisted;
		}

		return array();
	}

I have added comments that should help explain most of what is going on here. To make a quick summary though, basically what we are doing is looping through these extra attributes and then assigning them to the same array structure that our $atts will be using in the shortcode function. There are 2 checks here:

  1. To check for an attribute="value" combo.
  2. To check for an attibute with no value (i.e. [shortcode_test my_attribute])

The way these are sent to us varies so we need the extra check to find out if the attribute has a value or not.

Now all we need to do is use our new function in the shortcode function!

Shortcode Function Modifications

What we want to do here is add the returned array from get_unlisted_atts() into our already defined $atts array.

        function shortcode_test( $atts, $content ) {

		// Setup some defaults.
		$defaults = array(
			'attribute1' => 'default1',
			'attribute2' => 'default2',
		);

		// Now get our unlisted attributes before we rewrite the $atts array
		$unlisted_atts = $this->get_unlisted_atts( $defaults, $atts );

		// Update our $atts array using the WordPress native shortcode_atts function to set the defaults and get rid of any unlisted attributes
		$atts = shortcode_atts( $defaults, $atts, 'shortcode_test' );

		// Merge our unlisted attributes together with the updated $atts array
		$atts = array_merge( $atts, $unlisted_atts );

		// Now we can use our $atts array as normal and include any extra attributes that a user may have included
		$html = '';

		// Example output to show all of the attribute information
		foreach ( $atts as $k => $v ) {
			$html .= 'key: ' . $k . ', value: ' . $v . '<br>';
		}

		return $html;
	}

A brief breakdown of what’s happening. We first set our defaults for the actual attributes we know about. Next we will get the unlisted attributes before we loop our $atts through the shortcode_atts() function. This is important as we need the full array and not the stripped down version in order to get all of the unlisted attributes. After we process our unlisted attributes is when we run it through the loop. At this point we will have 2 different arrays – one with the whitelisted attributes and one with our unlisted attributes – so all we need to do is merge them together.


Here is what the entire file should look like now.

<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Shortcode_Class {


	public function __construct() {

		add_shortcode( 'shortcode_test', array( $this, 'shortcode_test' ), 10, 2 );
	}

	/**
	 * Function to output our shortcode.
	 *
	 * @param $atts array The attributes added in the shortcode tag.
	 *
	 * @return string
	 */
	function shortcode_test( $atts, $content ) {

		// Setup some defaults.
		$defaults = array(
			'attribute1' => 'default1',
			'attribute2' => 'default2',
		);

		// Now get our unlisted attributes before we rewrite the $atts array
		$unlisted_atts = $this->get_unlisted_atts( $defaults, $atts );

		// Update our $atts array using the WordPress native shortcode_atts function to set the defaults and get rid of any unlisted attributes
		$atts = shortcode_atts( $defaults, $atts, 'shortcode_test' );

		// Merge our unlisted attributes together with the updated $atts array
		$atts = array_merge( $atts, $unlisted_atts );

		// Now we can use our $atts array as normal and include any extra attributes that a user may have included
		$html = '';

		// Example output to show all of the attribute information
		foreach ( $atts as $k => $v ) {
			$html .= 'key: ' . $k . ', value: ' . $v . '<br>';
		}

		return $html;
	}

	/**
	 * Return an array of values that are not found in the default array.
	 *
	 * @param $defaults array The defaults to check against
	 * @param $values array The values we are checking
	 *
	 * @return array
	 */
	private function get_unlisted_atts( $defaults, $values ) {

		// First make sure we have the right data before continuing
		if ( is_array( $defaults ) && is_array( $values ) ) {

			// Temporary array
			$unlisted = array();

			// Make sure we have a valid array first before looping
			if ( ! empty( $values ) ) {
				// Loop through the passed in $values
				foreach ( $values as $k => $v ) {

					// If the value is in the defaults then we just skip this entry
					if ( in_array( $k, $defaults ) ) {
						continue;
					}

					// The WordPress $atts parameter passed in for the shortcode looks one of 2 ways:
					// If the attribute has a value set like `attribute="value"` then it will in the form of array( 'attribute' => 'value' )
					// If the attribute has no value set like `attribute` then it will get an index and the attribute name will become the value in the array like this - array( [0] => 'attribute' )
					// So right here we are checking if the type of the key value ($k) is a string or an integer and then appending it to our temp array appropriately
					if ( 'string' === gettype( $k ) ) {
						$unlisted[ $k ] = $v;
					} else if ( 'integer' === gettype( $k ) ) {
						$unlisted[ $v ] = '';
					}
				}
			}

			return $unlisted;
		}

		return array();
	}
}

But…WHY?!

If you have made it this far then awesome! The main reason of why you would do something like this could vary, but will likely be generally the same. It can be used to add certain attributes that you might not know exist yet and allow your shortcode to support future attributes that you haven’t even thought of yet! Let’s look at an example.

Imagine you have some sort of cool new application. This application connects some sort of markup to a JavaScript embedder or something like. Let’s say, for a comment application that allows users to add user comments from your service to their WordPress with this simple shortcode. This will work by just including a specific <div> tag to your site and then letting the JS do the rest. Pretty simple for the user. Let’s say your initial release of the shortcode/plugin will allow the user to set a few things.

  • Border color
  • Background color
  • Text color

So you want to add a shortcode that handles at least these 3 items and that’s it at this point. Adding the shortcode like this:

[my_comments border-color="red" background-color="black" text-color="white"]

Will translate to a <div> using data attributes like this:

<div id=”my-comments” data-border-color="red" data-background-color="black" data-text-color="white"></div>

Now, with the standard shortcode approach if we wanted to add an attribute called “show-avatar” then we would need to go update the shortcode attributes, add the logic, and then push out a new release of the plugin. With the method we just went over about adding unlisted attributes though, this would already work! All that would be needed is an update to your JS file that actually processes the data attributes from the <div>.

So you would simply just add the new attribute to your documentation and JS handler and then a user could add a shortcode like this:

[my_comments show-avatar="true"]

And it would automatically work without any updates to the WordPress plugin. This could be used for other things like adding each attribute as a CSS class.

When I started writing this out I didn’t realize it was going to be such a long post (sorry!) but if you made it all the way to the end then I appreciate your time and thanks for sticking with me.

What is another way you could find this useful? Share in the comments!

Leave a Reply

%d bloggers like this: