When I posted about my cleverness with grandchildren themes (which as Cosper pointed out was a plugin), reader Damien mentioned templates. He said:

I have considered using template_redirect() to override template files in the child theme. I’ve experimented with providing selectable page templates where the template file is not in the child theme directory (not so easy).

Well Damien, I think you’ll be pleased to know there is a solution.

WPExplorer has made a WordPress page templates plugin. My caution is not to use the GitHub repo, which is not up to date, but copy the one in the post.

However … I forked it. And my fork is only going to work if you have both WordPress 4.7 and PHP 7. That’s because in PHP 7, PHP finally decided to allow defines to have arrays.

The Pre-Code

I use the template in an MU plugin. It sits in a folder called “cpts” and is summoned by my master index.php file that has this:

define( 'PAGE_TEMPLATER_ARRAY', [
	'videos-template.php' => 'Videos Archive',
] );

include_once( dirname( __FILE__ ) . '/cpts/NAME-OF-cpt.php' );
include_once( dirname( __FILE__ ) . '/cpts/page-templater.php' );

As you can see, I define my array and then I call the CPT file and the templater itself.

The Template File

This is 90% the same as the original. The two changes are I removed the check and failsafe for pre-WP 4.7, and I changed the section to add templates to call my define.

<?php
/*
Plugin Name: Page Template Plugin
Plugin URI: http://www.wpexplorer.com/wordpress-page-templates-plugin/
Version: 2.0
Author: WPExplorer
*/

class PageTemplater {

	/**
	 * A reference to an instance of this class.
	 */
	private static $instance;

	/**
	 * The array of templates that this plugin tracks.
	 */
	protected $templates;

	/**
	 * Returns an instance of this class.
	 */
	public static function get_instance() {

		if ( null == self::$instance ) {
			self::$instance = new PageTemplater();
		}

		return self::$instance;

	}

	/**
	 * Initializes the plugin by setting filters and administration functions.
	 */
	private function __construct() {

		$this->templates = array();

		// Add a filter to the wp 4.7 version attributes metabox
		add_filter(
			'theme_page_templates', array( $this, 'add_new_template' )
		);

		// Add a filter to the save post to inject out template into the page cache
		add_filter(
			'wp_insert_post_data',
			array( $this, 'register_project_templates' )
		);


		// Add a filter to the template include to determine if the page has our
		// template assigned and return it's path
		add_filter(
			'template_include',
			array( $this, 'view_project_template')
		);

		$this->templates = PAGE_TEMPLATER_ARRAY;

	}

	/**
	 * Adds our template to the page dropdown for v4.7+
	 *
	 */
	public function add_new_template( $posts_templates ) {
		$posts_templates = array_merge( $posts_templates, $this->templates );
		return $posts_templates;
	}

	/**
	 * Adds our template to the pages cache in order to trick WordPress
	 * into thinking the template file exists where it doens't really exist.
	 */
	public function register_project_templates( $atts ) {

		// Create the key used for the themes cache
		$cache_key = 'page_templates-' . md5( get_theme_root() . '/' . get_stylesheet() );

		// Retrieve the cache list.
		// If it doesn't exist, or it's empty prepare an array
		$templates = wp_get_theme()->get_page_templates();
		if ( empty( $templates ) ) {
			$templates = array();
		}

		// New cache, therefore remove the old one
		wp_cache_delete( $cache_key , 'themes');

		// Now add our template to the list of templates by merging our templates
		// with the existing templates array from the cache.
		$templates = array_merge( $templates, $this->templates );

		// Add the modified cache to allow WordPress to pick it up for listing
		// available templates
		wp_cache_add( $cache_key, $templates, 'themes', 1800 );

		return $atts;

	}

	/**
	 * Checks if the template is assigned to the page
	 */
	public function view_project_template( $template ) {

		// Get global post
		global $post;

		// Return template if post is empty
		if ( ! $post ) {
			return $template;
		}

		// Return default template if we don't have a custom one defined
		if ( ! isset( $this->templates[get_post_meta(
			$post->ID, '_wp_page_template', true
		)] ) ) {
			return $template;
		}

		$file = plugin_dir_path( __FILE__ ). get_post_meta(
			$post->ID, '_wp_page_template', true
		);

		// Just to be safe, we check if the file exist first
		if ( file_exists( $file ) ) {
			return $file;
		} else {
			echo $file;
		}

		// Return template
		return $template;

	}

}
add_action( 'plugins_loaded', array( 'PageTemplater', 'get_instance' ) );

So long as you have PHP 7+ and WP 4.7+, it all works great.

Reader Interactions

Comments

  1. Genius! Thanks a bunch for sharing this, Mika.

  2. Hi Mika,

    Thanks for the write up here! I was actually looking at that function when trying to edit a child theme for a site I was working on. I, like you stumbled across this plugin and code so it’s great to see your edits.

    I ended up hooking into template_include in my site’s custom plugin. I was wanting to overwrite things like the 404 page, and archive pages for certain CPT without wanting to mess with templates.

    /**
     *  Switch Template for our Things Post type
     */
    add_filter(  'template_include', 'bb_custom_templates' , 99 );
    function bb_custom_templates( $template ) {
      if ( is_post_type_archive( 'movies' ) ) {
        $archive_template = plugin_dir_path( __FILE__ ) . 'templates/archive.php';
        if( file_exists( $archive_template ) ){
          return $archive_template;
        }
      }
      elseif ( is_singular( 'movie' ) ) {
        $single_movie_template = plugin_dir_path( __FILE__ ) . 'templates/single-movie.php';
        if( file_exists( $single_movie_template ) ){
          return $single_thing_template;
        }
      }
      elseif ( is_404() ) {
        $fourohfour_template = plugin_dir_path( __FILE__ ) . 'templates/404.php';
        if( file_exists( $fourohfour_template ) ){
          return $fourohfour_template;
        }
      }
    return $template;
    }
%d bloggers like this: