Set WordPress Default Template

The better, cleaner solution would be not to use custom page templates to define page layouts*, but rather to use custom post meta to define page layouts.

To implement:

  1. Create a Theme Option for Default static page layout, that includes all possible page layout options
  2. Create a _page_layout custom post meta for Page Layout, that includes a “Default Layout” option, as well as all possible layout options used in the above Theme Option
  3. Define a body_class filter to output a layout-$layout class to the HTML <body> tag, via the body_class() template tag, where $layout is defined as so:
    • If 'default' == '_page_layout', use the Theme Option value
    • If '$layout' == '_page_layout', use $layout

Proof-of-concept code from Oenology follows (note: modified from original, since I apply custom post meta for layouts for static pages, single blog posts, and archive index pages):

Adding Meta Boxes

/**
 * Add Layout Meta Box
 * 
 * @uses    __()
 * @uses    add_meta_box()
 */
function oenology_add_layout_meta_box( $post ) {
    global $wp_meta_boxes;

    $context="side"; // 'normal', 'side', 'advanced'
    $priority = 'default'; // 'high', 'core', 'low', 'default'

    add_meta_box( 
        'oenology_layout', 
        __( 'Static Page Layout', 'oenology' ), 
        'oenology_layout_meta_box', 
        'page', 
        $context, 
        $priority 
    );

}
// Hook meta boxes into 'add_meta_boxes'
add_action( 'add_meta_boxes-page', 'oenology_add_layout_meta_box' );

/**
 * Define Layout Meta Box
 * 
 * Define the markup for the meta box
 * for the "layout" post custom meta
 * data. The metabox will consist of
 * radio selection options for "default"
 * and each defined, valid layout
 * option for single blog posts or
 * static pages, depending on the 
 * context.
 * 
 * @uses    oenology_get_option_parameters()    Defined in \functions\options.php
 * @uses    checked()
 * @uses    get_post_custom()
 */
function oenology_layout_meta_box() {
    global $post;
    $option_parameters = oenology_get_option_parameters();
    $custom = ( get_post_custom( $post->ID ) ? get_post_custom( $post->ID ) : false );
    $layout = ( isset( $custom['_oenology_layout'][0] ) ? $custom['_oenology_layout'][0] : 'default' );
    $valid_layouts = $option_parameters['default_static_page_layout']['valid_options'];
    ?>
    <p>
    <input type="radio" name="_oenology_layout" <?php checked( 'default' == $layout ); ?> value="default" /> 
    <label>Default</label><br />
    <?php foreach ( $valid_layouts as $valid_layout ) { ?>
        <input type="radio" name="_oenology_layout" <?php checked( $valid_layout['name'] == $layout ); ?> value="<?php echo $valid_layout['name']; ?>" /> 
        <label><?php echo $valid_layout['title']; ?> <span style="padding-left:5px;"><em><?php echo $valid_layout['description']; ?></em></span></label><br />
    <?php } ?>
    </p>
    <?php
}

/**
 * Validate, sanitize, and save post metadata.
 * 
 * Validates the user-submitted post custom 
 * meta data, ensuring that the selected layout 
 * option is in the array of valid layout 
 * options; otherwise, it returns 'default'.
 * 
 * @uses    oenology_get_option_parameters()    Defined in \functions\options.php
 * @uses    array_key_exists()
 * @uses    update_post_meta()
 */
function oenology_save_layout_post_metadata(){
    global $post;
    $option_parameters = oenology_get_option_parameters();
    $valid_layouts = array();
    if ( 'post' == $post->post_type ) {
        $valid_layouts = $option_parameters['default_single_post_layout']['valid_options'];
    } else if ( 'page' == $post->post_type ) {
        $valid_layouts = $option_parameters['default_static_page_layout']['valid_options'];
    }
    $layout = ( isset( $_POST['_oenology_layout'] ) && array_key_exists( $_POST['_oenology_layout'], $valid_layouts ) ? $_POST['_oenology_layout'] : 'default' );

    update_post_meta( $post->ID, '_oenology_layout', $layout );
}
// Hook the save layout post custom meta data into
// publish_{post-type}, draft_{post-type}, and future_{post-type}
add_action( 'draft_page', 'oenology_save_layout_post_metadata' );
add_action( 'future_post', 'oenology_save_layout_post_metadata' );
add_action( 'future_page', 'oenology_save_layout_post_metadata' );

Determining Current Page Layout

/**
 * Get Current Page Layout
 */
function oenology_get_current_page_layout() {

    // Use default layout for 404 pages
    if ( is_404() ) {
        return 'default';
    }

    // Otherwise, determine appropriate layout
    $layout="";
    global $post;
    global $oenology_options;
    $custom = ( get_post_custom( $post->ID ) ? get_post_custom( $post->ID ) : false );
    $custom_layout = ( isset( $custom['_oenology_layout'][0] ) ? $custom['_oenology_layout'][0] : 'default' );  
    if ( ! is_admin() ) {
        if ( is_attachment() ) {
            $layout .= 'attachment';
        } 
        else if ( is_page() ) {
            if ( 'default' == $custom_layout ) {
                $layout .= $oenology_options['default_static_page_layout'];
            } else {
                $layout .= $custom_layout;
            }
        } 
    } 
    else if ( is_admin() ) {
        if ( 'attachment' == $post->post_type ) {
            $layout .= 'attachment';
        } 
        else if ( 'page' == $post->post_type ) {
            if ( 'default' == $custom_layout ) {
                $layout .= $oenology_options['default_static_page_layout'];
            } 
            else {
                $layout .= $custom_layout;
            }
        } 
    }
    return $layout;
}

Filtering Body Class

/**
 * Add layout CSS classes to the HTML body tag
 * 
 * Filter Hook: body_class
 * 
 * Filter 'body_class' to include
 * classes for page layout.
 * 
 * @uses    oenology_get_current_page_layout()  Defined in \functions\custom.php
 * 
 * @since   Oenology 2.0
 */
function oenology_filter_body_class( $classes ) {   
    $layout="layout-";
    $layout .= oenology_get_current_page_layout();
    $classes[] = $layout;
    return $classes;
}
// Hook custom classes into 'body_class'
add_filter( 'body_class', 'oenology_filter_body_class' );

*Custom page templates really weren’t intended for defining layouts, but rather for defining custom page content.