2

I've been trying to make a shortcode function that outputs some html for a tabbed gallery, and after many days I haven't gotten far. Here's an example of the shortcode:

[thumbTab id="test1" nav="top" shadow="off"]
    [imgTab id="1" src="source1" width="1280" height="720" ]
    [vidTab id="2" src="source2" width="1280" height="720" ]
    [htmlTab id="4" preview_src="source3"] Other HTML stuff in here [/html]
[/thumbTab]

I want the returned html to be something like this (simplified for example):

<div>
    <nav>
        <img .../>    /* 1st nested shortcode */
        <img.../>     /* 2st nested shortcode */
        <img.../>     /* 3st nested shortcode */
    </nav>
    <div>
        <img.../>         /* 1st nested shortcode */
        <video.../>       /* 2st nested shortcode */
        <section.../>     /* 3st nested shortcode */
    </div>
</div>

Therefore I need the nested shortcodes to output data into a nav tag and into a div element. The problem with this is (I think) it requires a variable accessible to the main and nested shortcode functions, which is what I've been stuck on.

My attempt at the shortcode plugin is below (fatal error upon attempted install). I'd appreciate a correction to my existing code or perhaps advice on a better approach to the problem.

add_shortcode('thumbTab', 'thumbTab_func');

function thumbTab_func( $atts, $content = null ) {  
    $a = shortcode_atts( array(
        'id' => 'theID',
        'nav' => 'top',
        'shadow' => 'off'
    ), $atts );

    class thumbTab_class
    {
        //Array to hold tabbed content
        $tabCon = array();

        //Array to hold navigation tabs
        $navCon = array();

        //run nested shortcodes
        do_shortcode( $content, $ignore_html = false); 

        //combine $navCon array elements into the navigation tab html
        function navCreate(){
            $navReturn = '<div><nav><ul>';
            for ($i = 0; $i < count($this->navCon) ; ++$i) {
                $navReturn .= $this->navCon["$i"];
            }
            $navReturn .= '</ul></nav></div>';
            return $navReturn;
        }
        //combine $tabCon array elements into tabbed content html
        function tabCreate(){
            $tabReturn = '';
            for ($i = 0; $i < count($this->tabCon) ; ++$i) {
                $tabReturn .= $this->tabCon["$i"];
            }
            $tabReturn .= '</ul></nav></div>';
            return $tabReturn;
        }

        // Assigning processed html code to variable
        $thumbReturn = '<div id="'.$a["id"].'" class="thumbTAB">';
        $thumbReturn .= navCreate();
        $thumbReturn .= tabCreate();
        $thumbReturn .= '</div>';

    }

    $thumbTab_class = new thumbTab_class();

    // output processed html
    return $thumbTab_class->thumbReturn; 

}

//One nested shortcode function for testing
add_shortcode('imgTab', 'imgTab_func');

function imgTab_func( $atts ) { 
    $a = shortcode_atts( array(
        'id' => 'no_order',
        'src' => 'no_source',
        'width' => '100%',
        'height' => 'auto'
    ), $atts );

    //create baseline structure
    $img = '<img src="'.$a["src"].'" width="'.$a["width"].'" height="'.$a["height"].'">';

    //assign tabbed html string to tabCon array using the id number to place in order
    $this->tabCon["a['id']"] = '<section class="tabPage tabPage_'.$a["id"].'>'.$img.'</section>';

    //same for nav html string
    $this->navCon["a['id']"] = '<li class="sDS tabHeader_'.$a["id"].'">'.$img.'</li>';
}
1

After a bit of lateral thinking and lots of testing I was able to come up with a solution that negated the need for scope crossing variables, or using a global variable for that matter.

The code goes like this:

  1. Split the nested $content(nested shortcodes) into single shortcodes with preg_match_all
  2. Apply do_shortcode to each shortcode with a for loop
  3. Each nested shortcode function returns a string which will be split into 2 strings by another preg_match function, with the 1st being the main content data and the 2nd being the nav data
  4. Assign all the main content data to one array and the nav data to another
  5. Then it's just a matter of processing the two arrays and returning the string!

I've supplied a slimmed down version of my code below. The trick for all this to work is having a robust preg_match expression to separate the shortcodes, so that they won't break when fed irregular user data.

