Boost your WYSIWYG with Shortcode module

Learn how to create custom shortcodes in 3 simple steps.

Photo of Salvador Molina Moreno
Tue, 2016-03-01 08:32By salva

Giving editors full control over the elements they want to place in their contents is not always an easy task. When there's a need to embed rich media content on a Drupal site through a WYSIWYG editor, it's important to find a balance between the markup to allow editors to use (for security), and the simplicity and convenience of the tools that allow for such embedding.

This article describes how the shortcode module for Drupal 7 can be used to cover complex requirements when it comes to embedding media content in WYSIWYG areas, with the minimum amount of code, and with a great user experience for editors, while maintaining secure filters on text formats.

So what's that shortcode thing?

For those of you who don't know, the shortcode module is a token-based text format that allows editors to enter special markup on WYSIWYG fields, without requiring any special permissions to insecure html tags. 

To illustrate this, let's say an editor wants to include a link in a content page, and make it look like an actual button. With the [button] shortcode, offered out of the box by the shortcode module, this editor could do this by simply entering this text in the textarea:

[button href="someurl" text="some text"]

And the shortcode module would do the rest to transform that into:

<a class="button" href="someurl">some text</a>

Or whatever markup is configured for that particular shortcode (your site could be using the <button> tag, for example), instead of having to configure new WYSIWYG styles or templates just for simple visual enhancements. Not bad, right? But, what are the actual benefits of using shortcode, compared to writing a custom filter for your text formats? Well:

  1. Shortcode module is a text format in itself. It comes with some shortcodes by default, which you can enable / disable individually. You can see the default ones on the project page.
  2. It's an API-like module, which means you can easily add new shortcodes for your needs without having to create new filters, therefore you avoid cluttering your text formats configuration page with multiple filters that are fundamentally the same. Also, you don't have to worry about regular expressions to replace the tokens with real content, and at the same time, maintain consistency in the codebase by using the same approach for all your token-replacement functionality.
  3. It has a WYSIWYG button plugin out of the box for each shortcode, via the Shortcode for WYSIWYG module. WYSIWYG integration out of the box, without writing a single line of Javascript.

In one of our recent projects, our client wanted to embed Shopify widgets in some of their pages. These widgets require some javascript to work, as well as a relatively complex html tag structure. We were already using the oEmbed module to embed content and widgets from a number of external sources. Unfortunately, Shopify didn't seem to support oEmbed at the time.

Luckily for me, Shortcode was exactly what I was after: API-like, token-based, and with WYSIWYG integration out of the box. Problem solved! In just a couple hours, without having any previous experience with the module, I had a nice-looking WYSIWYG plugin like this:

node_form_shortcode_widget

Implementing a custom shortcode

Without further ado, let's show an example of how you can implement your custom shortcodes, with WYSIWYG integration, in just a few lines of code. In 3 (optionally 4) simple hooks, we can get a shortcode token up and running. Let's start with the first one:

hook_shortcode_info(): Standard Drupal "info" hook implementation. All we do here is specify a title for our shortcode, and some callbacks the core module will need to transform our tokens into markup.

Our implementation:

/**
 * Implements hook_shortcode_info().
 */
function stem_wysiwyg_shortcode_info() {
  $shortcodes['shopify'] = array(
    'title' => t('Shopify Content'),
    'description' => t('Allows embedding a shopify widget with the required Javascript'),
    'process callback' => 'stem_wysiwyg_shortcode_shopify_process',
    'tips callback' => 'stem_wysiwyg_shortcode_shopify_tip',
    'attributes callback' => 'stem_wysiwyg_shortcode_shopify_attributes',
  );
  return $shortcodes;
}

 

The "process callback" is the function that will be used to transform the token into markup.  "tips callback" allows us to return the text that is displayed in the text format info, below all the Drupal textareas. Finally, the "attributes callback" is where we return the attributes that can be specified for our shortcode. In the button example from the beginning of the article, these would be the "href" and the "text" attributes.

I'm skipping the "tips callback" for simplicity, as it's not mandatory. You can find examples of it in the module itself, which has a shortcode.api.php file, well documented. As for the "attributes callback", this is what we had to implement for our Shopify widget:

/**
 * Attributes callback for the 'shopify' shortcode.
 *
 * @see stem_wysiwyg_shortcode_info().
 */
function stem_wysiwyg_shortcode_shopify_attributes($form, &$form_state) {
  $form['shopify-shop'] = array(
    '#title' => t('Shop (data-shop)'),
    '#type' => 'textfield',
    '#default_value' => 'shopname.myshopify.com',
    '#states' => array(
      'visible' => array(
        ':input[name="shortcode"]' => array('value' => 'shopify'),
      ),
    ),
  );
  $form['shopify-product_handle'] = array(
    '#title' => t('Product Handle (data-product_handle)'),
    '#type' => 'textfield',
    '#states' => array(
      'visible' => array(
        ':input[name="shortcode"]' => array('value' => 'shopify'),
      ),
    ),
  );
  /* Rest of form omitted for brevity */
  return $form;
}

 

All we have to do here is return a standard Form API array, as if we were building the form ourselves, except for shortcode we don't care about the processing of it. We just specify the attributes we need to gather. This hook is optional, since it's not required to gather any data for your shortcodes, although it's certainly something you'll implement for custom tokens if you want WYSIWYG integration!

Finally, for our process callback, this is what we did:

/**
 * Process callback for the 'shopify' shortcode.
 *
 * @see stem_wysiwyg_shortcode_info().
 */
function stem_wysiwyg_shortcode_shopify_process($attrs, $text) {
  // Prepare attributes for shopify embed.
  $attrs = shortcode_attrs(array(
    'embed_type' => 'product',
    'shop' => 'myshop.myshopify.com',
    'product_name' => '',
    'product_handle' => '',
    'buy_button_text' => t('Buy now'),
    'has_image' => 'true',
  ),
    $attrs
  );
 
  $shopify_embed = <<<EOF
  { Rest of embed code here, using values passed from the widget. }
EOF;
  return $shopify_embed;
}

 

As you can see, there's nothing too complex in there. We just merge the default values for our previously declared attributes, with the values entered alongside the token, to get the settings for each specific instance of the token.

After that, the complete markup is generated to include a full Shopify widget on the page, without having to allow editors to include "<script>" tags or any other dangerous elements. All of that, with complete WYSIWYG integration that works beautifully, and with a few lines of code. Let's use it:

shopify_widget_1

 

And that would render a functional Shopify widget:

shopify_widget_2

As you can see, it couldn't be easier to implement a plugin. If you're still here, that means you are actually looking for a solution like this. If not, it's always good to have it as an alternative in your toolset.