WordPress Shortcodes – My Way

As anyone whose work in WordPress whose tried to create their own shortcodes knows, it can be a nuisance. Trying to come up with unique names for the shortcodes so as not to cause conflicts, supporting nested shortcodes, etc., etc. It can be a challenge.

Instead of using functions, however, I’ve started using enclosures and classes. Such a class itself registers shortcodes which it can have embedded. And to overcome the actual shortcode tag itself conflicting – I’ve found you can “namespace” those, too. Here’s an actual example:

<?php

namespace sunsport\shortcodes;

/**
 * This class provides the functionality for creating the HTML structures for
 * the frontpage tiles
 *
 * @author ncrause
 */
class Tiles {
    public function __construct() {
        add_shortcode('sunsport:tiles:create', array(&$this, 'create'));
        wp_enqueue_style('sunsport-tiles', '/css/tiles.css');
    }
    
    public function __destruct() {
        remove_shortcode('sunsport:tiles:create');
    }
    
    public function start($atts = array(), $content = null) {
        include __DIR__ . '/fragments/tiles/start.php';
    }
     
   public function create($atts = array(), $content = null) {
        extract(shortcode_atts(array(
            'href' => '/',
            'img' => '/img/image_missing.png'
        ), $atts));
        
        include __DIR__ . '/fragments/tiles/create.php';
    }
}

add_shortcode('sunsport:tiles:start', function($atts, $content) {
    $instance = new Tiles();
    
    return $instance->start($atts, $content);
});

So, what we have here is a shortcode “sunsport:tiles:start” which creates an instance of our class. That instantiation registers a new shortcode “sunsport:tiles:create”, which would be unavailable otherwise, thus we avoid have to check to make sure it’s properly enclosed in a parent “start” shortcode, and we gracefully deregister it at the end of the run.

It’s probably worth include the “fragments/tiles/start.php” file just for reference:

<div class="sunsport-tiles">
    <?= do_shortcode($content) ?>
    <div class="clear"></div>
</div>

And here’s the actual usage:

[sunsport:tiles:start]
  [sunsport:tiles:create img="img/tiles/photo_button01.png" href="/products"]Vinyl Lettering[/sunsport:tiles:create]
  [sunsport:tiles:create img="img/tiles/billboard_photo.png"]Billboard[/sunsport:tiles:create]
  [sunsport:tiles:create img="img/tiles/photo_button03.png"]The Company[/sunsport:tiles:create]
[/sunsport:tiles:start]

There’s is one word of warning – do not do a naming convension like this:

  • parent shortcode – sunsport:tiles
    • child shortcode – sunsport:tiles:create

The child shortcode will never fire. For some reason, it seems WordPress doesn’t actually read in the full shortcode in this scenario – instead of “sunsport:tiles:create” firing, WordPress will simple re-run “sunsport:tiles”.

That caveat aside, I find this feels a lot cleaner and less collision-prone than other examples I’ve seen.

Another “WTF?!” IE9 Bug

With Internet Explorer’s complete lack of support for any of the neat and useful CSS styles, one always has to revert to Microsoft’s disgusting “filter” hack. The filters don’t take in very many useful parameters (such as color stops in gradients) and disable text anti-aliasing. 

But here’s something you probably really didn’t see coming – under IE9 only (this doesn’t affect IE8), filters completely cripple events. If you define any mouse over or even click events, they will not fire.

This created a situation where I could no longer use a horizontal sliding accordion, because IE doesn’t support text rotation and uses a … you guessed it … filter.

I hate Microsoft so much … so very very much …

Lithium Problem on Rackspace

Today I came across a situation where I was deploying a PHP-based webapp written in Lithium and running on a Rackspace cloud site. In my scenario, I noticed 2 symptoms (appearing differently, but having the same cause).

  1. if the Lithium app is a subdirectory of another webapp (in my example, the main site is WordPress), you will always get a WordPress “Oops! The page you are looking for does not exist.” error.
  2. if the Lithium app is in the root, you will get an “Internal Server Error” page.

As it turns out, the problem is the .htaccess file included with Lithium.

I don’t think there’s anything wrong with the .htaccess per se, but under Rackspace you seem to have to include the “RewriteBase” directive.

So, as a result, you must edit all 3 .htaccess files in your Lithium project thus:

  • /.htaccess – RewriteBase /
  • /app/.htaccess – RewriteBase /app/
  • /app/webroot/.htaccess – RewriteBase /app/webroot/

If your webapp is a subdirectory, this subdirectory name will need to prepended to RewriteBase path:

  • /.htaccess – RewriteBase /subdir/
  • /app/.htaccess – RewriteBase /subdir/app/
  • /app/webroot/.htaccess – RewriteBase /subdir/app/webroot/

And presto, it now magically works!

XML Prolog in PHP

Okay, I get it. For some PHP programmers, the notion of the PHP short-code being the same as the open and close of the XML prolog is enough to throw you into a foaming rage.

However – of all the “solutions” I’ve seen, this has to be the worst:

<?php echo ("<"); ?>?xml version="1.0" encoding="UTF-8"?<?php echo ">"; ?>

Come on now, guys … really?! That’s a good solution, is it? Hey, here’s an easy one, just for all of you who get confused and don’t like using short-codes:

<?= '<?xml version="1.0" encoding="UTF-8"?>' ?>

Geez, guys, seriously, it’s not that big of a deal!

Lithium Filters – What’s the Point?

I’ve used Lithium for 2 projects now, and I have one major issue – I honestly don’t see the benefit of “filters” vs. “callback” methods.

I understand that conceptually it’s supposed to “decouple” the filter logic from the model itself. However, every example of applying filters to Models show the filter being applied to an individual model directly using the 2 methods below:

<?php
class MyModel extends \lithium\data\Model {
    public static function __init() {
        // ...
        static::applyFilter('save', function(...) {
            // ...
        });
    }
} 

or

<?php
class MyModel extends \lithium\data\Model {
} 
MyModel::applyFilter('save', function(...) {
    // ...
});

In my humble opinion, neither of this is “decoupling”, since the actual code for the filters exists directly within the model itself anyway.

One would think you can overcome this my applying the filter to the “Model” class directly – WRONG! The filter never fires.

So, since I have a filter which is shared, I created a method in some other class which my individual filters call. How is this any different than simply having a callback which invokes the external method, too?

No – to me filters are thus-far fairly useless in the Model context (I’ve successfully used it on the Dispatcher).