10 Tips for a Deeply Customised WordPress Admin Area

by in WordPress on 8th Oct 2012 · Comments

Client-oriented WordPress development often requires the customisation of the default admin area. In many cases this customisation is ended with altering of the branding. Undoubtedly, branding is important, but it is not sufficient for satisfactory user-experience and everyday comfortable usage.

10 Tips for a Deeply Customised WordPress Admin Area

The WordPress admin area inherits a lot of things from the blogging concept, that was the foundation of the system. With active movement towards the "WordPress as CMS" direction this heritage becomes outdated. Especially in cases when custom installation actually does not have a "blog" in it's traditional meaning.

In such a situation a lot of elements like menu items, labels, notifications should be altered. In the following set of tips we are going to dive deeper into the dark waters of WordPress admin customisation with the noble objective of making it truly yours.

10 Tips for a Deeply Customised WordPress Admin Area

1. Execute admin code in admin only

if(!is_admin())
    return;

The first tip on our list is the shortest one. We just check whether we have an admin request with a native conditional tag and abort the script if it's not the case. This trick allows us not to execute unwanted code in the front-end to avoid possible issues and for better performance.

2. Change default post types labels

function frl_change_post_labels($post_type, $args){ /* change assigned labels */
    global $wp_post_types;
    
    if($post_type != 'post')
        return;
    
    $labels = new stdClass();
    
    $labels->name               = __('Articles', 'frl');
    $labels->singular_name      = __('Article', 'frl');
    $labels->add_new            = __('Add new', 'frl');
    $labels->add_new_item       = __('Add new Article', 'frl');
    $labels->edit_item          = __('Edit Article', 'frl');
    $labels->new_item           = __('New Article', 'frl');
    $labels->view_item          = __('View Article', 'frl');
    $labels->search_items       = __('Search Articles', 'frl');
    $labels->not_found          = __('No Articles found', 'frl');
    $labels->not_found_in_trash = __('No Articles found in Trash.', 'frl');
    $labels->parent_item_colon  = NULL;
    $labels->all_items          = __('All Articles', 'frl');
    $labels->menu_name          = __('Articles', 'frl');
    $labels->name_admin_bar     = __('Article', 'frl');
    
    $wp_post_types[$post_type]->labels = $labels;
}
add_action('registered_post_type', 'frl_change_post_labels', 2, 2);

function frl_change_post_menu_labels(){ /* change adming menu labels */
    global $menu, $submenu;
    
    $post_type_object = get_post_type_object('post');
    $sub_label = $post_type_object->labels->all_items; 
    $top_label = $post_type_object->labels->name;
    
    /* find proper top menu item */
    $post_menu_order = 0;
    foreach($menu as $order => $item){
        
        if($item[2] == 'edit.php'){
            $menu[$order][0] = $top_label;
            $post_menu_order = $order;
            break;
        }
    }
    
    /* find proper submenu */
    $submenu['edit.php'][$post_menu_order][0] = $sub_label;
}
add_action('admin_menu', 'frl_change_post_menu_labels');

function frl_change_post_updated_labels($messages){     /* change updated post labels */
    global $post;
        
    $permalink = get_permalink($post->ID);
        
    $messages['post'] = array(
        
    0 => '', 
    1 => sprintf( __('Article updated. <a href="%s">View post</a>', 'frl'), esc_url($permalink)),
    2 => __('Custom field updated.', 'frl'),
    3 => __('Custom field deleted.', 'frl'),
    4 => __('Article updated.', 'frl'),    
    5 => isset($_GET['revision']) ? sprintf(__('Article restored to revision from %s', 'frl'), wp_post_revision_title((int)$_GET['revision'], false)) : false,
    6 => sprintf( __('Article published. <a href="%s">View post</a>'), esc_url($permalink)),
    7 => __('Article saved.', 'frl'),
    8 => sprintf( __('Article submitted. <a target="_blank" href="%s">Preview</a>', 'frl'), esc_url(add_query_arg('preview','true', $permalink))),
    9 => __('Article scheduled. <a target="_blank" href="%2$s">Preview</a>', 'frl'),
    10 => sprintf( __('Article draft updated. <a target="_blank" href="%s">Preview</a>', 'frl'), esc_url(add_query_arg('preview', 'true', $permalink)))

    );

    return $messages;
}
add_filter('post_updated_messages', 'frl_change_post_updated_labels');

By default WordPress registers some "built-in" post types such as the well-known "post" and "page". For particular installations such names may not be applicable, therefore labels should be changed to avoid confusion. In our example we converted Post(s) labels into Article(s). WordPress does not provide any API for that need, so we have to manually alter global $wp_post_types object. We find the required post types in the array of registered ones, and assign artificially created $labels objects to it's labels property. We have to be sure that $labels objects have all necessary properties, even empty ones.

