Paulund
2013-11-11 #wordpress

Override Sidebars Per Page In WordPress

In WordPress development you define what sidebars can be used on your theme, these sidebars will then appear on the widget dashboard screen where you can assign all the widgets that are placed in the sidebar.

One of the areas that I've noticed with WordPress is the limitations you have with the widgets area. When you assign widgets to the sidebar they are used site wide and you can't change them page by page. If you want the widget to be different per page then you need to program the widget to display different data depending on what page is displayed. In other CMS's such as Drupal you can define the widgets for the sidebar not only site wide but can exclude these widgets from certain pages. The feature that WordPress is missing is the ability to show different widgets on a per page basis, in this tutorial we are going to create a WordPress plugin that means you can override the site wide sidebars and replace it with a sidebar on a per page basis.

Define The Plugin Class

First we need to start the plugin by adding WordPress plugin headers to the top of the file. This way WordPress will know that this code is a plugin which means we can activate it or deactivate it from the admin area. The class code will need to run in both the front-end and the admin area of the site so we can simply instantiate the class. Then we can create the class and define private variables for the sidebar name prefix and the post data meta key for the sidebar override.

<?php
/*
Plugin Name: Paulund Per Page Sidebar
Plugin URI: http://www.paulund.co.uk
Description: This plugin will allow you to create new sidebars to override existing site wide sidebars
Version: 1
Author: Paul Underwood
Author URI: http://www.paulund.co.uk/
*/
new Paulund_Per_Page_Sidebar();

class Paulund_Per_Page_Sidebar
{
    private $sidebarPrefix = 'paulund-sidebars';

    private $postDataMeta = 'paulund-override-sidebars';

    public function __construct()
    {

    }
}
?>

The Constructor

The constructor of the class is made up of adding functions to run on different actions and filters in WordPress. First we add a new meta box to the post and pages edit screen, inside this new meta box we will add a select box to choose which sidebar to override and then a link to create a brand new sidebar on the widget screen just for this page. If the page already has a sidebar then we display a link which will allow you to remove it and go back to the default sidebar. When the post is saved in the edit screen we will need to store the selected sidebar you want to override and save this to a post meta data. This will need to be used when we display the widgets to see what sidebar we are going to override. To create a new sidebar for a certain page we are going to link to the widgets screen with a query string parameter of the post ID, when this is set we can create a new sidebar on the widget screen which allows users to add new widgets just for this page. Next we add a function to run on the sidebar_widgets filter which will allow us to get an array of all registered sidebars and the widgets assigned to these sidebars. We can then search for the sidebar for the current page and use the widgets in here to override the select sidebar.


public function __construct()
{
    if(!empty($_GET['action']) && $_GET['action'] == 'edit')
    {
        add_action('add_meta_boxes', array(&$this, 'link_to_change_widgets') );
        add_action('save_post', array(&$this, 'save_sidebar_setting') );
    }

    add_action('sidebar_admin_setup', array(&$this, 'alter_page_widgets'));
    add_filter('sidebars_widgets', array(&$this, 'change_displayed_widgets'), 10);
}

Links For Override The Sidebar

On the edit screen of the post pages we need to add meta boxes which will allow you to create new sidebars for this current post. Because these rely on the post ID being set we only want this to appear on the edit screen and not the add post page. Therefore we create a function link_to_change_widgets() which will add new actions to add the meta box on to both pages and posts. Inside this meta box will be a select box of all registered sidebars for the site, the user will use this to select which sidebar you want to override. Then we add a link which links to the widget dashboard screen and adds a query string of the current post ID, with a current post ID we can then register a new sidebar just for this post. If a sidebar is registered then we can display a link which will allow you to delete the registered sidebar. Before we create these meta boxes we need to check if the user has requested to delete the registered sidebar, if this is set then we can remove all widgets registered to this sidebar and then remove the defined sidebar for the page.


/**
 * Add link to change the widgets
 *
 * @return void
 */
