Making Custom Content
Management Disappear
into the WordPress Admin
Helen Hou-Sandí
WordCamp NYC 2012
Helen Hou-Sandí
WordCamp NYC 2012
User Experience Engineer with 10up, LLC
@helenhousandi on Twitter
WordPress Core Contributor *
This dashed border tells us … that I am a developer, not a designer.mdawaffe, WCSF 2011
// Admin menu icon
register_post_type( 'event', array(
...
'menu_icon' => get_template_directory_uri() . '/images/icon-16.png';
));
// Screen icon
add_action( 'admin_head', 'hhs_event_screen_icon' );
function hhs_event_screen_icon() {
$post_type = get_current_screen()->post_type;
if ( 'event' != $post_type )
return;
?>
<style type="text/css">
.icon32.icon32-posts-event {
background: url(<?php echo get_template_directory_uri(); ?>/images/icon-32.png) !important;
}
</style>
<?php
}
// Don't set anything for the menu_icon arg in register_post_type()
add_action( 'admin_head', 'hhs_event_admin_menu_icon' );
function hhs_event_admin_menu_icon() {
?>
<style type="text/css">
#adminmenu #menu-posts-event div.wp-menu-image{
background: transparent url(<?php echo get_template_directory_uri(); ?>/images/icon-16.png) no-repeat 6px -17px;
}
#adminmenu #menu-posts-event:hover div.wp-menu-image,
#adminmenu #menu-posts-event.wp-has-current-submenu div.wp-menu-image {
background-position: 6px 7px;
</style>
<?php
}
register_post_type( 'slide', array( ... 'show_in_menu' => 'themes.php', 'show_in_nav_menus' => false, 'supports' => array( 'thumbnail' ) ) );
add_action( 'add_meta_boxes_slide', 'hhs_slide_add_meta_boxes' );
add_filter( 'admin_post_thumbnail_html', 'hhs_slide_post_thumbnail_html' );
function hhs_slide_add_meta_boxes() {
remove_meta_box( 'postimagediv', 'slide', 'side' );
add_meta_box( 'postimagediv', 'Slide Image', 'post_thumbnail_meta_box', 'slide', 'normal', 'high' );
}
function hhs_slide_post_thumbnail_html( $output ) {
global $post_type;
// beware of translated admin
if ( ! empty ( $post_type ) && 'slide' == $post_type ) {
$output = str_replace( 'Set featured image', 'Select / Upload a slide image', $output );
$output = str_replace( 'Remove featured image', 'Remove slide image', $output );
}
return $output;
}
Note: does not work via Ajax. See Trac ticket #20891
add_filter( 'manage_edit-slide_columns', 'hhs_slide_edit_columns' );
add_action( 'manage_slide_posts_custom_column', 'hhs_slide_custom_columns' );
function hhs_slide_edit_columns( $columns ) {
$columns = array(
'cb' => '<input type="checkbox" />',
'thumbnail' => 'Slide',
);
return $columns;
}
function hhs_slide_custom_columns( $column ) {
global $post;
switch ($column) {
case 'thumbnail' :
if ( has_post_thumbnail( $post->ID ) ) { // the current post has a thumbnail
the_post_thumbnail( $post->ID );
}
else { // the current post lacks a thumbnail
?>
No image
<?php
}
// add row_action links for Edit and Trash because there's no title column
$post_type_object = get_post_type_object( $post->post_type );
$can_edit_post = current_user_can( $post_type_object->cap->edit_post, $post->ID );
$always_visible = false; // change to true to make it always show instead of on hover
$actions = array();
if ( $can_edit_post && 'trash' != $post->post_status ) {
$actions['edit'] = '<a href="' . get_edit_post_link( $post->ID, true ) . '" title="' . esc_attr( __( 'Edit this item' ) ) . '">' . __( 'Edit' ) . '</a>';
}
if ( current_user_can( $post_type_object->cap->delete_post, $post->ID ) ) {
if ( 'trash' == $post->post_status )
$actions['untrash'] = "<a title='" . esc_attr( __( 'Restore this item from the Trash' ) ) . "' href='" . wp_nonce_url( admin_url( sprintf( $post_type_object->_edit_link . '&action=untrash', $post->ID ) ), 'untrash-' . $post->post_type . '_' . $post->ID ) . "'>" . __( 'Restore' ) . "</a>";
elseif ( EMPTY_TRASH_DAYS )
$actions['trash'] = "<a class='submitdelete' title='" . esc_attr( __( 'Move this item to the Trash' ) ) . "' href='" . get_delete_post_link( $post->ID ) . "'>" . __( 'Trash' ) . "</a>";
if ( 'trash' == $post->post_status || !EMPTY_TRASH_DAYS )
$actions['delete'] = "<a class='submitdelete' title='" . esc_attr( __( 'Delete this item permanently' ) ) . "' href='" . get_delete_post_link( $post->ID, '', true ) . "'>" . __( 'Delete Permanently' ) . "</a>";
}
$action_count = count( $actions );
$i = 0;
$out = '<div class="' . ( $always_visible ? 'row-actions-visible' : 'row-actions' ) . '">';
foreach ( $actions as $action => $link ) {
++$i;
( $i == $action_count ) ? $sep = '' : $sep = ' | ';
$out .= "<span class='$action'>$link$sep</span>";
}
$out .= '</div>';
echo $out;
break;
}
}

