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:
- Split the nested
$content
(nested shortcodes) into single shortcodes withpreg_match_all
- Apply
do_shortcode
to each shortcode with a for loop - 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 thenav
data - Assign all the main content data to one array and the nav data to another
- 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="https://wordpress.stackexchange.com/questions/295847/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="https://wordpress.stackexchange.com/questions/295847/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="https://wordpress.stackexchange.com/questions/295847/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!