public function link_to_change_widgets()
{
    global $sidebars_widgets;

    if(!empty($_GET['new_sidebar_page_id']) && !empty($_GET['remove_sidebar']) && $_GET['remove_sidebar'] == 1)
    {
        $sidebarName = $this->sidebarPrefix.$_GET['new_sidebar_page_id'];

        // Unregister all sidebar widgets
        if(isset($sidebars_widgets[$sidebarName]))
        {
            foreach($sidebars_widgets[$sidebarName] as $k => $widgetId)
            {
                wp_unregister_sidebar_widget($k);
            }
        }

        // Unregister sidebar
        unset($sidebars_widgets[$sidebarName]);
        unregister_sidebar($sidebarName);

        // Save the widget
        wp_set_sidebars_widgets( $sidebars_widgets );

        add_action('admin_notices', array(&$this, 'show_deleted_sidebar_message'));
    }

    add_meta_box( 'custom_page_widget', __('Page Widgets'), array(&$this, 'display_widget_links'), 'page', 'normal', 'high' );
    add_meta_box( 'custom_post_widget', __('Post Widgets'), array(&$this, 'display_widget_links'), 'post', 'normal', 'high' );
}

/**
 * Show a message to say the sidebar has been deleted
 *
 * @return Deleted message
 */
public function show_deleted_sidebar_message()
{
    echo '<div id="message" class="updated"><p>Custom Sidebar Deleted</p></div>';
}

/**
 * Display links to the add a new override sidebar or delete existing sidebar
 */
public function display_widget_links()
{
    global $post, $sidebars_widgets, $wp_registered_sidebars;

    $sidebarName = $this->sidebarPrefix.$post->ID;

    $currentSelectedSidebar = get_post_meta($post->ID, $this->postDataMeta, true);
    ?>
        <h2>Select A Sidebar For This Page</h2>
        <input type="hidden" name="selectSidebarNonce" value="<?php echo wp_create_nonce(basename(__FILE__)); ?>" />
        <p><label for="selectSidebar">Sidebar To Override: </label>
            <select id="selectSidebar" name="selectSidebar">
                <option value="">Select A Sidebar To Override</option>
                <?php
                    if(!empty($wp_registered_sidebars))
                    {
                        foreach($wp_registered_sidebars as $sidebar)
                        {
                            $id = $sidebar['id'];
                            printf('<option value="%s" %s>%s</option>', $id, selected($currentSelectedSidebar, $id, false), $sidebar['name']);
                        }
                    }
                ?>
            </select>
        </p>

        <p><a href="widgets.php?new_sidebar_page_id=<?php echo $post->ID; ?>" target="_blank">Override Sidebar Widgets</a></p>
        <?php
            if(isset($sidebars_widgets[$sidebarName]))
            {
                ?><p><a href="?post=<?php echo $post->ID; ?>&action=edit&new_sidebar_page_id=<?php echo $post->ID; ?>&remove_sidebar=1">Remove This Page Widgets</a></p><?php
            }
        ?>
    <?php
}

Save Override Sidebar Settings

On the saving of the post we will then get the current selected value of the select box to override a sidebar, then we need to save this to the database by using post meta data with the function update_post_meta().


/**
 * Save the sidebar settings when the page is saved
 *
 * @param  INT $post_id - The post ID
 */
public function save_sidebar_setting($post_id)
{
    if (!wp_verify_nonce($_POST['selectSidebarNonce'], basename(__FILE__)))
        return $post_id;

    // check autosave
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
    {
        return $post_id;
    }

    // check permissions
    if ('page' == $_POST['post_type'])
    {
        if (!current_user_can('edit_page', $post_id))
        {
            return $post_id;
        }
        elseif (!current_user_can('edit_post', $post_id))
        {
            return $post_id;
        }
    }

    $old = get_post_meta($post_id, $this->postDataMeta, true);

    $new = $_POST["selectSidebar"];

    if ($new && $new != $old)
    {
        update_post_meta($post_id, $this->postDataMeta, $new);
    }
    elseif ('' == $new && $old)
    {
        delete_post_meta($post_id, $this->postDataMeta, $old);
    }
}

Create New Sidebar

From the post meta boxes we link to the widgets dashboard screen with the post id as the query string parameter. In the constructor we add a new action to the sidebar_admin_setup from this function we can register a new sidebar for this page is the sidebar doesn't already exist.


/**
 * Register a new sidebar for your page ID
 * Display new sidebar on the widget page
 *
 * @return Void
 */
