You can try this plugin:
/**
* Plugin Name: My Boxes Shortcode
* Author: birgire
* Author URI: http://wordpress.stackexchange.com/a/121423/26350
*/
add_action( 'plugins_loaded', array( 'My_Boxes_Shortcode', 'get_instance' ) );
if( ! class_exists( 'My_Boxes_Shortcode' ) )
{
class My_Boxes_Shortcode
{
static private $instance = NULL;
static public function get_instance()
{
if ( NULL === self::$instance )
self::$instance = new self;
return self::$instance;
}
public function __construct()
{
add_shortcode( 'boxes', array( $this, 'boxes_callback' ) );
add_shortcode( 'box', array( $this, 'box_callback' ) );
}
public function box_callback( $atts, $content )
{
$atts = shortcode_atts(
array(
'class' => 'box',
), $atts, 'my_box_shortcode' );
// input
$content = esc_textarea( trim( $content ) );
$atts['class'] = esc_attr( $atts['class'] );
// output
return sprintf( '<div class="%s">%s</div>', $atts['class'], $content );
}
public function boxes_callback( $atts, $content )
{
$atts = shortcode_atts(
array(
'class' => 'boxes',
'per_row' => '3',
), $atts, 'my_boxes_shortcode' );
// input
$content = str_replace( '<br />', ' ', $content );
$atts['class'] = esc_attr( $atts['class'] );
$atts['per_row'] = (int) $atts['per_row'];
// if per_row is not positive then force it to the default value.
$atts['per_row'] = ( 1 > $atts['per_row'] )? 3 : $atts['per_row'];
// split the content into boxes
$content = str_replace( '[/box]', '[/box]<!--x-->', $content );
$boxes = explode( '<!--x-->', do_shortcode( $content ) );
// output
$html = '';
// add a div wrapper around each row
if( 0 < count( $boxes ) )
{
$html .= '<div class="row-wrapper">';
foreach( $boxes as $i => $box )
{
$html .= $box;
// inject the row seperator
if( ( $atts['per_row'] - 1 ) === ( $i % $atts['per_row'] ) )
$html .= '</div><div class="row-wrapper">';
}
$html .= '</div>';
// remove the last row wrapper if it's empty
if( 1 === ( count( $boxes ) % $atts['per_row'] ) )
$html = substr( $html, 0, (-1) * strlen( '<div class="row-wrapper"> </div> ' ) );
}
return sprintf( '<!-- start boxes wrapper: -->
<div class="%s">%s</div>
<!-- end boxes wrapper -->', $atts['class'], $html );
}
} // end class
} // end if class ! exists
I guess there are much better ways to do this, but you can adjust the class to your needs.
Then this kind of shortcode setup:
[boxes class="myboxes" per_row="3"]
[box class="box first"]foo[/box]
[box]bar[/box]
[box]baz[/box]
[box class="box last"]quux[/box]
[/boxes]
should give you the following HTML:
<!-- start boxes wrapper: -->
<div class="myboxes">
<div class="row-wrapper">
<div class="box first">foo</div>
<div class="box">bar</div>
<div class="box">baz</div>
</div>
<div class="row-wrapper">
<div class="box last">quux</div>
</div>
</div>
<!-- end boxes wrapper -->