4

I am developing a WP theme with MVC approach. It only have index.php, functions.php and styles.css on the parent directory. So, I do not want to place page templates on it rather then I want to programmatically provide them from View classes while functionality from edit screen stays the same.

Users need to have page templates select option on the edit screen. How do I successfully add Items to the template dropdown?

I tried to hook into theme_page_templates filter.

Example code:

add_filter( 'theme_page_templates', function($templates){

    $templates['my-page-template.php'] = "My Page Template";

    return $templates;

});

This does not work because of the use of array_intersect_assoc() on the filtered array, which removes the added page template item. I don't understand why this function is used. It seems you can only remove page template from the list but can not add a new one using the given filter.

Is there any other way around?

  • I remember playing with this filter here, when I wanted to add extra info to the template dropdown, like usage statistics. But this part stopped me too at that time ;-) I haven't revisit it since, but there might be workarounds, like with javascript or through a custom metabox. – birgire Aug 7 '15 at 17:58
  • @birgire - Check my answer for this as I think you can do what you want using the approach I posted. – MikeSchinkel Aug 8 '15 at 16:34
  • Have a look to this answer wordpress.stackexchange.com/a/143054/35541 I'm using this trick and works pretty well – gmazzap Aug 9 '15 at 23:07
  • Thanks for sharing @gmazzap, just noticed your link ;-) It's now easier in WordPress 4.4+, I just reviewed this again in my old linked answer above. – birgire Nov 19 '16 at 13:41
5

I thought I would provide you with another approach. It is also a bit hackish, but it is general purpose and allows you to simply register the filename and label that you want to use, like so:

if ( class_exists( 'WPSE_196995_Page_Templates' ) ) {   
    WPSE_196995_Page_Templates::register_page_template(
        'My Page Template',
        'my-page-template.php'
    );
}

You could add the above code to your theme's functions.php file.

To enable the above to actually work I implemented a self-contained class that could be used as a plugin or just copied into functions.php:

<?php   
/**
 * Plugin Name: WPSE 196995 Page Templates
 *
 * Class WPSE_196995_Page_Templates
 *
 * Allows registering page templates via code.
 */
class WPSE_196995_Page_Templates {

    static $registered_templates = array();

    static function on_load() {

        /**
         * Add registered page templates to 'page_template' cache.
         * @note This hook is called just before page templates are loaded
         */
        add_action( 'default_page_template_title', array( __CLASS__, '_default_page_template_title' ) );
    }

    /**
     * Register page templates
     *
     * @param string $label
     * @param string $filename
     */
    static function register_page_template( $label, $filename ) {

        self::$registered_templates[ $filename ] = $label;

    }

    /**
     * Add registered page templates to 'page_template' cache.
     *
     * @param string $title
     *
     * @return string mixed
     */
    static function _default_page_template_title( $title ) {

        /**
         * @var WP_Theme $theme
         */
        $theme = wp_get_theme();

        /**
         * Access the cache the hard way since WP_Theme makes almost everything private
         */
        $cache_hash = md5( $theme->get_stylesheet_directory() );

        /**
         * Get the page templates as the 'page_templates' cache will already be primed
         */
        $page_templates = wp_cache_get( $key = "page_templates-{$cache_hash}", $group = 'themes' );

        /**
         * Add in registered page templates
         */
        $page_templates += self::$registered_templates;

        /**
         * Now update the cache, which is what the get_page_templates() uses.
         */
        wp_cache_set( $key, $page_templates, $group, 1800 );

        /**
         * We are using this hook as if it were an action.
         * So do not modify $title, just return it.
         */
        return $title;

    }

}
WPSE_196995_Page_Templates::on_load();

The class provides the register_page_template() method, of course, but to actually add your page template it updates the value for 'page_templates' set in the object cache.

It is a bit hacky because WordPress made most methods and properties of the WP_Theme class private, but fortunately they used the publicly-accessible WordPress object cache to store the values. And by updating the object cache in the 'default_page_template_title' hook, which is called just before the page templates dropdown is generated and sent to the browser, we can get WordPress to display your page template(s), as you desired.

  • 1
    What a creative workaround, thanks for sharing - I look forward to test&play ;-) – birgire Aug 8 '15 at 17:29
  • 1
    I thought about adding to the cache some point using the WP_Theme class. But everything can be use as a work around is private on the class. Your implementation is very clever indeed :) I will try it out and let you know.. – Sisir Aug 9 '15 at 12:42
  • The method worked! I had to change the hook to admin_head with appropriate conditionals. Somehow adding to the filter didn't worked for me. – Sisir Aug 9 '15 at 14:54
  • @Sisir - So the 'default_page_template_title' hook did not work but 'admin_head' did? But ultimately you got it working? – MikeSchinkel Aug 9 '15 at 18:40
  • Yep, Probably I did something wrong on the filter but it was running out of time so used admin_head and didn't gave much though why the filter didn't worked. – Sisir Aug 10 '15 at 12:21
1

Got around with a ugly hack :-/. I will update the answer If I go with jQuery later on. The solution still requires to have template files but code for the template file loads from the index.php

  1. I made a new template/ directory and put all page template there.
  2. All page templates are blank. We need it only for them to show up on the dropdown.
  3. Use template_include filter to redirect to index.php

Code

Blank Page Template Example

<?php
/*
Template Name: No Sidebar
 *
 * */

Filter

add_filter( 'template_include', function ($template ) {

    if ( !is_page_template()  )
        return $template;

    return locate_template( array( 'index.php' ), true );

}, 99);

I have created a trac ticket to allow adding templates via filter.

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.