public function alter_page_widgets()
{
    global $wp_registered_sidebars;

    if(!empty($_GET['new_sidebar_page_id']) && empty($_GET['remove_sidebar']))
    {
        $sidebarName = $this->sidebarPrefix.$_GET['new_sidebar_page_id'];

        if(empty($wp_registered_sidebars[$sidebarName]))
        {
            $args = array(
                'name'          => __( 'Page Override Sidebar'),
                'id'            => $sidebarName,
                'description'   => '',
                'class'         => '',
                'before_widget' => '<li id="%1$s" class="widget %2$s">',
                'after_widget'  => '</li>',
                'before_title'  => '<h2 class="widgettitle">',
                'after_title'   => '</h2>' );

            register_sidebar( $args );
        }
    }
}

Override Theme Sidebar With Page Sidebar

On the filter of the sidebar_widgets we add a function called change_displayed_widgets() this will have a parameter of widgets which is populated with an array of all sidebars and all widgets assigned to that sidebar. Inside this function we check to see if we are on the post single page, then we can get the post meta data for this post and see if there is a sidebar to override for this post. Checking the $widgets array we can see if this post has its own specific sidebar, if it does and there is an sidebar to override then we move the widgets for this post into the sidebar to override. When this function returns the $widgets array the main sidebar will be populated with the page specific widgets and will be displayed on the page.


/**
 * Change the widgets that we display in the sidebar
 *
 * @param  Array $widgets - Sidebar widgets
 *
 * @return Widgets to display
 */
public function change_displayed_widgets($widgets)
{
    global $post;

    if (isset($post) && is_numeric($post->ID) && is_singular())
    {
        $sidebarName = $this->sidebarPrefix.$post->ID;

        $sidebarToReplace = get_post_meta($post->ID, $this->postDataMeta, true);

        // Check for page override
        if(!empty($widgets[$sidebarName]) && !empty($sidebarToReplace))
        {
            $widgets[$sidebarToReplace] = $widgets[$sidebarName];
        }
    }

    return $widgets;
}

Full Plugin Code

Below is the full code that we can use for the per page sidebar plugin. I've also uploaded the plugin to Github where you can download the code for your own projects. Per Page Sidebar Plugin


<?php
/*
Plugin Name: Paulund Per Page Sidebar
Plugin URI: http://www.paulund.co.uk
Description: This plugin will allow you to create new sidebars to override existing site wide sidebars
Version: 1
Author: Paul Underwood
Author URI: http://www.paulund.co.uk/
*/
new Paulund_Per_Page_Sidebar();

class Paulund_Per_Page_Sidebar
{
    private $sidebarPrefix = 'paulund-sidebars';

    private $postDataMeta = 'paulund-override-sidebars';

    public function __construct()
    {
        if(!empty($_GET['action']) && $_GET['action'] == 'edit')
        {
            add_action('add_meta_boxes', array(&$this, 'link_to_change_widgets') );
            add_action('save_post', array(&$this, 'save_sidebar_setting') );
        }

        add_action('sidebar_admin_setup', array(&$this, 'alter_page_widgets'));
        add_filter('sidebars_widgets', array(&$this, 'change_displayed_widgets'), 10);
    }

    /**
     * Add link to change the widgets
     *
     * @return void
     */
    public function link_to_change_widgets()
    {
        global $sidebars_widgets;

        if(!empty($_GET['new_sidebar_page_id']) && !empty($_GET['remove_sidebar']) && $_GET['remove_sidebar'] == 1)
        {
            $sidebarName = $this->sidebarPrefix.$_GET['new_sidebar_page_id'];

            // Unregister all sidebar widgets
            if(isset($sidebars_widgets[$sidebarName]))
            {
                foreach($sidebars_widgets[$sidebarName] as $k => $widgetId)
                {
                    wp_unregister_sidebar_widget($k);
                }
            }

            // Unregister sidebar
            unset($sidebars_widgets[$sidebarName]);
            unregister_sidebar($sidebarName);

            // Save the widget
            wp_set_sidebars_widgets( $sidebars_widgets );

            add_action('admin_notices', array(&$this, 'show_deleted_sidebar_message'));
        }

        add_meta_box( 'custom_page_widget', __('Page Widgets'), array(&$this, 'display_widget_links'), 'page', 'normal', 'high' );
        add_meta_box( 'custom_post_widget', __('Post Widgets'), array(&$this, 'display_widget_links'), 'post', 'normal', 'high' );
    }