// can also use callback in register_post_type() instead
register_post_type( 'event', array(
...
'register_meta_box_cb' => 'hhs_event_add_meta_boxes' ),
)
);
function hhs_event_add_meta_boxes( $post ) {
add_meta_box( 'hhs-event-date', 'Event Information', 'hhs_event_datepicker_meta_box', 'event', 'side', 'default' );
}
function hhs_event_datepicker_meta_box( $post ) {
$event_date = get_post_meta( $post->ID, 'hhs_event_date', true );
// mid-page enqueueing came in 3.3
wp_enqueue_script( 'jquery-ui-datepicker' );
wp_nonce_field( plugin_basename(__FILE__), 'hhs_eventmeta_nonce' );
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('.datepicker').datepicker({
dateFormat : 'yy-mm-dd'
});
});
</script>
<p><label for="hhs_event_date">Event Date</label><br />
<input type="text" class="datepicker" name="hhs_event_date" id="hhs_event_date"<?php if( $event_date ) echo ' value="' . esc_attr( $event_date) . '"'; ?> /></p>
<?php
}
add_filter( 'manage_edit-events_columns', 'hhs_event_edit_columns' );
add_action( 'manage_event_posts_custom_column', 'hhs_event_custom_columns' );
function hhs_event_edit_columns( $columns ) {
// relabel title column
$columns['title'] = 'Event Title';
// remove published date column
unset( $columns['date'] );
// append new columns
$columns['event-date'] = 'Event Date';
return $columns;
}
function hhs_event_custom_columns( $column ) {
global $post;
$event_date = get_post_meta( $post->ID, 'hhs_event_date', true );
switch ( $column ) {
case 'event-date' :
// escape as appropriate
// Idea: Use get_option( 'date_format' ) to format the date instead :)
echo esc_html( $event_date );
break;
}
}
add_filter( 'manage_edit-event_sortable_columns', 'hhs_event_sortable_columns' );
add_action( 'load-edit.php', 'hhs_event_edit_load' );
function hhs_event_sortable_columns( $columns ) {
$columns['event-date'] = 'event-date';
return $columns;
}
function hhs_event_edit_load() {
add_filter( 'request', 'hhs_event_sort' );
}
function hhs_event_sort( $vars ) {
// Check post type and for the orderby request
if ( isset( $vars['post_type'] ) && 'event' == $vars['post_type'] &&
isset( $vars['orderby'] ) && 'event-date' == $vars['orderby'] ) {
// Merge in with the rest of the query vars
$vars = array_merge(
$vars,
array(
'meta_key' => 'hhs_event_date',
'orderby' => 'meta_value'
)
);
}
return $vars;
}
Using regular post thumbnails and the Multiple Post Thumbnails plugin to add two more sizes/orientations
function hhs_bmpt_metabox( $post ) {
wp_nonce_field( basename(__FILE__), 'hhs_bmpt_nonce' );
$id = get_post_meta( $post->ID, '_thumbnail_id', true );
$img = false;
if ( $id )
$img = wp_get_attachment_image_src( $id, 'post-thumbnail' );
?>
<div class="item" data-size="post-thumbnail">
<input type="hidden" name="_thumbnail_id" value="<?php if ( $id ) echo esc_attr( $id ); ?>" />
<h4>Post Thumbnail</h4>
<div class="image">
<?php if ( $img ) :?>
<img src="<?php echo esc_url( $img[0] ); ?>" />
<?php else : ?>
<img src="" class="screen-reader-text" />
<?php endif; ?>
</div>
<p>
<a href="#" class="upload-image">Upload / select image</a><br />
<a href="#" class="remove-image<?php if ( ! $img ) echo ' screen-reader-text'; ?>">Remove image</a>
</p>
<p class="desc">This is the regular post thumbnail.</p>
</div>
<?php
}
jQuery(document).ready(function($){
$('.upload-image').on('click', function(e){
e.preventDefault();
var item = $(this).closest('.item'),
post_id = $('input#post_ID').val(),
preview_size = item.data('size');
window.image_item = item;
tb_show('Select image', 'media-upload.php?post_id=' + post_id + '&type=image&meta_type=image&meta_preview_size=' + preview_size + 'TB_iframe=1');
});
$('.remove-image').on('click', function(e){
e.preventDefault();
var $this = $(this),
div = $this.closest('.item');
div.find('input').attr('value', '');
div.find('img').attr('src', '').addClass('screen-reader-text');
$this.addClass('screen-reader-text');
});
});
<?php
// Insert some CSS and JS in the head of the Thickbox to simplify
add_action( 'admin_head-media-upload-popup', 'hhs_bmpt_thickbox_head' );
function hhs_bmpt_thickbox_head() {
// generally hide the post thumbnail selection link
if ( ! isset( $_GET['meta_type'] ) || 'image' !== $_GET['meta_type'] ) :
?>
<style type="text/css">
tr.submit .wp-post-thumbnail {
display: none;
}
</style>
<?php
// hide those links and a bunch of other things
else :
$preview_size = isset( $arr_postinfo['meta_preview_size'] ) ? sanitize_key( $arr_postinfo['meta_preview_size'] ) : 'post-thumbnail';
?>
<style type="text/css">
#media-upload-header #sidemenu li#tab-type_url,
#gallery-settings,
#gallery-form table.widefat thead,
#gallery-form .menu_order,
#sort-buttons,
.ml-submit,
tr.url,
tr.align,
tr.image_alt,
tr.image-size,
tr.post_title,
tr.post_excerpt,
tr.post_content,
tr.image_alt p,
table thead input.button,
table thead img.imgedit-wait-spin,
tr.submit a.wp-post-thumbnail {
display: none !important;
}
</style>
<script type="text/javascript">
jQuery(document).ready(function($){
$('#media-items').bind('DOMNodeInserted',function(){
$('input[value="Insert into Post"]').each(function(){
$(this).attr('value','Select Image');
});
}).trigger('DOMNodeInserted');
$('form#filter').each(function(){
$(this).append('<input type="hidden" name="meta_preview_size" value="<?php echo esc_js( $preview_size ); ?>" />');
$(this).append('<input type="hidden" name="meta_type" value="image" />');
});
});
</script>
<?php
endif;
}
// Inject JS to show the image thumbnail and set the hidden input value
add_filter( 'media_send_to_editor', 'hhs_bmpt_media_send_to_editor', 15, 2 );
function hhs_bmpt_media_send_to_editor( $html, $id ) {
if( ! isset( $arr_postinfo['meta_type'] ) || $arr_postinfo['meta_type'] !== 'image' )
return $html;
else {
$preview_size = ( ! empty( $arr_postinfo['meta_preview_size'] ) ) ? sanitize_key( $arr_postinfo['meta_preview_size'] ) : 'post-thumbnail';
$file_src = wp_get_attachment_image_src( $id, $preview_size );
$file_src = $file_src[0];
?>
<script type="text/javascript">
self.parent.image_item.find('input').val('<?php echo esc_js( $id ); ?>');
self.parent.image_item.find('img').attr('src','<?php echo esc_js( $file_src ); ?>');
self.parent.image_item.find('img').removeClass('screen-reader-text');
self.parent.image_item.find('.remove-image').removeClass('screen-reader-text');
self.parent.image_item = null;
self.parent.tb_remove();
</script>
<?php
exit;
}
}
add_action( 'add_meta_boxes', 'hhs_add_meta_boxes', 10, 2 );
function hhs_add_meta_boxes( $post_type, $post ) {
$front_page = get_option( 'page_on_front' );
if ( $post->ID === $front_page ) {
add_meta_box( 'hhs_front_page_meta', 'Home Page Content', 'hhs_front_page_meta', 'page', 'normal', 'high' );
// remove the editor
remove_post_type_support( 'page', 'editor' );
}
}
<?php
add_action( 'post_submitbox_misc_actions', 'hhs_post_submitbox_misc_actions' );
function hhs_post_submitbox_misc_actions() {
// restrict post type if needed
if ( 'post' != get_post_type() )
return;
wp_nonce_field( 'hhs_post_submitbox_nonce', plugin_basename(__FILE__) );
// Inline styles needed pre-3.4 - see WCPHX 2012 slides
// But you're using the latest, right?
?>
<div class="misc-pub-section">
<input type="checkbox" name="remove_claim_link" id="remove_claim_link" value="1" <?php checked( get_post_meta( get_the_ID(), 'remove_claim_link', true ) ); ?> />
<label for="remove_claim_link">Claimed</label>
</div>
<div class="misc-pub-section">
<input type="checkbox" name="is_featured" id="is_featured" value="1" <?php checked( get_post_meta( get_the_ID(), 'is_featured', true ) ); ?> />
<label for="is_featured">Featured</label>
</div>
<div class="misc-pub-section">
<input type="checkbox" name="is_premium" id="is_premium" value="1" <?php checked( get_post_meta( get_the_ID(), 'is_premium', true ) ); ?> />
<label for="is_premium">Premium</label>
</div>
<?php
}
// These are actual text, not placeholders - will save if not removed
// Also: default_excerpt
add_filter( 'default_title', 'hhs_event_default_title', 10, 2 );
function hhs_event_default_title( $title, $post ) {
if ( 'event' == $post->post_type )
$title = 'Event Title';
return $title;
}
add_filter( 'default_content', 'hhs_event_default_content', 10, 2 );
function hhs_event_default_content( $content, $post ) {
if ( 'event' == $post->post_type )
$content = 'Event description';
return $content;
}
// This is a placeholder
add_filter( 'enter_title_here', 'hhs_event_enter_title_here', 10, 2 );
function hhs_event_enter_title_here( $placeholder, $post ) {
if ( 'event' == $post->post_type )
$placeholder = 'Event Title';
return $placeholder;
}
Slides online at
http://slides.helenhousandi.com/wcnyc2012.html