6

Is there any way of defining a Page Template from some function/filter/hook, without having any .php file containing the comment at the top of the page to declare the Template Name?


For example, instead of doing this:

some-custom-page-template.php

/**
* Template Name: Some Custom Page Template
*/

another-custom-page-template.php

/**
* Template Name: Another Custom Page Template
*/

I wonder if I can do something like:

functions.php

wp_some_function_to_define_templates( 'Some Custom Page Template' );
wp_some_function_to_define_templates( 'Another Custom Page Template' );

EDIT: I'm writing a complex and kind of experimental WP theme - It is as Object-Oriented as WP lets it to be, I have Controller classes to decide what to display in each page, DAO classes to retrieve data from DB, Twig templates to generate the views and so on...

I've spreaded the code so much, that my Page Templates have become just a line of code where I call some Controller and let it do the job, to finally render a Twig template. So I decided to move that call to the Controller, to inside the template_include filter, so that I don't even need to put any code in the Page Templates...

I might get rid of Page Templates, but I still need them to group pages somehow, to set some Advanced Custom Fields groups for them, etc...

So I ended up with about 15 PHP files that contain only a comment at the top with some Template Name, which is exactly what I'd like to avoid by defining those templates from some function...

Am I trying to reinvent the wheel? Maybe in some sense, but I think it's becoming a really cool theme, very maintenable and modifiable...

  • Is this mechanism you were following using Template name in Comment? And what is wrong with it? – Pratik Apr 30 '14 at 19:51
  • @PratikJoshi, I edited my question to include a proper explanation if you want to take a look... – MikO Apr 30 '14 at 20:04
  • So, the only function for the page templates is populating the template selection box on the back end? – s_ha_dum Apr 30 '14 at 20:07
  • @s_ha_dum, basically yes, but that allows me to group pages, so I can set ACF field groups for all of them, use the same Controller for process their data (with some parameter obtained from the page name), use the same Twig template for them, and so on... – MikO Apr 30 '14 at 20:09
  • 1
    the function that populates the page templates meta box isn't filterable, but since you're not really using it in the way it was intended, why not just add your own meta box and populate it with whatever options you want? – Milo Apr 30 '14 at 20:13
6

WordPress read the templates from file headers, and then set them in the cache. using wp_cache_set() nad a key that is derived from the md5 has of stylesheet folder.

So, just overriding that cache we can show the templates we want. To override that cache we need to call wp_cache_set() again using same key.

First of all let's write a function that retrieve the templates we want to display. There are a lot of ways we can set templates: option, configuration file, a filter, or a combination of them:

function get_custom_page_templates() {
  $templates = array();
  // maybe by options? --> $templates = get_option( 'custom_page_templates' );
  // maybe by conf file? --> $templates = include 'custom_page_templates.php';
  return apply_filters( 'custom_page_templates', $templates );
}

After that we need to retrieve the templates inside the page editing, just after WordPress saved the cache. Moreover we need to get them again when the editing form is posted to allow saving.

We can use 'edit_form_after_editor' hook for first scope, 'load-post.php' and 'load-post.new' for the second:

add_action( 'edit_form_after_editor', 'custom_page_templates_init' );
add_action( 'load-post.php', 'custom_page_templates_init_post' );
add_action( 'load-post-new.php', 'custom_page_templates_init_post' );

function custom_page_templates_init() {
  remove_action( current_filter(), __FUNCTION__ );
  if ( is_admin() && get_current_screen()->post_type === 'page' ) {
    $templates = get_custom_page_templates(); // the function above
    if ( ! empty( $templates ) )  {
      set_custom_page_templates( $templates );
    }
  }
}

function custom_page_templates_init_post() {
  remove_action( current_filter(), __FUNCTION__ );
  $method = filter_input( INPUT_SERVER, 'REQUEST_METHOD', FILTER_SANITIZE_STRING );
  if ( empty( $method ) || strtoupper( $method ) !== 'POST' ) return;
  if ( get_current_screen()->post_type === 'page' ) {
    custom_page_templates_init();
  }
}

Last thing we have to do, write the set_custom_page_templates() function that edit the cache to include our templates, being sure to merge any templates defined by file headers:

function set_custom_page_templates( $templates = array() ) {
  if ( ! is_array( $templates ) || empty( $templates ) ) return;
  $core = array_flip( (array) get_page_templates() ); // templates defined by file
  $data = array_filter( array_merge( $core, $templates ) );
  ksort( $data );
  $stylesheet = get_stylesheet();
  $hash = md5( get_theme_root( $stylesheet ) . '/' . $stylesheet );
  $persistently = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' );
  $exp = is_int( $persistently ) ? $persistently : 1800;
  wp_cache_set( 'page_templates-' . $hash, $data, 'themes', $exp );
}

Having this code running, you can setup custom templates just using the method you used in get_custom_page_templates() function, here I use the filter:

add_filter( 'custom_page_templates', function( $now_templates ) {

  $templates = array(
    'some-custom-page-template' => 'Some Custom Page Template',
    'another-custom-page-template' => 'Another Custom Page Template' ,
  );

  return array_merge( $now_templates, $templates );

} );

And you're done.

  • Brilliant trick! It's clear that you've inmersed yourself in the bowels of WP :) Anyway, I think that adding so much work load each time you want to load a page is probably not a very good idea... I think I'd better keep my template files with one line of code, I'll just put them in a subfolder and forget them... do you not think so? – MikO May 3 '14 at 12:29
  • 1
    @MikO Not really. This function act mainly on backend, on frontend do nothing. Moreover, reading a file form disk, and parse it's comments heading is slower than just update an array... Test, it with an high number of templates and you'll see that the files workflow is pretty slower. – gmazzap May 3 '14 at 14:07
  • This didn't work for me, is there a new way to do this? – mrmadhat Feb 15 '18 at 13:40
  • I've created a plugin based on this one to do this. – certainlyakey Nov 11 '18 at 18:25
2

If I understand you, you can do this with the template_include hook. For example:

add_filter( 'template_include', 'phpless_template');
function phpless_template( $template ) {  
  get_header();
  // some php
  get_footer();
  die;
}
  • Good trick, but not what I'm looking for... if you wait a second I'll write an EDIT in my question because the scenario is a bit complex – MikO Apr 30 '14 at 19:53
0

I suggest to you a simpler workaround:

  1. In your configuration/functions/index file define a config variable $config_template = false; (*)
  2. Create a folder "include" (probably you have one already)
  3. Put there the files some-custom-page-template.php and another-custom-page-template.php
  4. In these files simply assign a value to $config_template and then require the page you are using to render the loop - for example: $config_template = 'some-custom-page'; require( "../index.php" );
  5. In this file (index.php) you can do want you want based on the value of $config_template

Using this method you are not forcing the normal workflow of WP (this is never a good idea) but you are keeping your project sufficiently clean.

(*) Probably you are already using some object property to manage your configurations. You'll put the $config_template variable there.

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.