    /**
     * Show a message to say the sidebar has been deleted
     *
     * @return Deleted message
     */
    public function show_deleted_sidebar_message()
    {
        echo '<div id="message" class="updated"><p>Custom Sidebar Deleted</p></div>';
    }

    /**
     * Display links to the add a new override sidebar or delete existing sidebar
     */
    public function display_widget_links()
    {
        global $post, $sidebars_widgets, $wp_registered_sidebars;

        $sidebarName = $this->sidebarPrefix.$post->ID;

        $currentSelectedSidebar = get_post_meta($post->ID, $this->postDataMeta, true);
        ?>
            <h2>Select A Sidebar For This Page</h2>
            <input type="hidden" name="selectSidebarNonce" value="<?php echo wp_create_nonce(basename(__FILE__)); ?>" />
            <p><label for="selectSidebar">Sidebar To Override: </label>
                <select id="selectSidebar" name="selectSidebar">
                    <option value="">Select A Sidebar To Override</option>
                    <?php
                        if(!empty($wp_registered_sidebars))
                        {
                            foreach($wp_registered_sidebars as $sidebar)
                            {
                                $id = $sidebar['id'];
                                printf('<option value="%s" %s>%s</option>', $id, selected($currentSelectedSidebar, $id, false), $sidebar['name']);
                            }
                        }
                    ?>
                </select>
            </p>

            <p><a href="widgets.php?new_sidebar_page_id=<?php echo $post->ID; ?>" target="_blank">Override Sidebar Widgets</a></p>
            <?php
                if(isset($sidebars_widgets[$sidebarName]))
                {
                    ?><p><a href="?post=<?php echo $post->ID; ?>&action=edit&new_sidebar_page_id=<?php echo $post->ID; ?>&remove_sidebar=1">Remove This Page Widgets</a></p><?php
                }
            ?>
        <?php
    }

    /**
     * Save the sidebar settings when the page is saved
     *
     * @param  INT $post_id - The post ID
     */
    public function save_sidebar_setting($post_id)
    {
        if (!wp_verify_nonce($_POST['selectSidebarNonce'], basename(__FILE__)))
            return $post_id;

        // check autosave
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
        {
            return $post_id;
        }

        // check permissions
        if ('page' == $_POST['post_type'])
        {
            if (!current_user_can('edit_page', $post_id))
            {
                return $post_id;
            }
            elseif (!current_user_can('edit_post', $post_id))
            {
                return $post_id;
            }
        }

        $old = get_post_meta($post_id, $this->postDataMeta, true);

        $new = $_POST["selectSidebar"];

        if ($new && $new != $old)
        {
            update_post_meta($post_id, $this->postDataMeta, $new);
        }
        elseif ('' == $new && $old)
        {
            delete_post_meta($post_id, $this->postDataMeta, $old);
        }
    }

    /**
     * Register a new sidebar for your page ID
     * Display new sidebar on the widget page
     *
     * @return Void
     */
    public function alter_page_widgets()
    {
        global $wp_registered_sidebars;

        if(!empty($_GET['new_sidebar_page_id']) && empty($_GET['remove_sidebar']))
        {
            $sidebarName = $this->sidebarPrefix.$_GET['new_sidebar_page_id'];

            if(empty($wp_registered_sidebars[$sidebarName]))
            {
                $args = array(
                    'name'          => __( 'Page Override Sidebar'),
                    'id'            => $sidebarName,
                    'description'   => '',
                    'class'         => '',
                    'before_widget' => '<li id="%1$s" class="widget %2$s">',
                    'after_widget'  => '</li>',
                    'before_title'  => '<h2 class="widgettitle">',
                    'after_title'   => '</h2>' );

                register_sidebar( $args );
            }
        }
    }

    /**
     * Change the widgets that we display in the sidebar
     *
     * @param  Array $widgets - Sidebar widgets
     *
     * @return Widgets to display
     */
    public function change_displayed_widgets($widgets)
    {
        global $post;

        if (isset($post) && is_numeric($post->ID) && is_singular())
        {
            $sidebarName = $this->sidebarPrefix.$post->ID;

            $sidebarToReplace = get_post_meta($post->ID, $this->postDataMeta, true);

            // Check for page override
            if(!empty($widgets[$sidebarName]) && !empty($sidebarToReplace))
            {
                $widgets[$sidebarToReplace] = $widgets[$sidebarName];
            }
        }

        return $widgets;
    }
}
?>