WordPress normally uses labels provided upon custom post type's registration to name corresponding admin menu items. Unfortunately, with built-in post types it's not the case. So, we have to change the name of posts menu into something appropriate ourselves. Function frl_change_post_menu_labels finds the necessary labels (that we've assigned in the previous step) and injects them directly into global $menu and $submenu objects.

Another aspect, that requires changes is post updated labels - they appear on the post edit screen after saving. For custom post types the custom set of labels could be provided as a registration parameter, but for built-in ones we have to alter them manually. Fortunately, there is a post_updated_messages filter, that allows us to do exactly that.

Post Labels

3. Remove default admin menu items, e.g. Links section

function frl_remove_links_admin_menu(){ 
        
    $remove_pages = array();
    $redirect = admin_url('index.php');

    $remove_pages[] = array('parent_slug' => '', 'menu_slug' => 'link-manager.php', 'redirect' => $redirect);   
    $remove_pages[] = array('parent_slug' => 'link-manager.php', 'menu_slug' => 'link-add.php', 'redirect' => $redirect);
    $remove_pages[] = array('parent_slug' => 'link-manager.php', 'menu_slug' => 'edit-tags.php?taxonomy=link_category', 'redirect' => $redirect);

    foreach($remove_pages as $rpage){
    
    /* if we have request for deleted page - redirect it */
    if(!empty($redirect) && false != strpos($_SERVER['REQUEST_URI'], '/'.$rpage['menu_slug']))          
        wp_redirect($redirect);

    if(empty($rpage['parent_slug'])) // top level page
        remove_menu_page($rpage['menu_slug']);
    else //submenu page
        remove_submenu_page($rpage['parent_slug'], $rpage['menu_slug']);
    }
}
add_action('admin_menu', 'frl_remove_links_admin_menu', 30);    

Some default admin menu items and corresponding functionality may not be needed for particular installations. It's better to remove them to keep things clear. For this example we are going to remove the links management section completely.

We prepare an array of items to be removed from the menu (in our example it includes top-level link-manager.php item and sub-items - link-add.php, edit-tags.php?taxonomy=link_category). Moreover, we would like to redirect pages under these items to something neutral, for example to dashboard. By looping through the prepared array we remove each item with native functions remove_menu_page or remove_submenu_page. We also test whether the current request is for deleted pages and if so - perform redirect.

4. Alter CSS class in admin

function frl_admin_body_class($class){
        
    $screen = get_current_screen();                     
    $tax = (!empty($screen->taxonomy)) ? ' '.sanitize_html_class($screen->taxonomy) : '';

    $class .= $tax;

    return $class;
}
add_filter('admin_body_class', 'frl_admin_body_class');

By default WordPress assigns many useful classes to the tag in the admin area. In particular, they allow us distinct user roles, or locales in admin. But sometimes it's not enough. For example, it does not allow us to use distinct screens for different custom taxonomies, so that if you have to alter styles for a particular taxonomy screen it could be tricky. Filter admin_body_class allows us to solve the problem by adding the taxonomy name to the tags classes.

5. Disable WYSIWYG Editor

add_filter('user_can_richedit', '__return_false');

That's again a very short tip. But very powerful. Actually, I would not like to open a discussion about visual editors being evil or about that making people learn a couple of HTML tags may not be so difficult as it seems. I just like to show that you have such a power and apart from that bring your attention to a simple but elegant built-in function __return_false. As you can guess from the name, the function just returns 'false' and could be used with some WordPress filters in a demonstrated way.

6. Custom set of default quick tags

function frl_quicktags_settings($qt_init, $editor_id){
   
    if($editor_id != 'content')
        return $qt_init; //not a default case

    /* default set of values - "strong,em,link,block,del,ins,img,ul,ol,li,code,more,spell,close,fullscreen" */;
    $qt_init['buttons'] = "strong,em,link,block,img,ul,ol,li,code,fullscreen"; ;
    
    return $qt_init;
}
add_filter('quicktags_settings', 'frl_quicktags_settings', 2, 2);

If we are going to use an HTML-editor intensively we can improve the experience with it by adjusting the set of default quick tags buttons. To achieve that we alter the button settings for the editor that has a 'content' id (which is a default id for all post editors) using the quicktags_settings filter.

Quick Tags

7. Change "Enter title here" placeholder

function frl_enter_title_here_filter($label, $post){

    if($post->post_type == 'post')
        $label = __('Enter article\'s title here', 'frl');

    return $label;
}
add_filter('enter_title_here', 'frl_enter_title_here_filter', 2, 2);

The default placeholder that appears in the title edit field for any post type is Enter title here. That may be not applicable for some custom post types. Fortunately, we can change it using the enter_title_here filter. We just have to be careful and check before filtering that we are on the correct post type screen.

Title Here Placeholder

8. Display additional warning message after post saving

function frl_on_save_post($post_id, $post) {/* add warning filter when saving post */

    if($post->post_type == 'post') //test for something real here       
        add_filter('redirect_post_location', 'frl_custom_warning_filter');

}
add_action('save_post', 'frl_on_save_post', 2, 2);

function frl_custom_warning_filter($location) { /* filter redirect location to add warning parameter*/

    $location = add_query_arg(array('warning'=>'my_warning'), $location);
    return $location;
}

function frl_warning_in_notice() { /* print warning message */
        
    if(!isset($_REQUEST['warning']) || empty($_REQUEST['warning']))
        return;
        
    $warnum = trim($_REQUEST['warning']);

    /* possible warnings codes and messages */                  
    $warnings = array(
        'my_warning' => __('This is my truly custom warning!', 'frl')
    );
        
    if(!isset($warnings[$warnum]))
        return; 
    
    echo '<div class="error message"><p><strong>';
    echo $warnings[$warnum];
    echo '</strong></p></div>';
}       
add_action('admin_notices', 'frl_warning_in_notice');

Custom post types, created for a particular installation, very often need to operate with a custom set of metadata. After saving such a post we need to inform the user about the success or error during the operation. At the same time, default WordPress updated notices should be left intact.

Upon saving the post WordPress performs a redirect with a message code added to the URL parameter, that allows it to print the familiar Post updated notice. Actually, we have no choice but to follow the same pattern - add a custom parameter with a message code to a redirect location when saving custom data (using redirect_post_location filter). After that we can print a custom message on the post edit screen based on the code in the URL (using admin_notices action hook).

In our example, we just added a dummy notice every time a post is saved (for the purposes of demonstration only). In a real-world case you'll have to perform some real testing for achieving results that are correct and produce a real message.

Custom Warning

9. Display favicon in admin pages

function frl_add_favicon(){
        
        $favicon_test = ABSPATH. '/favicon.ico'; //use correct path to favicon.ico here
    if(!file_exists($favicon_test))
        return;
        
    $favicon = home_url('favicon.ico'); //use correct url to favicon.ico here
    echo "<link href='{$favicon}' rel='shortcut icon' type='image/x-icon' >";
}
add_action('admin_enqueue_scripts', 'frl_add_favicon', 1);

There are plenty of tutorials teaching you how to add a custom logo inside WordPress admin, but favicon is another tiny but elegant detail that makes the admin area truly customised. The code, actually, is the same that would be used for the front-end, but it should be applied to some action hook inside the admin head, e.g. admin_enqueue_scripts. The snippet assumes that favicon is located in the root of the install, but this could be altered, if needed. You can even use different images as favicons on admin and front-end pages.

Favicon Sample

10. Enable shortlink feature for custom post types

function frl_get_shortlink_filter($shortlink, $id) {

    $post = get_post($id);

    $supported_types = array('page'); //post types to enable shortlink for

    if(empty($post) || !in_array($post->post_type, $supported_types)) //test for supported post type
        return $shortlink;

    return add_query_arg('p', intval($id), home_url());
}
add_filter('pre_get_shortlink', 'frl_get_shortlink_filter', 2, 2);

It's a well known feature of WordPress - shortlink support in format www.domain.com?p=post_id. Every post despite it's type could be accessed with such a URL. In admin we have a Get shortlink button for posts, that produces this type of URL for a particular post. It's useful for sharing and other tasks of cross-posting. By default this button appears only for posts, but we can actually enable it for any other post type.

We use pre_get_shortlink to build the URL and thus force wp_get_shortlink to return an appropriate value. That's enough for WordPress to detect a support of functionality and output the button on the post edit screen. In our example, we enabled this button for pages.

Shortlink Button

If you have found the set of tips above useful you can download them as a separate plug-in. Feel free to adapt it to your needs.

More Resources

Conclusion

This round-up demonstrates the power of WordPress functions and filters that allow every developer to adjust the admin area for specific needs. By combining these little but important tweaks with custom CSS styles and custom branding, we can build a highly personalised interface inside WordPress admin for our clients to benefit from.

Do you have your own approaches to WordPress admin customisation? Let us know in the comments.

Anna Ladoshkina is a developer with a primarily statistical and analytical background so she is interested first of all in approaches and techniques that allow her to implement this knowledge in her web projects.