how to include other plugins css files in a shortcode?

I’ve coded a plugin that creates a shortcode, among other things. That shortcode expects a post_id parameter, it extracts the content of that post and it returns the filtered content of that post (e.g. it applies all filters so that any other shortcode in that content is done).
Here is a cut down version of the code:

public static function myshortcode($atts = null, $content = "")
{
  global $wp_filter, $merged_filters, $wp_current_filter, $post;

  if (is_array($atts))
    extract($atts, EXTR_OVERWRITE);

  $save_wp_filter = $wp_filter;
  $save_merged_filters = $merged_filters;
  $save_wp_current_filter = $wp_current_filter;
  $post = get_post($post_id);
  setup_postdata($post);
  $content = $post->post_content;
  $content = apply_filters('the_content', $content);        
  $content = str_replace(']]>', ']]>', $content);
  $wp_filter = $save_wp_filter;
  $merged_filters = $save_merged_filters;
  $wp_current_filter = $save_wp_current_filter;

  return $content;
}

All works great, except that other plugins shortcodes fail to include their own CSS files (e.g. Ultimate Addons for Visual Composer has its own CSS files to include, but it does not include them when called this way).
I suspect the problem is that when WP executes the “the_content” hook for my shortcode (not the one I call explicitly, but the hook that WP itself calls in the first place and that leads to my shortcode execution) it is already too late to include CSS files in the page (e.g. the <head> tag is already closed), and WordPress, at that point, has given a chance to include CSS files only to the plugins it sees as needed for the page (e.g. only my plugin, because the page initially contains only my shortcode).

I could use wp_enqueue_style to explicitly add the required third party CSS files, but the problem is I want to include them in a portable way. Today the problem is Ultimate Addons for VC, tomorrow will be Content Egg or something else. The best solution would be to call the function of WP which makes the decision about what are the required plugins for the page and calls them, handing that function the the actual content the page will have after my shortcode will be executed, instead of the content the page has before my shortcode is done.

Does such WP function exist?

EDIT:

For clarification after comments, yes, my shortcode is like [embed_page id=”123″], except I need it the other way around, e.g. [embed_post id=”123″] in a page. There’s no risk of looping because it is meant to be used only to embed posts into pages, and it’s not meant to be used in posts. And finally yes, the embedded content does include other third party shortcodes that should automatically enqueue their own CSS, JS, whatever.

EDIT AFTER THE RECEIVED ANSWERS:

The short answer is no, there doesn’t exist such function in WordPress. The long answer is below. I’d like to reward both @majick and @cjbj for their extensive and detailed answers, but I have to choose one…

3 s
3

I think you could get around this by pre-running the shortcodes on the page by applying the content filters before the header is output. This should allow any internal shortcodes run inside the included post to add any action hooks properly and thus any needed stylesheets/resources.

add_action('wp_loaded','maybe_prerun_shortcodes');
function maybe_prerun_shortcodes() {
    if (is_page()) {
        global $post; 
        if (has_shortcode($post->post_content,'embed_post')) {
            $content = apply_filters('the_content',$post->post_content);
        }
    }
}

EDIT The above will only enqueue stylesheets and resources that are enqueued within any shortcode calls in the embedded post (which, although it is better practice, is actually not done very often.) It should work for those cases because applying the filters to the current page content will run the embed shortcode which then applies filters for the embedded post (in your existing function.)

However, To get the stylesheets for a particular post you would have to fetch that particular post (which is why the iframe method works, well kind of, given the problems already discussed about that.)

My suggested workaround in that case is to store the style resources loaded on a post when the post is loaded…

add_filter('style_loader_tag','custom_store_style_tags',11,2)
function custom_store_style_tags($tag,$handle) {
    global $styletags; $styletags[$handle] = $tag;}
}

add_action('wp_footer','custom_save_style_tags');
function custom_save_style_tags() {
    global $embedstyles;
    if (is_single()) {
        global $styletags, $post; 
        update_post_meta($post->ID,'styletags',$styletags);
    } elseif (is_array($embedstyles)) {
        global $styletags;
        foreach ($embedstyles as $handle => $tag) {
            if (!array_key_exists($handle,$styletags)) {echo $tag;}
        }
    }
}

Then you can use the shortcode to retrieve the style tags to be retrieved and the second part of the above function will output them in the footer:

public static function myshortcode($atts = null, $content = "")
{
  // EXISTING CODE SNIPPED

  global $embedstyles;
  $embedstyle = get_post_meta($post_id,'styletags',true);
  if (is_array($embedstyle)) {
      if (!is_array($embedstyles)) {$embedstyles = array();}
      $embedstyles = array_merge($embedstyle,$embedstyles);
  }

  return $content;
}

And you could do the same for scripts via script_loader_tag filter and simply changing the variables to match…

Of course there is still the possibility some body classes may not be targeted by styles as already mentioned by @cjbj… or some scripts may initialize in the footer even if they are enqueued properly.

…and it does not account for inline styles/scripts printed (not enqueued) on the page yet… but it’s a good start… depends how far you want to go with it.

Leave a Comment