Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I'm writing a plugin that creates a custom post type named "my_plugin_lesson":

$args = array (
    'public' => true,
    'has_archive' => true,
    'rewrite' => array('slug' => 'lessons', 'with_front' => false)
);
register_post_type ('my_plugin_lesson', $args);

The custom post type has an archive, and the URL of the archive is:

http://example.com/lessons

I want to customize the look of this archive; I want to list the posts in a table format, rather than the standard WordPress blog post archive. I understand that a custom archive template could be created in the theme by making the archive-my_plugin_lesson.php file; however, I would like the plugin to work with any theme.

How can I alter the content of the archive page without adding or modifying theme files?

Edit: I understand that I could use the archive_template filter hook. However, all this does is replace the theme template, which still needs to be theme-specific. For example, just about every theme template will need the get_header, get_sidebar, and get_footer functions, but what should the id of the content <div> be? This is different in every theme.

What I would like to do is to replace the content itself with my own content, and use that in place of the archive page for my custom post type.

share|improve this question

5 Answers 5

You can use the archive_template hook to process the content of a theme's archive template, by using the scheme below, but obviously you'll only ever be able to process a fraction of the themes out there, given that a template can basically contain any old thing.

The scheme is to load the template into a string ($tpl_str) in the archive_template filter, substitute your content, include the string (using the trick eval( '?>' . $tpl_str );), and then return a blank file so that the include in "wp-includes/template-loader.php" becomes a no-op.

Below is a hacked version of code I use in a plugin, which targets "classic" templates that use get_template_part and is more concerned with processing single templates than archive, but should help you get started. The setup is that the plugin has a subdirectory called "templates" which holds a blank file ("null.php") and content templates (eg "content-single-posttype1.php", "content-archive-postype1.php") as well as a fall back template "single.php" for the single case, and utilizes a custom version of get_template_part that looks in this directory.

define( 'MYPLUGIN_FOLDER', dirname( __FILE__ ) . '/' );
define( 'MYPLUGIN_BASENAME', basename( MYPLUGIN_FOLDER ) );

add_filter( 'single_template', 'myplugin_single_template' );
add_filter( 'archive_template', 'myplugin_archive_template' );

function myplugin_single_template( $template ) {
    static $using_null = array();

    // Adjust with your custom post types.
    $post_types = array( 'posttype1', );

    if ( is_single() || is_archive() ) {
        $template_basename = basename( $template );
        // This check can be removed.
        if ( $template == '' || substr( $template_basename, 0, 4 ) == 'sing' || substr( $template_basename, 0, 4 ) == 'arch' ) {
            $post_type = get_post_type();
            $slug = is_archive() ? 'archive' : 'single';
            if ( in_array( $post_type, $post_types ) ) {
                // Allow user to override.
                if ( $single_template = myplugin_get_template( $slug, $post_type ) ) {
                    $template = $single_template;
                } else {
                    // If haven't gone through all this before...
                    if ( empty( $using_null[$slug][$post_type] ) ) {
                        if ( $template && ( $content_template = myplugin_get_template( 'content-' . $slug, $post_type ) ) ) {
                            $tpl_str = file_get_contents( $template );
                            // You'll have to adjust these regexs to your own case - good luck!
                            if ( preg_match( '/get_template_part\s*\(\s*\'content\'\s*,\s*\'' . $slug . '\'\s*\)/', $tpl_str, $matches, PREG_OFFSET_CAPTURE )
                            || preg_match( '/get_template_part\s*\(\s*\'content\'\s*,\s*get_post_format\s*\(\s*\)\s*\)/', $tpl_str, $matches, PREG_OFFSET_CAPTURE )
                            || preg_match( '/get_template_part\s*\(\s*\'content\'\s*\)/', $tpl_str, $matches, PREG_OFFSET_CAPTURE )
                            || preg_match( '/get_template_part\s*\(\s*\'[^\']+\'\s*,\s*\'' . $slug . '\'\s*\)/', $tpl_str, $matches, PREG_OFFSET_CAPTURE ) ) {
                                $using_null[$slug][$post_type] = true;
                                $tpl_str = substr( $tpl_str, 0, $matches[0][1] ) . 'include \'' . $content_template . '\'' . substr( $tpl_str, $matches[0][1] + strlen( $matches[0][0] ) );
                                // This trick includes the $tpl_str.
                                eval( '?>' . $tpl_str );
                            }
                        }
                    }
                    if ( empty( $using_null[$slug][$post_type] ) ) {
                        // Failed to parse - look for fall back template.
                        if ( file_exists( MYPLUGIN_FOLDER . 'templates/' . $slug . '.php' ) ) {
                            $template = MYPLUGIN_FOLDER . 'templates/' . $slug . '.php';
                        }
                    } else {
                        // Success! "null.php" is just a blank zero-byte file.
                        $template = MYPLUGIN_FOLDER . 'templates/null.php';
                    }
                }
            }
        }
    }
    return $template;
}

function myplugin_archive_template( $template ) {
    return myplugin_single_template( $template );
}

The custom get_template_part:

