How to add custom content template part for a custom post type on main query using a plugin

I have a custom post type paper defined in my WordPress plugin that I show along with standard posts in the main query via:

function add_custom_post_types_to_query( $query ) {
    if ( is_home() && $query->is_main_query() )
        $query->set( 'post_type', array( 'post', 'paper' ) );
    // As was pointed out by Ihor Vorotnov this return is not necessary. 
    // return $query;
}
add_action( 'pre_get_posts', 'add_custom_post_types_to_query' );

I have also been able to register a custom single-paper.php template from the plugin directory for my post type via:

function get_custom_post_type_single_template( $single_template ) {
     global $post;

     if ( $post->post_type == 'paper' ) {
          $single_template = dirname( __FILE__ ) . '/single-paper.php';
     }
     return $single_template;
}
add_filter( 'single_template', 'get_custom_post_type_single_template' );

I would like to do something similar for the content-____.php template that is used by my theme (onepress) to have control over how my posts appear in the main query.

The index.php of the theme even says:

<?php /* Start the Loop */ ?>
<?php while ( have_posts() ) : the_post(); ?>

<?php

    /*
     * Include the Post-Format-specific template for the content.
     * If you want to override this in a child theme, then include a file
     * called content-___.php (where ___ is the Post Format name) and that will be used instead.
     */
    get_template_part( 'template-parts/content', get_post_format() );
    ?>

<?php endwhile; ?>

but I would rather not want to implement a child theme for this and keep all the code related to the custom post type in the plugin.

Is there any way to add a filter such that get_template_part( 'template-parts/content', get_post_format() ); uses a custom template supplied by my plugin for paper posts and the standard template of the theme for normal posts?

I have tried different variations of the code I used above for the 'single_template', but to no avail.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Background

Unfortunately get_template_part() function doesn’t have any suitable filter to achieve what you want. It’s possible to use the get_template_part_{$slug} action hook to inject template parts, however, without any change to your theme or a child theme, the original template part will be added anyway. So this way you’ll not be able to replace existing template parts.

However, you’ll be able to achieve the desired result by following one of the options below:


Option 1: Use the default CSS classes:

If only thing you want is to change the style for the paper custom post type, then you may simply use the generated CSS class by WordPress. Any standard theme in WordPress will generate $post_type and type-{$post_type} CSS classes for the post content. For example, the custom post type paper will have paper & type-paper CSS classes and general post will have post and type-post CSS classes. Also home page will have home CSS class in the body. So to target paper entries in the home page, you may use the following CSS rules:

body.home .type-paper {
    /* Custom CSS for paper entries in the home page */
}

Option 2: Modify CSS classes:

If the default CSS classes are not enough for you, you can also modify (add / remove) CSS classes using the post_class filter hook in your plugin. Like this:

add_filter( 'post_class', 'paper_post_class' );
function paper_post_class( $class ) {
    if ( get_post_type() === 'paper' && is_home() ) {

        // remove these classes
        $remove = array( 'css-class-to-remove', 'another-class-to-remove' );
        $class = array_diff( $class, $remove );

        // add these classes
        $add = array( 'custom-paper', 'my-paper-class' );
        $class = array_merge( $add, $class );
    }
    return $class;
}

This way you’ll be able to remove CSS classes you don’t want for paper entries and add new CSS classes you want for paper entries without modifying the theme files. Then use those CSS classes to change the style of paper entries as needed.


Option 3: Modify template & template parts

If your desired style change is not possible by only targeting CSS classes, then you may change the template parts from your plugin too. However, since replacing template parts added by get_template_part() is not possible by using hooks, you’ll have to change the template in a way so that you may modify the get_template_part() call from within the plugin.

To do that, you may target your pre_get_posts hook function and use template_include filter hook to modify the home page template, like the following:

function add_custom_post_types_to_query( $query ) {
    if ( is_home() && $query->is_main_query() ) {
        $query->set( 'post_type', array( 'post', 'paper' ) );

        // now also change the home page template, but only when this condition matches
        add_filter( 'template_include', 'wpse258844_custom_template', 999 );
    }
}
add_action( 'pre_get_posts', 'add_custom_post_types_to_query' );

