The get_template_part() function in WordPress was never really designed with plugins in mind, since it relies on locate_template() which only checks child and parent themes. So we can add in a final fallback that uses the templates in the plugin, we have to use a custom locate_template() function, and a custom get_template_part() function. The solution here just wraps them up as a class for convenience.
1. Add a folder inc to your theme directory
Create a class-gamago-template-loader.php file in this directory. Add the code below in this file:
<?php /** * Template Loader for Plugins. * * @package Gamajo_Template_Loader * @author Gary Jones * @link http://github.com/GaryJones/Gamajo-Template-Loader * @copyright 2013 Gary Jones * @license GPL-2.0-or-later * @version 1.3.1 */ if ( ! class_exists( 'Gamajo_Template_Loader' ) ) { /** * Template loader. * * Originally based on functions in Easy Digital Downloads (thanks Pippin!). * * When using in a plugin, create a new class that extends this one and just overrides the properties. * * @package Gamajo_Template_Loader * @author Gary Jones */ class Gamajo_Template_Loader { /** * Prefix for filter names. * * @since 1.0.0 * * @var string */ protected $filter_prefix = 'your_plugin'; /** * Directory name where custom templates for this plugin should be found in the theme. * * For example: 'your-plugin-templates'. * * @since 1.0.0 * * @var string */ protected $theme_template_directory = 'plugin-templates'; /** * Reference to the root directory path of this plugin. * * Can either be a defined constant, or a relative reference from where the subclass lives. * * e.g. YOUR_PLUGIN_TEMPLATE or plugin_dir_path( dirname( __FILE__ ) ); etc. * * @since 1.0.0 * * @var string */ protected $plugin_directory = 'YOUR_PLUGIN_DIR'; /** * Directory name where templates are found in this plugin. * * Can either be a defined constant, or a relative reference from where the subclass lives. * * e.g. 'templates' or 'includes/templates', etc. * * @since 1.1.0 * * @var string */ protected $plugin_template_directory = 'templates'; /** * Internal use only: Store located template paths. * * @var array */ private $template_path_cache = array(); /** * Internal use only: Store variable names used for template data. * * Means unset_template_data() can remove all custom references from $wp_query. * * Initialized to contain the default 'data'. * * @var array */ private $template_data_var_names = array( 'data' ); /** * Clean up template data. * * @since 1.2.0 */ public function __destruct() { $this->unset_template_data(); } /** * Retrieve a template part. * * @since 1.0.0 * * @param string $slug Template slug. * @param string $name Optional. Template variation name. Default null. * @param bool $load Optional. Whether to load template. Default true. * @return string */ public function get_template_part( $slug, $name = null, $load = true ) { // Execute code for this part. do_action( 'get_template_part_' . $slug, $slug, $name ); do_action( $this->filter_prefix . '_get_template_part_' . $slug, $slug, $name ); // Get files names of templates, for given slug and name. $templates = $this->get_template_file_names( $slug, $name ); // Return the part that is found. return $this->locate_template( $templates, $load, false ); } /** * Make custom data available to template. * * Data is available to the template as properties under the `$data` variable. * i.e. A value provided here under `$data['foo']` is available as `$data->foo`. * * When an input key has a hyphen, you can use `$data->{foo-bar}` in the template. * * @since 1.2.0 * * @param mixed $data Custom data for the template. * @param string $var_name Optional. Variable under which the custom data is available in the template. * Default is 'data'. * @return Gamajo_Template_Loader */ public function set_template_data( $data, $var_name = 'data' ) { global $wp_query; $wp_query->query_vars[ $var_name ] = (object) $data; // Add $var_name to custom variable store if not default value. if ( 'data' !== $var_name ) { $this->template_data_var_names[] = $var_name; } return $this; } /** * Remove access to custom data in template. * * Good to use once the final template part has been requested. * * @since 1.2.0 * * @return Gamajo_Template_Loader */ public function unset_template_data() { global $wp_query; // Remove any duplicates from the custom variable store. $custom_var_names = array_unique( $this->template_data_var_names ); // Remove each custom data reference from $wp_query. foreach ( $custom_var_names as $var ) { if ( isset( $wp_query->query_vars[ $var ] ) ) { unset( $wp_query->query_vars[ $var ] ); } } return $this; } /** * Given a slug and optional name, create the file names of templates. * * @since 1.0.0 * * @param string $slug Template slug. * @param string $name Template variation name. * @return array */ protected function get_template_file_names( $slug, $name ) { $templates = array(); if ( isset( $name ) ) { $templates[] = $slug . '-' . $name . '.php'; } $templates[] = $slug . '.php'; /** * Allow template choices to be filtered. * * The resulting array should be in the order of most specific first, to least specific last. * e.g. 0 => recipe-instructions.php, 1 => recipe.php * * @since 1.0.0 * * @param array $templates Names of template files that should be looked for, for given slug and name. * @param string $slug Template slug. * @param string $name Template variation name. */ return apply_filters( $this->filter_prefix . '_get_template_part', $templates, $slug, $name ); } /** * Retrieve the name of the highest priority template file that exists. * * Searches in the STYLESHEETPATH before TEMPLATEPATH so that themes which * inherit from a parent theme can just overload one file. If the template is * not found in either of those, it looks in the theme-compat folder last. * * @since 1.0.0 * * @param string|array $template_names Template file(s) to search for, in order. * @param bool $load If true the template file will be loaded if it is found. * @param bool $require_once Whether to require_once or require. Default true. * Has no effect if $load is false. * @return string The template filename if one is located. */ public function locate_template( $template_names, $load = false, $require_once = true ) { // Use $template_names as a cache key - either first element of array or the variable itself if it's a string. $cache_key = is_array( $template_names ) ? $template_names[0] : $template_names; // If the key is in the cache array, we've already located this file. if ( isset( $this->template_path_cache[ $cache_key ] ) ) { $located = $this->template_path_cache[ $cache_key ]; } else { // No file found yet. $located = false; // Remove empty entries. $template_names = array_filter( (array) $template_names ); $template_paths = $this->get_template_paths(); // Try to find a template file. foreach ( $template_names as $template_name ) { // Trim off any slashes from the template name. $template_name = ltrim( $template_name, '/' ); // Try locating this template file by looping through the template paths. foreach ( $template_paths as $template_path ) { if ( file_exists( $template_path . $template_name ) ) { $located = $template_path . $template_name; // Store the template path in the cache. $this->template_path_cache[ $cache_key ] = $located; break 2; } } } } if ( $load && $located ) { load_template( $located, $require_once ); } return $located; } /** * Return a list of paths to check for template locations. * * Default is to check in a child theme (if relevant) before a parent theme, so that themes which inherit from a * parent theme can just overload one file. If the template is not found in either of those, it looks in the * theme-compat folder last. * * @since 1.0.0 * * @return mixed|void */ protected function get_template_paths() { $theme_directory = trailingslashit( $this->theme_template_directory ); $file_paths = array( 10 => trailingslashit( get_template_directory() ) . $theme_directory, 100 => $this->get_templates_dir(), ); // Only add this conditionally, so non-child themes don't redundantly check active theme twice. if ( get_stylesheet_directory() !== get_template_directory() ) { $file_paths[1] = trailingslashit( get_stylesheet_directory() ) . $theme_directory; } /** * Allow ordered list of template paths to be amended. * * @since 1.0.0 * * @param array $var Default is directory in child theme at index 1, parent theme at 10, and plugin at 100. */ $file_paths = apply_filters( $this->filter_prefix . '_template_paths', $file_paths ); // Sort the file paths based on priority. ksort( $file_paths, SORT_NUMERIC ); return array_map( 'trailingslashit', $file_paths ); } /** * Return the path to the templates directory in this plugin. * * May be overridden in subclass. * * @since 1.0.0 * * @return string */ protected function get_templates_dir() { return trailingslashit( $this->plugin_directory ) . $this->plugin_template_directory; } } }
Also create a inc file in the directory class-myplugin-template-loader.php
<?php class myPlugin_Template_Loader extends Gamajo_Template_Loader { protected $_filter_prefix = 'myplugin'; // our plugin slug protected $_theme_template_directory = 'myplugin'; // name for search in template // define('MYPLUGIN_PATH', plugin_dir_path(__FILE__)); protected $_plugin_directory = MYPLUGIN_PATH; protected $_plugin_template_directory = 'templates'; //the name of the directory in the plug-in files where the templates will be stored public $templates; public function register() { add_filter('template_include', [$this, 'myplugin_templates']); } public function myplugin_templates($template) { if (is_post_type_archive('cars')) { // check if we are in the posts archive $theme_files = ['archive-cars.php', 'myplugin/archive-cars.php']; // we will look for these files in theme folder $exist = locate_template($theme_files, false); if ($exist != '') { return $exist; } else { return plugin_dir_path(__DIR__) . 'templates/archive-cars.php'; //if the theme directory does not find these files, load from the plugin folder } } return $template; } } $myPlugin_Template = new myPlugin_Template_Loader(); $myPlugin_Template->register();
Now you can now attach these files by adding code to the main plugin file mypluginname.php
<?php /* Plugin Name: My Plugin Plugin URI: https://mypluginsite.com/ Description: One of best plugins in the planet Version: 1.0 Author: John Author URI: https://facebook.com/john Licence: GPLv2 or later Text Domain: myplugin Domain Path: /lang */ if(!definded('ABSPATH')) { die; } define('MYPLUGIN_PATH', plugin_dir_path(__FILE__)); //connect our files if(!class_exists('Gamajo_Template_Loader')) { require MYPLUGIN_PATH . 'inc/class-gamago-template-loader.php'; } require MYPLUGIN_PATH . 'inc/class-myplugin-template-loader.php';