Anfragen

Provide overwritable templates from your plugin

If you provide frontend output with your plugin, it is important that other developers can modify it for theming purpose. This can be done via filters or actions but I think the easiest and most common way is to provide template paths that can be overwritten.

It is common practice to define a path and file naming convention that can be used in a theme. This way WordPress itself lets you modify its templates and other big players like WooCommerce do it the same way. It’s simple and intuitive. Theme developers can copy-paste template files into their theme and start working on changing the output. No additional boilerplate code is needed.

Lookup

The most important thing for our intend is path finding. There are several locations that could be interesting.

  1. Active theme
  2. Parent theme
  3. Other plugins
  4. Default templates

This list will also reflects the priority of the template lookup.

Active theme

We can search for template files in the active theme with the locate_template function. It will return a path string or false if nothing was found.

namespace PublicFunctionOrg\WordPress;
class Templates {
  private $theme_dir = "/my-plugin-templates/";

  public function get_template_path($template){
      if ( $overridden_template = locate_template( $this->theme_dir.$template ) ) {
          return $overridden_template;
      }
      return false;
  }
}

$templates = new Templates();
$filepath = $templates->get_template_path("my-template.php");

Parent theme

Normally, the locate_template function should also find template files in a parent theme directory. But I had a situation where it didn’t. That’s why I always add a second lookup explicitly for parent theme template files.

namespace PublicFunctionOrg\WordPress;
class Templates {
  private $theme_dir = "/my-plugin-templates/";

  public function get_template_path($template){
      $path = $this->theme_dir.$template;
      if( is_file( get_template_directory().$path)){
          return get_template_directory().$path;
      }
      return false;
  }
}

$templates = new Templates();
$filepath = $templates->get_template_path("my-template.php");

Other Plugins

If other plugins might be interested in overwriting our plugin templates, we first need to provide a filter to allow plugins to register their template paths. Then we will include those paths in our lookup.

add_filter("my_plugin_add_template_paths", function($paths){
  $paths[] = plugin_file_path(__FILE__)."/my-templates-dir/";
  return $paths;
});
namespace PublicFunctionOrg\WordPress;
class Templates {
  public function get_template_path($template){
      $paths = apply_filters("my_plugin_add_template_paths", []);
      foreach ($paths as $path){
          if(is_file("$path/$template")){
              return "$path/$template";
          }
      }
      return false;
  }
}

// find templates
$templates = new Templates();
$filepath = $templates->get_template_path("my-template.php");

Default templates

The last lookup is the simplest. Just use your plugins default templates.

namespace PublicFunctionOrgWordPress;
class Templates {
  public function get_template_path($template){
      $path = plugin_file_path(__FILE__)."/templates/";
      if(is_file("$path/$template")){
          return "$path/$template";
      }
      return false;
  }
}

// find templates
$templates = new Templates();
$filepath = $templates->get_template_path("my-template.php");

All in one

Now that we’ve found a solution for each individual location where templates can be found, let’s combine these destinations to create a combined template lookup class.

namespace PublicFunctionOrgWordPress;
class Templates {

  const THEME_DIR = "/my-plugin-templates/";
  const FILTER_ADD_TEMPLATES_PATHS = "my_plugin_add_template_paths";

  public function get_template_path($template){

      if ( $overridden_template = locate_template( self::THEME_DIR.$template ) ) {
          return $overridden_template;
      }

      $path = self::THEME_DIR.$template;
      if( is_file( get_template_directory().$path)){
          return get_template_directory().$path;
      }

      $paths = apply_filters(self::FILTER_ADD_TEMPLATES_PATHS, []);
      foreach ($paths as $path){
          if(is_file("$path/$template")){
              return "$path/$template";
          }
      }

      $path = plugin_file_path(__FILE__)."/templates/";
      if(is_file("$path/$template")){
          return "$path/$template";
      }

      return false;

  }

}

Render templates

If we want to render a template in the plugin we should first add the default template file templates/my-template.php to the template directory in our plugin. This will be the master copy, so it’s very helpful to be very detailed in the environment documentation.

// my-plugin.php
add_filter("the_content", function($content){
$data = new MyModel();
$templates = new PublicFunctionOrgWordpressTemplates();

$filepath = $templates->get_template_path("my-template.php");
if($filepath){
  ob_start();
  include $filepath;
  $content.= ob_get_contents();
  ob_end_clean();
}
return $content;
});
// my-template.php
/**
* @var SomeClass $this
* @var MyModel $data
*/
echo $data->getDescription();

Final words

These are the basics for the starting point of my templates class. You can find my master copy for the templates class in the wp-components repository on GitHub. It has some additional features and configuration methods, but basically it works the same way.

Do you have a similar or completely different approach to this feature? Please reply to this article and discuss with me.


ADS for Plugin and Theme starterkit