How can I add an option to the Page Template list from a Plugin?

I’ve been looking around for the past couple hours for a way to create a custom page template from within a plugin but haven’t had any luck yet.

What I am specifically trying to do is add an option to the list of available page templates when editing a page, and not using some other method like if( is_page( 'page-slug' ) ) { /* etc */ }

Read More

Is there a global variable I can modify to do this?

Edit:

I’m using this code currently, based on the link @m0r7if3r gave me in a comment, the problem is it will run this function when viewing the page, but not when editing the page (to populate the dropdown list with the page templates):

class JW_SiteGrader {

    private static $instance;

    private function __construct() {


        add_action( 'template_redirect', array( &$this, 'sitegrader_template_redirect' ), 20 );

    }

    public static function getInstance() {

        // Return the class data in a Singleton fashion
        if (self::$instance == null)
            self::$instance = new JW_SiteGrader();
        return self::$instance;

    }

    public function sitegrader_template_redirect() {

        add_filter( 'page_template', array( &$this, 'sitegrader_page_template' ), 10, 1 );

    }

    public function locate_plugin_template( $template_names, $load = false, $require_once = true ) {

        if ( !is_array( $template_names ) )
            return '';

        $located = '';

        $this_plugin_dir = WP_PLUGIN_DIR . '/' . str_replace( basename( __FILE__ ), '', plugin_basename( __FILE__ ) );

        foreach ( $template_names as $template_name ) {

            if ( !$template_name )
                continue;

            if ( file_exists( STYLESHEETPATH . '/' . $template_name ) ) {

                $located = STYLESHEETPATH . '/' . $template_name;
                break;

            } else if ( file_exists( TEMPLATEPATH . '/' . $template_name ) ) {

                $located = TEMPLATEPATH . '/' . $template_name;
                break;

            } else if ( file_exists( $this_plugin_dir .  $template_name ) ) {

                $located =  $this_plugin_dir . $template_name;
                break;

            }

        }

        if ( $load && '' != $located )
            load_template( $located, $require_once );

        return $located;
    }

    public function sitegrader_page_template( $template ) {

        $object = get_queried_object();

        if ( 'page' == $object->post_type ) {

            // New 
            $templates[] = "page-sitegrader.php";
            // Like in core
            $templates[] = "page-{$object->post_type}.php";
            $templates[] = "page.php";

            return locate_template( $templates );  

        }

        // return apply_filters('page_template', $template);
        return $template;
    }

}

Edit 2:

It seems that this functionality will be released in a future update, I read quite a few trac tickets about this and there has been some discussion but no real answer (hoping for 3.4). Will list trac tickets URLs here in a bit.

Edit 3:

The code above works, BUT, the only issue I’m having at this point is there’s no template being added to the dropdown list when editing/adding a new page. I’m trying a few things and will update my question soon.

Related posts

Leave a Reply

6 comments

  1. Filters? Anyone?

    There’s no filter there to help: page_template_dropdown($template); is used to build the drop down and it’s not filterable.

    Sneaking into the Templates Array?

    To build the drop downs contents, the core meta box uses get_page_templates(). From inside, the function looks like the following:

    $themes = get_themes();
    $theme = get_current_theme();
    $templates = $themes[$theme]['Template Files'];
    

    But when looking into get_themes(); then there seams to be no possibility to intercept the list of templates. Further more we have the problem, that there’s no chance to get a template from outside the themes directory…

    …Faking a Theme!

    The theory and it’s drawbacks…

    You can use register_theme_directory() to add an additional theme directory where you can place templates. So the easiest thing would be to register your plugin as themes folder too:

    // Register during init hook:
    register_theme_directory( plugin_dir_path( __FILE__ ).'templates' );
    

    Note: This is where I’m not sure if it will work.

    During plugin activation:
    Then you should place a style.css.php file inside your templates folder. This would allow you to add variables to your file. This variable would then be the parent theme. And the parent theme should simply be the currently active theme. Then update your active theme to your plugin.

    Drawback #2: About the »Appearance« UI… Maybe add a note that this “Theme” is not meant to be used as actual Theme. I leave the rest of »Avoid activating this theme« stuff up to your imagination. Anyway: It should work.

    Drawback #2: This trick will successfully avoid child themes. You’re allowed to have one parent theme. Nothing more.

  2. As a suggestion for a potential work around have you considered using the WordPress file system to write a page template file from your plugin to your current active theme directory? Depending upon how much control you want over this process you can have your plugin write the file on activation and remove it on uninstall. Alternatively you could could create page template files dynamically from within your plugin UI using a form to pass certain values such as the page template name which is to appear in the dropdown box of the post edit screen. You could also in theory remove the page template from within your plugin UI at the click of a button and similarly add multiple templates for different purposes. There’s a good post on using the file system over at Otto’s blog. I don’t have the link with me now but you can search for it.

    I hope they release what you were intending to do by way of a hook in the next core release.

  3. I took a browse through the source, and quite shockingly there doesn’t look to be a way to do this! My suggested hack would be to add a second meta box with a theme override/second theme dropdown. Then hook into this filter:

    # wp-includes/theme.php line 751 in 3.3.1:
    return apply_filters( "{$type}_template", locate_template( $templates ) );
    

    to return your theme file if it’s been overridden. Of course, you’d want to only add_filter if it’s the post you want.

    If you wanted to be fancy, you could scrap the original box and re-create it (via filters of course, not modifying core code).

    I’d also suggest adding a trac ticket requesting that functionality (if there isn’t one already in there for it).

  4. I have accomplished this in the past by removing the default page template metabox and then adding your own metabox. I had the new metabox use the built in get_page_templates and then added the others that I needed programatically. On a save it can can update the page template just like wordpress does.

  5. Check out the hook theme_page_templates, used by the get_page_templates function, filter added since 3.9 updated in 4.4:

    apply_filters( 'theme_page_templates', $page_templates, $this, $post );
    
  6. A search result with DuckDuckGo add a wordpress page template turned up the answer for me on a wpmu.org page titled WordPress Custom Page Template Tutorial.

    Essentially, inside the theme directory, wp-content/themes/your-theme, copy an existing page template to a new php file name of your choice. Edit the new file, taking care to change the magic “Template Name:” property value in the comment at the top of the file.

    That was all there was to it. It is possible this is a feature that post-dates the question. I am using WP 3.5 with the twenty_eleven theme.

    It is also possible that there are cleaner ways to extend a theme. Edits like this might be overwritten by theme updates. Mea Culpa.