get attributes/part of the gallery shortcode

The task

It’s not as easy, as it looks at a first glance. The main problem is, that you can define your own shortcode and simply override the default. Actually that’s what some themes are doing (blame you ThemeForest authors!). In this case, simply hooking into post_gallery won’t work in every case.

Drawbacks for post_gallery

The problem is that both the shortcode, as well as the core callback can be overridden and therefore every callback will simply be knocked out.

Drawbacks for the_content

If you’re switching to another filter and take the raw output, then you’ll have to deal (another time) with the Regex. This is slow and mostly won’t be simple.

So what to do now? Easy. Interact with the global $shortcode_tags. It holds the callback as second argument, so it’s actually not too hard to determine in which case we are. Then we can simply switch on demand. This way we have a fine balance between reliability and performance.

Build a base plugin

Here’s a plugin that defines an abstract class (one that must get extended in order to work). There’re three things that need definition in the child class:

  • $part – the part you want to retrieve
  • $type – the type of match you need. Valid are digit/alphanumeric/alpha
  • process_atts() – the method that processes your output – whatever you want to do with the result

Just upload this via (S)FTP into your plugins folder and activate.

<?php
/** Plugin Name: (#70451) »kaiser« Get Gallery attributes (Base) */
if ( ! class_exists( 'wpse70451_extract_gallery_atts' ) )
{

abstract class wpse70451_extract_gallery_atts
{
    public $atts  = array();

    public $error;

    // Must get defined in extending class
    public $part;
    public $type;

    public static function init()
    {
        is_null( self :: $instance ) AND self :: $instance = new self;
        return self :: $instance;
    }

    public function __construct()
    {
        ! isset( $this->part ) AND new WP_Error();

        add_action( 'loop_start', array( $this, 'error_handler' ) );

        // The value of the array is the callback fn name
        // If it's the default, then we're on the safe side,
        // as the core fn is no pluggable and can't get overridden.
        if ( 'gallery_shortcode' === $GLOBALS['shortcode_tags'] )
        {
            add_filter( 'post_gallery', array( $this, 'get_gallery_atts' ), 0, 2 );
        }
        // Else we have to go with a slower regex
        else
        {
            add_filter( 'the_content', array( $this, 'get_gallery_ids' ), 0 );
        }
    }

    public function error_handler()
    {
        if (
            ! in_array(
                 $this->type
                ,array(
                     'digit'
                    ,'alphanumeric'
                    ,'alpha'
                 )
            )
            OR ! empty( $this->type )
        )
            return new WP_Error( 
                 'invalid_type'
                ,__( 'Invalid type set.', 'wpse70451_textdomain' )
                ,__FILE__
            );
    }

    public function __toString()
    {
        $is_error = $this->error;

        if ( ! is_wp_error( $is_error ) )
            return;

        // No error message for Guests or Subscribers
        // Assuming that no one has activated caching plugins when debugging
        // and not set WP_DEBUG to TRUE on a live site
        if ( 
            ! is_user_logged_in()
            AND ! current_user_can( 'edit_posts' ) 
            AND ( ! defined( 'WP_DEBUG' ) OR ! WP_DEBUG )
        )
            return '';

        // Error output for development
        return "{$is_error->get_error_message( 'invalid_type' )}: {$is_error->get_error_data()}";
    }

    public function get_gallery_ids( $content )
    {
        $pattern = get_shortcode_regex( $content );
        preg_match_all( "/{$pattern}/s", $content, $matches );
        $atts = explode( " ", array_shift( $matches[3] ) );
        foreach ( $atts as $att )
        {
            if ( strstr( $att, $this->part ) )
                break;
        }
        preg_match_all( $this->get_regex( $this->type ), trim( $att ), $atts );
        $this->atts = array_filter( $atts );

        return $content;
    }

    // Build pattern
    public function get_regex( $type )
    {
        switch ( $type )
        {
            case 'digit' :
                $pattern_atts="/(\d*)/";
                break;

            case 'alphanumeric' :
                $pattern_atts="/([A-Za-z0-9]*)/";
                break;

            case 'alpha' :
                $pattern_atts="/([A-Za-z]*)/";
                break;

            default :
                // Add a method name `get_pattern()` to the extending class
                // to work with a custom regex pattern.
                if (  method_exists( $this, 'get_pattern' ) )
                {
                    $pattern_atts = $this->get_pattern();
                    break;
                }
                $pattern_atts = $this->get_regex( 'alphanumeric' );
                break;
        }

        return $pattern_atts;
    }

    public function get_gallery_atts( $content, $atts )
    {
        $this->atts = $atts[ $this->part ];

        // Allow overrides to trigger
        // at this point we already got what we need
        return $content;
    }

    // Must get defined in extending class
    public abstract function process_atts() {}
} // END Class

} // endif;

Handle a task, get a child

Here you see the actual processing plugin. First it hooks itself statically to the init hook and then runs the parents __construct() method to retrieve the attributes.

Then you need to define what attributes you want to retrieve (see the classes properties $part and $type, which already default to what you asked for).

The last two decisions you need to make are

  1. What do I want to do with my result? See process_atts()
  2. When do you want to output the result. See __construct() and where process_atts() is hooked in.

It’s as easy as that.

If you need a custom regex, just add a method named get_regex() to your extending class and return your custom pattern. Then set the $type to and empty string '' and you’re ready to go.

<?php
/** Plugin Name: (#70451) »kaiser« Get Gallery excludes */

if ( ! class_exists( 'wpse70451_extract_gallery_excludes' ) )
{
    add_action( 'init', array( 'wpse70451_extract_gallery_excludes', 'init' ) );

final class wpse70451_extract_gallery_excludes extends wpse70451_extract_gallery_atts
{
    public static $instance;

    public $part="exclude";

    public $type="digit";

    public static function init()
    {
        is_null( self :: $instance ) AND self :: $instance = new self;
        return self :: $instance;
    }

    public function __construct()
    {
        parent :: __construct();

        // Example hook: `loop_end`
        add_action( 'loop_end', array( $this, 'process_atts' ) );
    }

    public function process_atts()
    {
        $markup = '';
        // Do something with $this->atts;
        return print $markup;
    }
} // END Class

} // endif;

Alter what you need and then again: Just upload this via (S)FTP into your plugins folder and activate.