<?php
add_shortcode('tabs', 'tabs_func');
//Main shortcode function
function tabs_func( $atts, $content = null ) {  
    $a = shortcode_atts( array(
        'id' => '',
    ), $atts );

    //array to store nav data
    $navCon = array();

    //array to store main content data
    $tabCon = array();

    /* 
    Preg_match_all splits $content into predefined individual shortcodes [imgTab], [vidTab] and [htmlTab][/htmlTab]

    A basic explanation of the expression I used. If you want a more detailed breakdown, plug the expression below into "https://regexr.com/"

        (?<!\[)    <= checks if there's a square bracket behind the proceeding match, to avoid matching escaped shortcode
        \[    <= matches the opening bracket
        (?:(?:imgTab|vidTab).*(?<!\])\](?!\])|htmlTab[\S\s]*?\[\/htmlTab\](?!\]))    <= can't break this into parts easily, but in essence this part tries to match a string which next part goes 'imgTab', 'vidTab', or 'htmlTab' (although the 'htmlTab' is put in it's own non-capture group as it has to deal with possible linebreaks). It then matches the remaining characters until it gets to a closing square bracket (or in the case of htmlTab, '[/htmlTab]. It then checks that there aren't two closing brackets next to each other before finishing the match. 
    */
    preg_match_all( '@(?<!\[)\[(?:(?:imgTab|vidTab).*(?<!\])\](?!\])|htmlTab[\S\s]*?\[\/htmlTab\](?!\]))@', $content, $matches );   

    $matchLI = 1;
    //loops through each of the shortcode matches
    foreach ($matches[0] as $match) {

        //runs current shortcode and assigns to variable
        $match_processed = do_shortcode($match);

        /*
        This expression is much more simple than the last one :D

            ([\S\s]+)    <= this capture group basically takes all the characters that aren't matched by the next part of the expression. This capture group contains the main content
            (<[\S\s]+>)    <= this capture group matches a 'less-than' character, a string of any characters and finally a 'greater-than' character. Since this expression is greedy it won't false match any 'greater-than' symbols in the first capture group, as it will look for the last 'greater-than' in the string
        */
        preg_match( '@([\S\s]+)(<[\S\s]{4,}>)@', $match_processed, $navMatch );

        //assigns nav data of current shortcode to the $navCon array, using the $matchLI value to index it
        $navCon[$matchLI] .= "<li class='nav_NUM_" . $matchLI . "'>" . $navMatch[2] . "</li>";

        //assigns main content data of current shortcode to the $tabCon array, using the $matchLI value to index it
        $tabCon[$matchLI] = "<section class='content_NUM_" . $matchLI . "'>" . $navMatch[1] . "</section>";
        }

        //increments the value for the next loop
        $matchLI++;

    }

    //constructing html in $tabReturn variable
    $tabReturn = "<div id='" . $a['id'] . "'>";
    $tabReturn .= "<nav><ul>";
    //loops through and assigns content of $navCon array to $tabReturn
    foreach ($navCon as $navElement) {
        $tabReturn .= $navElement;
    }
    $tabReturn .= "</ul></nav>";
    //loops through and assigns content of $tabCon array to $tabReturn
    foreach ($tabCon as $tabElement) {
        $tabReturn .= $tabElement;
    }
    $tabReturn .= "</div>";

    //finished html string is returned. Mission complete!
    return $tabReturn;

}

add_shortcode('imgTab', 'imgTab_func');
function imgTab_func( $atts ) { 
    $a = shortcode_atts( array(
        'src' => '',
        'width' => '100%',
        'height' => 'auto'
    ), $atts );

    //First img element is the main content, and the second is the nav preview 
    return "<img src='" . $a['src'] . "' width='" . $a['width'] . "' height='" . $a['height'] . "'>
    <img src='". $a['src'] . "' width='" . $a['width'] . "' height='" . $a['height'] . "'>";


}

add_shortcode('vidTab', 'vidTab_func');
function vidTab_func( $atts ) { 
    $a = shortcode_atts( array(
        'poster' => '',
        'src' => '',
        'width' => '100%',
        'height' => 'auto'
    ), $atts );

    //vid element is the main content, and the img is the nav preview 
    return "<video width='" . $a['width'] . "' height='" . $a['height'] . "' poster='" . $a['poster'] . "' controls>
        <source src='" . $a['src'] . "' type='video/mp4'>
    </video>
    <img src='". $a['poster'] . "' width='" . $a['width'] . "' height='" . $a['height'] . "'>";

}

add_shortcode('htmlTab', 'htmlTab_func');
function htmlTab_func( $atts, $content = null ) {   
    $a = shortcode_atts( array(
        'poster' => '',
        'width' => '100%',
        'height' => 'auto'
    ), $atts );

    //$content is the main content, and the img is the nav preview.
    return $content . "<img src='". $a['poster'] . "' width='" . $a['width'] . "' height='" . $a['height'] . "'>";
}

And the shortcode that the user would type into the text editor:

[tabs id="test1"]

//any number and combination of these elements

[imgTab src="imgTab.jpg" width="100" height="100"]

[vidTab poster="vidTab.jpg" src="vidTab.mp4" width="128" height="72"]

[htmlTab poster="htmlTab.jpg" width="100" height="100"] 
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam scelerisque maximus neque gravida gravida.</p>
[/htmlTab]

[/tabs]

And the output should be something like this:

<div id='test1'>
    <nav><ul>
        <li class='nav_NUM_1'> 
            <img src='imgTab.jpg' width='100' height='100'>
        </li>
        <li class='nav_NUM_2'> 
            <img src='vidTab.jpg' width='128' height='72'>
        </li>
        <li class='nav_NUM_3'> 
            <img src='htmlTab.jpg' width='100' height='100'>
        </li>
    </ul></nav>
    <section class='content_NUM_1'>
        <img src='imgTab.jpg' width='100' height='100'>
    </section>
    <section class='content_NUM_2'>
        <video width='128' height='72' poster='vidTab.jpg' controls>
            <source src='vidTab.mp4' type='video/mp4'>
        </video>
    </section>
    <section class='content_NUM_3'>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam scelerisque maximus neque gravida gravida.</p>
    </section>
</div>

Ready to be processed by a javascript tab function!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.