Then use the following CODE (modify as you need):

// for better file management, use a separate "theme-{$theme_name_slug}" directory within your plugin directory
// so if the active theme name is "OnePress", the directory name will be "theme-onepress"
// This will save you a ton of headache if you change the theme,
// as different themes may use different function calls within the templates, which other themes may not have
define( 'wpse258844_TEMPLATE_DIR', plugin_dir_path( __FILE__ ) . sprintf( 'theme-%s/', sanitize_title( wp_get_theme() ) ) );

function wpse258844_custom_template( $template ) {
    // this file will replace your homepage template file
    $index_paper = wpse258844_TEMPLATE_DIR . 'custom-home.php';

    // file_exists() check may need clearing stat cache if you change file name, delete the file etc.
    // Once you are done, comment out or remove clearstatcache(); in production
    clearstatcache();

    if ( file_exists( $index_paper ) ) {
        $template = $index_paper;
    }
    return $template;
}

function wpse258844_get_template_part( $slug, $name = null ) {
    if( get_post_type() === 'paper' ) {

        // just to be consistant with get_template_part() function
        do_action( "get_template_part_{$slug}", $slug, $name );

        $located = '';
        $name = (string) $name;

        // file_exists() check may need clearing stat cache if you change file name, delete the file etc.
        // Once you are done, comment out or remove clearstatcache(); in production
        clearstatcache();

        if ( '' !== $name && file_exists( wpse258844_TEMPLATE_DIR . "{$slug}-{$name}.php" ) ) {
            $located = wpse258844_TEMPLATE_DIR . "{$slug}-{$name}.php";
        }
        else if ( file_exists( wpse258844_TEMPLATE_DIR . "{$slug}.php" ) ) {
            $located = wpse258844_TEMPLATE_DIR . "{$slug}.php";
        }

        if ( '' != $located ) {
            load_template( $located, false );
            return;
        }
    }

    get_template_part( $slug, $name );
}

Once you have the above CODE in your plugin (read the comments within the CODE):

  1. Create a new directory within you plugin directory to keep theme template files, e.g. theme-onepress. This will help you in the future if you want to test design changes with a different theme (I suppose that’s the main purpose of all these mess 😉 ).
  2. Within the new theme-onepress directory, create a file named custom-home.php. Copy the CODE of home page template from your theme (may be index.php, or home.php or whatever your theme is using for the home page template).
  3. Now in custom-home.php change all the call of get_template_part to wpse258844_get_template_part. No need to change the parameter, only function name. wpse258844_get_template_part() function in the above CODE is identical to get_template_part() function and falls back to default behaviour if custom template part is not found within your plugin’s theme-{$theme_name_slug} (e.g. theme-onepress) directory.
  4. Finally, replace whatever template part file you wanted to replace in your plugin’s theme-{$theme_name_slug} directory.

    For example, if the original call was get_template_part( 'template-parts/content', get_post_format() ) and you want to replace content.php template part, then simply place a file named content.php in your plugin’s theme-onepress/template-parts directory. That means, the theme-onepress directory will behave like a child theme for template parts – i.e. simple drop-in replacement.

Method 2

create your own function for it:

function get_template_part_custom($content_name) {
     global $post;
     //its paper type?
     if ($post->post_type == 'paper') {
         //get template-parts/content-paper.php
         get_template_part( 'template-parts/content', $content_name );
     }else{
         //fallback to the default
         get_template_part( 'template-parts/content', get_post_format($post) );
     }
}

use it like this:

get_template_part_custom('paper');

if the post is of the type paper it will try to load a file called content-paper.php inside a folder called template-parts in the root theme folder, if the file doesnt exists it will try to load content.php, check the template-hierarchy, i dont think you need to register your template single-paper.php, WordPress will pick it up for you.

EDIT:

For loading a template from a plugin:

//load a custom file from the plugin
add_filter( 'template_include', 'return_our_template');

//Returns template file
function return_our_template( $template ) {

    // Post ID
    $post_id = get_the_ID();

    // For all other Types that arent ours
    if ( get_post_type( $post_id ) != 'paper' ) {
        return $template;
    }

    // Else use our custom template
    if ( is_single() ) {
        return get_custom_template( 'single' );
    }

}

//Get the custom template if is set
function get_custom_template( $template ) {

    // Get the slug
    $template_slug = rtrim( $template, '.php' );
    $template = $template_slug . '.php';

    // Check if a custom template exists in the theme folder, if not, load the plugin template file
    if ( $theme_file = locate_template( array( 'plugin_template/' . $template ) ) ) {
        $file = $theme_file;
    }
    else {
        //here path to '/single-paper.php'
        $file = PATH_TO_YOUR_BASE_DIR . $template;
    }
    //create a new filter so the devs can filter this
    return apply_filters( 'filter_template_' . $template, $file );
}

Method 3

As far as I can see from the source code, you can’t filter get_template_part() as you would normally do. Please see this thread for some solutions.

Method 4

Here is another “hack” to achieve what was asked for:

One can use the post format (not to be confused with the post type!) to use a custom template for a custom post type. Remember the line

get_template_part( 'template-parts/content', get_post_format() );

in the index.php described in the question. Through this construciton most standard themes load different templates depending on the post format.

One can claim that all posts of a given custom post type, say paper have a certain post format, say aside by calling

set_post_format( $post_id, 'aside' )

during the a function hooked into the 'save_post' hook and by adding something like

function paper_format_terms( $terms, $post_id, $tax ) {
    if ( 'post_format' === $tax && empty( $terms ) && 'paper' === get_post_type( $post_id ) ) {
        $aside = get_term_by( 'slug', 'post-format-aside', $tax );
        $terms = array( $aside );
    }
    return $terms;
}
add_filter( 'get_the_terms', 'paper_format_terms', 10, 3 );

to the 'get_the_terms' hook.

One can not define custom post formats, but usually there is at least one post format that is not used for anything else. The ‘aside’ format can be a good choice.

A standard theme will then, whenever it encounters a paper post look for the template .../template-parts/content-aside.php. All that is left to do is to install a suitable template to the theme directory in a way so that it does not get overwritten or deleted when the theme is updated (we want to keep all the code well contained inside the plugin!). This can be done my putting a suitable template into the fiel content-aside.php in the plugins directory and hooking into `’upgrader_process_complete’ as follows

function install_aside_template_on_upgrade( $upgrader_object, $options ) {
    if ($options['type'] == 'theme' ) {
        $content_aside = plugin_dir_path( __FILE__ ) . 'content-aside.php';
        $template_parts_content_aside = get_stylesheet_directory() . '/template-parts/content-aside.php';

        copy($content_aside, $template_parts_content_aside);
    }
}
add_action( 'upgrader_process_complete', 'install_aside_template_on_upgrade',10, 2);

In this way the content-aside.php is copied to the theme directory after each update of the theme.

Keep in mind that this is a pretty terrible hack and it can have unwanted side effects! Still, I thought this can be useful for some…

Method 5

While not exactly an answer to the question as it was posted, there is yet another quite clean and simple way to achieve something similar that, I suspect, will be the actual optimal solution for many people ending up on this page. For me the requirements changed slightly after I asked this question, allowing me to go for this solution in the end.

Instead of using a custom theme, in many cases it is enough to change what a custom post type returns when the theme’s template calls the_excerpt(). This can be achieved by hooking into the 'the_excerpt' filter as follows:

function paper_get_the_excerpt ($content) {
    global $post;
    $post_id = $post->ID;
    if ( get_post_type($post_id) === 'paper' ) {
        [do whatever you want with $content]
    }
    return $content;
}
add_filter( 'the_excerpt', 'paper_get_the_excerpt' );

Nice, clean and simple and allows for almost as much flexibility as changing the template.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x