DiggingIntoWordPress

by Chris Coyier & Jeff Starr

Shortcode for Includes

Posted by on

One thing that WordPress doesn’t have the ability to do “out-of-the-box” is do includes, in the sense of including the content of one post into the content of another post directly in the post editor. For the umpteenth time around here, shortcodes to the rescue!

This issue came up while my co-worker Tim at Wufoo was documenting parts of the latest Wufoo API. Some of the API pages have areas on them that are exactly the same as other pages, for example, a chunk of navigation that links to other pages of documentation.

One possible way to deal with this is to make a special template just for these pages and include that chunk inside that template. This solution edges on the issue of template-bloat which I’ve been thinking a lot about lately. Creating a new template every time you need one little change is solving the problem with a sledgehammer rather than a scalpel.

The ideal solution is just to make a shortcode. You pass the ID of the post (in our case, page) that you want to include and the shortcode is replaced with that content. This is the usage, where XXXX would be the ID of the post:

[digwp_include postidparam=XXXX]

To make it work, we’ll add a fairly simple function to the functions.php file in our theme. The function will take the parameter, run a query for it, and return back the content if it finds any:

function digwp_includeContentShortcode($atts) {
  
  $thepostid = intval($atts[postidparam]);
  $output = '';

  query_posts("p=$thepostid&post_type=page");
  if (have_posts()) : while (have_posts()) : the_post();
    $output .= get_the_content($post->ID);
  endwhile; else:
    // failed, output nothing
  endif;
  wp_reset_query();

  return $output;

}

// USAGE
// In the post content, you can use [digwp_include postidparam="1234"]
// "1234" would be the WordPress ID of the Page you are trying to include

add_shortcode("digwp_include", "digwp_includeContentShortcode");

Now you can publish small modules of content, and include them on any Post/Page that needs them! I’d probably create a Page on your site called like “Includes” or “Modules” and post them as Pages with that as the Parent Page. That way you don’t clutter up the root and they all stay organized together.

Check out this graphic (click for full size), which hopefully will drive home the idea:

Random notes:

  • This is similar to the custom loop shortcode I previously published.
  • This may be good territory for a plugin rather than functions.php code as I have done.
  • Notice the function is properly prefixed.

