6

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.

  • If I understood you correctly, you don't want to make any change to the theme at all, right? – Fayaz Mar 4 '17 at 16:33
  • @Fayaz Yes, exactly. I would like to keep all modifications theme independent and encapsulated inside the plugin. – cgogolin Mar 5 '17 at 12:09
  • 1
    Not answering your question, but important - you don't need to return $query; in the pre_get_posts callback. The $query object is passed by reference, any changes you make to the object via object's methods (e.g. set) are applied directly to the original object. Also, as a rule of thumb - filters allow you to modify the value passed to your callback and return it, while actions allow you to run basically any code. – Ihor Vorotnov Mar 7 '17 at 10:34
7

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.

  • Thanks for this very comprehensive answer. The technique you describe sounds complicated but is a rather "clean" solution to my problem. I will not accept it quite yet, as I am looking into one other option that I discovered. A shame that Wordpress makes this so hard... – cgogolin Mar 9 '17 at 11:35
  • I've described 3 options, only third option looks complicated, but it's really not complicated once you check what I did ;) – Fayaz Mar 9 '17 at 12:34
  • But for what I need only option 3 gives enough flexibility. – cgogolin Mar 9 '17 at 13:04
  • Yes, but as I said, it's not that complicated once you follow the instructions. Besides, the simplest option is the child theme, all of these is because you don't want a child theme. I only added two functions to make it a kind of replacement of the child theme by plugin :) – Fayaz Mar 9 '17 at 13:17
  • I am accepting this a the "best" answer, but I if you came here, I recommend that you also have a look at the alternative solutions I posted to see whether they work for you. – cgogolin Apr 27 '17 at 10:08
0

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 );
}
  • I also need what was asked. But how to load a custom post type template from a specific plugin folder while in the main query? And how activate this function from the functions.php file? – Iurie Malai Mar 5 '17 at 8:33
  • But for that to work I would have to replace the call to get_template_part( 'template-parts/content', get_post_format() ); in the theme by get_template_part_custom('paper');, right? I don't want to modify the theme. I want to keep all code related to the custom post type in the plugin. – cgogolin Mar 5 '17 at 12:12
  • I my updated answer. – David Lee Mar 5 '17 at 14:38
  • This only changes the single-___.php template. I already know how to do that. What I want to do is to change the appearance of paper posts in the main query, i.e., I want to influence the html generated by the call to get_template_part( 'template-parts/content', get_post_format() ); in the theme. – cgogolin Mar 7 '17 at 10:34
0

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.

0

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...

0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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