/*
 * Version of WP get_template_part() that looks in theme, then parent theme, and finally in plugin template directory (sub-directory "templates").
 * Also looks initially in "myplugin" sub-directory if any in theme and parent theme directories so that plugin templates can be kept separate.
 */
function myplugin_get_template( $slug, $part = '' ) {
    $template = $slug . ( $part ? '-' . $part : '' ) . '.php';

    $dirs = array();

    if ( is_child_theme() ) {
        $child_dir = get_stylesheet_directory() . '/';
        $dirs[] = $child_dir . MYPLUGIN_BASENAME . '/';
        $dirs[] = $child_dir;
    }

    $template_dir = get_template_directory() . '/';
    $dirs[] = $template_dir . MYPLUGIN_BASENAME . '/';
    $dirs[] = $template_dir;
    $dirs[] = MYPLUGIN_FOLDER . 'templates/';

    foreach ( $dirs as $dir ) {
        if ( file_exists( $dir . $template ) ) {
            return $dir . $template;
        }
    }
    return false;
}

For completeness here's the fall back "single.php", that uses the custom get_template_part:

<?php
get_header(); ?>

    <div id="primary" class="content-area">
        <div id="content" class="clearfix">
            <?php while ( have_posts() ) : the_post(); ?>

            <?php if ( $template = myplugin_get_template( 'content-single', get_post_type() ) ) include $template; else get_template_part( 'content', 'single' ); ?>

                <?php
                    // If comments are open or we have at least one comment, load up the comment template
                    if ( comments_open() || '0' != get_comments_number() ) :
                        comments_template();
                    endif;
                ?>

            <?php endwhile; ?>

        </div><!-- #content -->
    </div><!-- #primary -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>
share|improve this answer

What you need is hooking template_include filter and selectively load your template inside plugin.

As a good practice, if you plan to distribute your plugin, you should check if archive-my_plugin_lesson.php (or maybe myplugin/archive-lesson.php) exists in theme, if not use the plugin version.

In this way is easy for users replace the template via theme (or child theme) without edit the plugin code.

This is the method used by popular plugins, e.g. WooCommmerce, just to say one name.

add_filter('template_include', 'lessons_template');

function lessons_template( $template ) {
  if ( is_post_type_archive('my_plugin_lesson') ) {
    $theme_files = array('archive-my_plugin_lesson.php', 'myplugin/archive-lesson.php');
    $exists_in_theme = locate_template($theme_files, false);
    if ( $exists_in_theme != '' ) {
      return $exists_in_theme;
    } else {
      return plugin_dir_path(__FILE__) . 'archive-lesson.php';
    }
  }
  return $template;
}

More info on Codex for

share|improve this answer
    
This still just replaces the theme's template file, right? What do I put in my plugin's archive-lesson.php file? It would need to be different to work with each theme. Even the default "Twenty" themes do not agree on what div/section containers are that surround the content. –  Ben Miller Sep 29 '13 at 4:35

You can use the filter single_template. A basic example taken from the Codex:

function get_custom_post_type_template($single_template) {
     global $post;

     if ($post->post_type == 'my_post_type') {
          $single_template = dirname( __FILE__ ) . '/post-type-template.php';
     }
     return $single_template;
}

add_filter( "single_template", "get_custom_post_type_template" );
share|improve this answer
    
I think the filter hook for an archive template is archive_template, but I don't think this will work for what I am trying to do. I've edited my question with more information. –  Ben Miller Sep 28 '13 at 4:49

I had a similar issue and i found this, https://github.com/wpexplorer/page-templater/blob/master/pagetemplater.php it works quite well, the template file ofcourse needs to be created in the plugin and I put general styles that would change according to the theme it was on. Although there are a few issues though doesn't work exactly as a template would for wordpress.

share|improve this answer
    
Please put all necessary information into your answer - answers that are basically link-only are discouraged. –  ialocin Sep 17 at 13:56
    
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. –  Pieter Goosen Sep 17 at 14:02

I agree with G.M. about his answer.

If you use a Plugin, that generates a custom-post-type i assume, that you also have a template for the non-archived lessons in your plugin folder, since somehow you will want to query it with its specific Arguments, no?

That means you can use the code of G.M. to achieve that:

add_filter('template_include', 'lessons_template');

function lessons_template( $template ) {
  if ( is_post_type_archive('my_plugin_lesson') ) {
    $theme_files = array('archive-my_plugin_lesson.php', 'myplugin/archive-lesson.php');
    $exists_in_theme = locate_template($theme_files, false);
    if ( $exists_in_theme != '' ) {
      return $exists_in_theme;
    } else {
      return plugin_dir_path(__FILE__) . 'archive-lesson.php';
    }
  }
  return $template;
}

In this template-file try not to use any background-color or fixed widths, rather do percentages. Wordpress will get the header and footer information anyways and also the users stylesheet. This way you can maximize the "integration" in the users template. Maybe even use styles in the template, not as extra css-file.

Do the same for the archive-template.

If the user wants to change styles he just has to copy your template-file and customize it to his needs.

Provide a FAQ to your Plugin, tada.

I hope i understand you right.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.