28 Responses

  1. query_posts() should only be used for modifying the primary loop, not creating a secondary loop. get_posts() is what to use for that.

    Also that’s not how get_the_content() works (it’s first argument is the “more” text).

    Anyway, here’s the best code for this situation:

    function digwp_includeContentShortcode( $atts ) {
              $thepostid = (int) $atts['postidparam'];

              if ( ! $post = get_post( $thepostid ) )
                   return '<em>Invalid post/page ID.</em>';

              return apply_filters( 'the_content', $post->post_content );
    }

    • Jeff Starr

      Isn’t get_posts limited in what it can do? I use query_posts all the time for multiple loops with no problems whatsoever.

      • Alex is using get_post() (no plural) – which, for this job of pulling one explicit post from the database, seems like the best method to use.

        I guess a custom post_type (in WP3) would be a good place to put these “modules”, instead of jamming them into Pages.

      • No, as of WordPress 2.6, get_posts() uses the WP_Query class, i.e. it can accept all of the arguments that query_posts can.

        See the codex: http://codex.wordpress.org/Template_Tags/get_posts

        query_posts() modifies a ton of globals which affects all of the conditional tags (is_single() for example).

        get_posts() merely returns an array — it doesn’t modify any globals.

        • Jeff Starr

          Ah nice. That’s good to know. There is also a safe way to use query_posts by doing something like this:

          $posts = query_posts($query_string.'&cat=-40&offset=0');

          This avoids global modification by keeping the query string intact. So stuff like page navigation and conditional tags work with multiple loops.

        • No you’re misunderstanding.

          Say you’re on a single page (say your “About” page) and you want to display 5 posts in your sidebar.

          If you call query_posts( 'whatever' ), everything from there on will think you are no longer on your About page. Conditional tags is the example I used before, but lots lots more could be affected. wp_reset_query() fixes this I believe by putting back the old globals, but why mess with them in the first place?

          $posts = get_posts('cat=-40&offset=0');

        • The key thing about get_posts() is that it’s creating a new instance of WP_Query, and as Alex points out won’t affect your main page query/The Loop.

        • Jeff Starr

          If you call query_posts( ‘whatever’ ), everything from there on will think you are no longer on your About page

          Not so if you include the $query_string as in my previous comment. Works great on any page (including those with multiple loops).

          Also, wp_reset_query() is unnecessary for multiple loops running after the main loop. It’s only required after multiple loops running before the main/paginated loop.

          Certainly, get_posts looks a little easier, but either will work just fine.

        • I’m sorry Jeff, but you’re wrong.

          $query_string is merely the variables that have been set based on the current URL. If you then go on to overwrite some of those variables (for example by querying a post), the old value will no longer apply.

          This is easily proven using the following code, based on the code from the original post:

          var_dump( is_page( 'about' ) );
          $posts = query_posts( $query_string. "p=$thepostid&post_type=page" );
          var_dump( is_page( 'about' ) );

          If you visit your “About” page, you’ll get true and then false.

          This is exactly why there is a heading called “Important Note” on the documentation page for query_posts():

          http://codex.wordpress.org/Template_Tags/query_posts#Important_note

        • Jeff Starr

          After more testing I agree that Alex is correct that get_posts should be used for multiple loops on actual Page pages (such as an “About” page).

          query_posts will work on Page pages if you exclude the $query_string parameter, but as Alex says this may interfere with certain conditional tags.

    • Thanks for the improved code, that is much cleaner.

      I was a bit confused when I first started playing with it, because functions like get_the_title() you CAN just pass an ID and it will return the title of that post without having to do a loop. But with get_the_content() it doesn’t work that way. You also can’t use the_content() because that automatically echos and that would be bad. Long story short, the code that I ended up with above works fine, despite it being technically invalid.

      I like very much the idea of not having to run a loop or upset/reset the current query. The reason I’ve always leaned toward using query_posts just because of old habits and the fact that it always seems to work fine for me (and I don’t mind resetting the query afterwards, it’s just one line).

  2. You can also try this plugin:
    http://wordpress.org/extend/plugins/improved-include-page/
    Nice because you can also use php code in order to include content in your template.
    Olivier

  3. Seems this could come in handy for the 3.org Codex redesign ;-)

  4. You may not want to do this exact thing if you’re concerned about the duplicate content SEO issues. How I’ve gotten around this, in a way that actually improves PageRank flow, is to have it grab the_excerpt or the custom description meta and display it and a link to the post. It’s better to not repeat yourself, in programming terms and in SEO terms.

    I’d also second the notion that custom post types would be best for this, rather than “pages”.

    Alex’s arguments and the confusion of the wp_query system by two popular and successful WP devs perfectly demonstrates why WP should have a more modular loop system where template tags can be scoped to a particular instance of a query, rather than the assumption that everything is the global post. I know I’m talking out of my level of PHP, but I, and most WP developers I know of, run into that issue all the time.

  5. This is an interesting solution but seems to me it would be easier to add small snippets of code to a functions file and then call/place them into a post/page using a userfunction, like; <? userfunc_snippetname0(); ?>

    I use runPHP plugin to allow PHP on posts/pages.

    Granted, you can’t change the content from within WordPress admin but it seems a cleaner option than having a slew of extra pages.

    But for lengthier content, the shortcode option is nice.

    Thanks for sharing.

  6. That comment got hosed;

    1st paragraph should read;

    This is an interesting solution but seems to me it would be easier to add small snippets of code to a functions file and then call/place them into a post/page using a userfunction, like; <? userfunc_snippetname0(); ?>

  7. i might use this one day.. bookmarked!

  8. Acts7 July 9, 2010

    Here’s a goofy question.
    Why would this work if I preview the page.
    BUT, if I view the live version I get the following error:

    This webpage has a redirect loop.

    The webpage at http://grandfather.com/grandfatherwp/worldofwonder/wildflower-bloom-events/ has resulted in too many redirects. Clearing your cookies for this site or allowing third-party cookies may fix the problem. If not, it is possibly a server configuration issue and not a problem with your computer.

    Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects.

  9. Acts7 July 9, 2010

    Hmm working now:
    I had already deleted the page and created a new page
    SOLUTION:
    Even though I permenantly deleted the old page, I had to give it a new name.
    So I have PAGE1(source)
    Then PAGE2 (show source content)
    deleted PAGE2. got the loop error
    CREATED PAGE2-2 —- WORKING.

  10. Works like a dream, BUT and there’s always a but….

    I have Custom Fields which I would like to put the shortcode include in but shortcodes don’t run in custom fields. Any code I can add to the function.php to get the custom fields to work with shortcodes?

    I’m using Page.ly Multi-edit plugin to give the user a panel of content to update and some of these panels will just be includes of common content, but means with your solution they can still maintain that content. http://blog.page.ly/multiedit-plugin/ it’s this plugin that uses custom fields hence the need for the shortcodes to work.

    Any guidance please ?

  11. Could someone please assist me>??
    When I use the above, its grabbing the content. But it is not placing the html formatting around p tags etc like wordpress normally would.
    The original post has all the hard returns replaced with tags.
    However when I run this – it grabs all of the copy. But if there is no specific tag hardcoded – then all I get is one big run-on.

    • alchymyth August 30, 2010

      hi Acts7,
      try to use:
      $output .= apply_filters('the_content',get_the_content($post->ID));

      that will put the ‘formatting’ back on.

      • Alchymyth – THANKS A LOT.
        I had just come across a similar solution and I found your reply.

        return apply_filters( 'the_content', $post->post_content );

        Could you tell me – what the difference is between

        apply_filters( 'the_content', $post->post_content );

        and

        apply_filters('the_content',get_the_content($post->ID));

        Are these two identical? Or is there a difference in what they are pulling?

        • Take a look at the source of get_the_content(). ;)

          get_the_content() hides the content of password protected but also does some stuff with the “more” feature.

          Probably better to use get_the_content().

        • Thanks for clearing that up Alex

  12. //$output .= get_the_content($post->ID);
    $output .= the_content($post->ID);

    PLEASE PLEASE??? Someone explain this?
    I’ve searched. And when using “get_the_content” I get the raw content.
    When I use “the_content” I get what I want – content that gets html formatted.

    What is the functional difference? And why would you ever want it to not maintain its html formatting? Is that the difference? AND Is it safe to use “the_content” rather than “get_the_content”?

  13. Did anyone ever fix @Alex’s code? It doesn’t return just content, it includes the title.

Comments are closed. Contact us with any critical information. Thank you!

Code is poetry