Can certain (site-crashing) limitations on WP_Query in shortcode be overcome?

In producing a relatively complex plugin that outputs a lot of html via either widget, inserted function in a template, or shortcode, I encountered a problem, or set of limitations, that applies only to the shortcode version, when used in post content.

The plugin outputs a table that is constructed via WP_Query. Until this new stumbling block, there wasn’t anything I could output via a widget or template that I could not also produce in the shortcode.

To condense a lot of code into a a simple statement of the problem, the $query_args array includes a “cat” argument:

So, something like:

$query_args = array(

    'order'             => $order,
    'orderby'           => $orderby,
    'showposts'         => $number_posts,
    'post_status'       => 'publish',
    'cat'               => $cat_id,
    'category__not_in'  => $exclude_array,
    'category__in'      => $include_array,
    'date_query'        => $date_query,

) ;

$my_query = new WP_Query( 
        apply_filters( 'cks_lpa_table_query_sc' , $query_args ) 
        ) ;

if ( $my_query->have_posts() ) {

//assemble and output the table

In normal uses of the plugin, or the loop-based table that it produces, the key ‘cat’ always has as value a category (or list of categories) identified by category ID. If ‘cat’ is left empty in widgets or template function, the functions produce a loop of posts according to whatever other parameters, but if ‘cat’ is left empty (or isn’t supplied at all) in post or page via shortcode (so within the main loop), the result is a rather catastrophic site crash – and not even with a fast “fatal error,” but instead with a slow-to-arrive connection reset error (ERR_CONNECTION_RESET).

The purposes achieved by allowing for “no ‘cat'” aren’t critical to the main uses of the plug-in, and keeping users from crashing their sites this way isn’t hard, but I still wonder if there is some other factor I should be considering, or some way to circumvent the issue.

I’m also wondering exactly what is going on underneath the hood – so any tips on investigating the problem with inspection tools would be great.

EDIT: See own answer below.

Solutions Collecting From Web of "Can certain (site-crashing) limitations on WP_Query in shortcode be overcome?"

Oops – too late to withdraw the question, I guess – but realized while out taking a walk that the answer was obvious: The query within the main query creates an infinite loop, since the post containing the shortcode would be within the main loop, so opens a hole in the universe etc.

Will be examining ways to deal with and isolate the problem/danger…

EDIT: Just want to note specifically, in addition, that I believe the problem has nothing to do with the specific $query_args key or with shortcodes as such. As I just verified, the problem is placing a post whose content would include a version of the post itself (containing a version of the post itself, and so on) – thus the “reset overload” rather than a fatal error.

To solve this problem or avoid this possible danger in the future, I think I need to add a sub-routine that excludes any offending post, or any offending post content, from whatever loop-within-a-loop the plug-in happens to output. Since I’ve taken up WordPress Dev’s space exploring this (initially misstated) problem, I’ll post a more complete answer, code included, if/when I can.

ADDENDUM: SOLUTIONS

First, a note: I now see that, under prior versions of the plug-in even before I started fiddling with a range of $query_args, an inadvertent site-crash via infinite-looping was always a danger, presuming the right wrong move by some user.

I think the easiest and simplest solution is just to exclude all posts containing the shortcode from the loop-within-the-loop. Inserting the following within the new query loop or loop-within-the-loop (after $my_query->the_post() ; ), assuming a shortcode “tag” of “add_loopy_table,” does it:

  //$post was already here for other purposes, but anyway we need it
  global $post ;

  if ( has_shortcode( $post->post_content, 'add_loopy_table' ) ) {

      continue ;

  } 

The above – or a slightly augmented version of it including excerpts, for rare cases in which shortcodes in excerpts are activated – is the solution I’m at this point intending to apply, for reasons I’ll get to.

Another alternative would be to allow inclusion of the potentially infinitely offensive posts in whatever loop-within-a-loop, but to modify their content prior to display.

WordPress does include a strip_shortcodes() function that allows us to write a function like the following one, also employing has_shortcode(), but called when the loopy table gets to post content within its query:

/***
 *AVOID INFINITE LOOPS BY STRIPPING
 *Strips shortcodes from potentially infinitely loopy posts
 *With message indicating deed has been done
***/
function ck_vs_infinity( $content ) {

    if ( has_shortcode( $content, 'add_loopy_table' ) ) {

         $content = '<p>Shortcodes removed to avoid danger of site crash.</p>' . 
             strip_shortcodes( $content ) ;

    }

    return $content ;

 }

The reason that in this approach we don’t remove the known-dangerous shortcode and leave any others alone is that strip_shortcodes() doesn’t accept parameters narrowing its strip. It’s full shortcode-Monty or nothing as far as strip_shortcodes() is concerned. That also seems to mean that oEmbedded tweets and videos will be ruined, apparently because they rely on an internalized shortcode functionality.

To target the specific code (and variations) precisely, we’d need to parse the content, perhaps using some variation on WordPress’s own regex which you can peruse for your pleasure here: https://core.trac.wordpress.org/browser/tags/4.5.3/src/wp-includes/shortcodes.php#L570 A probably very reliable version could be written for the specific shortcode “tag” (with or without arguments), and with massaged and messaged content returned. The function would look like the stripper above, but with a preg_replace() on $content instead of strip_shortcodes().

Alternatively, we could have some other alternatives or chain of alternatives available for when the dangerous code appears: Force display of excerpt unless also dangerous, whole or partial replacement with animated infinity sign while a “Nearer My God to Thee” audio plays… That kind of thing.

The potential drawback to such solutions, special effects notwithstanding, would involve possible concern over even edgier cases in which a user wanted to include posts containing not-actually-infinitely-loopy tables.

To enable these users, I think I’d either have to be able to test whether the potentially infinitely-loopy table was really a bad one, or introduce a settings option. No practical as well as user-proof way of performing the test occurs to me, and anything requiring the user to follow instructions, or check or uncheck a box, etc., and possibly relying on a new taxonomy or post meta or more, still probably risks accidental inclusions of an actually infinitely-loopy table along with other loose ends, and altogether represents an investment of time and effort on something that may not ever make a difference to anyone.

Maybe someone who has for some reason read this far has a bright and simple additional idea to share. For now, I’m leaning to the first, “has_shortcode/continue” version, because it’s simple and seems most user-proof: Posts that display potentially infinitely loopy tables won’t be displayed, period. I think that’ll be the rule, and, until and unless someone (including possibly me) speaks up in favor of special exceptions, I’ll probably leave things that way.