-
-
Save geminorum/6802752 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* Plugin Name: Grist Authors | |
* Description: Handles a special 'Author' post type and co-authors for posts. | |
* Author: Andrew Nacin | |
* Author URI: http://andrewnacin.com/ | |
*/ | |
class Grist_Authors { | |
static function init() { | |
add_filter( 'single_template', array( __CLASS__, 'single_template' ) ); | |
add_action( 'init', array( __CLASS__, 'register_post_type' ) ); | |
add_action( 'add_meta_boxes_author', array( __CLASS__, 'add_meta_boxes_author' ) ); | |
add_action( 'admin_head-post-new.php', array( __CLASS__, 'header_inline' ) ); | |
add_action( 'admin_head-post.php', array( __CLASS__, 'header_inline' ) ); | |
add_action( 'admin_footer-post-new.php', array( __CLASS__, 'footer_inline' ) ); | |
add_action( 'admin_footer-post.php', array( __CLASS__, 'footer_inline' ) ); | |
add_action( 'save_post', array( __CLASS__, 'save_post_type_author' ), 10, 2 ); | |
add_filter( 'request', array( __CLASS__, 'request' ) ); | |
add_filter( 'post_row_actions', array( __CLASS__, 'post_row_actions' ), 10, 2 ); | |
add_filter( 'page_row_actions', array( __CLASS__, 'post_row_actions' ), 10, 2 ); // while hierarchical = true | |
add_filter( 'bulk_actions-edit-author', array( __CLASS__, 'bulk_actions_author' ) ); | |
add_filter( 'manage_post_posts_columns', array( __CLASS__, 'manage_post_posts_columns' ) ); | |
add_filter( 'manage_author_posts_columns', array( __CLASS__, 'manage_author_posts_columns' ) ); | |
add_action( 'manage_post_posts_custom_column', array( __CLASS__, 'manage_post_posts_custom_column' ), 10, 2 ); | |
add_action( 'manage_author_posts_custom_column', array( __CLASS__, 'manage_author_posts_custom_column' ), 10, 2 ); | |
add_action( 'add_meta_boxes_post', array( __CLASS__, 'add_meta_boxes_post' ) ); | |
add_filter( 'wp_insert_post_data', array( __CLASS__, 'wp_insert_post_data' ), 10, 2 ); | |
} | |
/** | |
* Forces author-style templates for the /author/ pages, even though these are a post type. | |
* | |
* author.php, archive.php, index.php is the template hierarchy. | |
*/ | |
static function single_template( $template ) { | |
if ( get_queried_object()->post_type == 'author' ) | |
return locate_template( array( 'author.php', 'archive.php', 'index.php' ) ); | |
return $template; | |
} | |
/** | |
* Adds an 'Authors' column to after the 'title' field. | |
* | |
* The current 'author' column does not need to be removed, as it isn't there. | |
* remove_post_type_support() all standard author UI in our register_post_type() method. | |
*/ | |
static function manage_post_posts_columns( $columns ) { | |
$new_columns = array(); | |
foreach ( $columns as $column_key => $column_name ) { | |
$new_columns[ $column_key ] = $column_name; | |
if ( $column_key == 'title' ) | |
$new_columns['grist_author'] = 'Authors'; | |
} | |
return $new_columns; | |
} | |
/** | |
* Render the 'Authors' column. | |
* | |
* This caches all author data for all posts on the first run. | |
*/ | |
static function manage_post_posts_custom_column( $column, $post_id ) { | |
global $wp_query; | |
static $cached_users = false; | |
if ( ! $cached_users ) { | |
$pids = array(); | |
foreach ( $wp_query->posts as $post ) { | |
$pids[] = $post->ID; | |
} | |
$author_ids = array(); | |
foreach ( $pids as $pid ) { | |
$author_ids = array_merge( $author_ids, (array) get_post_meta( $pid, '_grist_author_id', false ) ); | |
} | |
// We don't care about the return value here, only that the posts end up | |
// in the cache for future get_post() calls. | |
get_posts( array( 'post_type' => 'author', 'include' => $author_ids, 'nopaging' => true ) ); | |
unset( $author_ids, $pid, $pids, $post ); | |
$cached_users = true; | |
} | |
switch ( $column ) { | |
case 'grist_author' : | |
$post = get_post( $post_id ); | |
$authors = get_post_meta( $post_id, '_grist_author_id', false ); | |
$output = array(); | |
foreach ( $authors as $author_id ) { | |
$author = get_post( $author_id ); | |
if ( $author->ID == $post->post_author ) { | |
echo '<strong>' . esc_html( $author->post_title ) . '</strong><br />'; | |
} else { | |
if ( $author->post_author ) { | |
$output[] = '<a href="' . esc_url( add_query_arg( 'author', $author->post_author ) ) . '">' . esc_html( $author->post_title ) . '</a>'; | |
} else { | |
$output[] = esc_html( $author->post_title ); | |
} | |
} | |
} | |
if ( $output ) | |
echo implode( "<br />\n", $output ); | |
echo '<br /> '; | |
break; | |
} | |
} | |
/** | |
* Adds 'Twitter' and 'Linked User Account' columns to the Authors (post type) list table. | |
*/ | |
static function manage_author_posts_columns( $columns ) { | |
unset( $columns['date'] ); | |
$columns['twitter'] = 'Twitter'; | |
$columns['user_account'] = 'Linked User Account'; | |
return $columns; | |
} | |
/** | |
* Renders the 'Twitter' and 'Linked User Account' columns for the Authors (post type) list table. | |
*/ | |
static function manage_author_posts_custom_column( $column, $post_id ) { | |
switch ( $column ) { | |
case 'twitter' : | |
$twitter = get_post_meta( $post_id, '_grist_author_twitter', true ); | |
if ( $twitter ) | |
echo '<a href="' . esc_url( 'http://twitter.com/' . $twitter ) . '">@' . esc_html( $twitter ) . '</a>'; | |
break; | |
case 'user_account' : | |
if ( ! get_the_author_meta( 'ID' ) ) | |
break; | |
// Provide direct links to the user on users.php if we can. | |
if ( current_user_can( 'list_users' ) ) { | |
$user_link = add_query_arg( 's', urlencode( get_the_author_meta( 'user_login' ) ), admin_url( 'users.php' ) ) . '#user-' . get_the_author_meta( 'ID' ); | |
echo '<a href="' . esc_url( $user_link ) . '">' . get_the_author() . '</a>'; | |
} else { | |
the_author(); | |
} | |
break; | |
} | |
} | |
/** | |
* For authors (post type), no bulk actions. | |
*/ | |
static function bulk_actions_author( $actions ) { | |
return array(); | |
} | |
/** | |
* For authors (post type), no quick edit, trash, or delete action links. | |
*/ | |
static function post_row_actions( $actions, $post ) { | |
if ( $post->post_type == 'author' ) | |
unset( $actions['inline hide-if-no-js'], $actions['trash'], $actions['delete'] ); | |
return $actions; | |
} | |
/** | |
* If core's /author/$author/ rewrite rule gets hit, catch it and serve up the post type instead. | |
*/ | |
static function request( $qvs ) { | |
if ( ! is_admin() && isset( $qvs['author_name'] ) ) { | |
$qvs['post_type'] = 'author'; | |
$qvs['name'] = $qvs['author_name']; | |
unset( $qvs['author_name'] ); | |
} | |
return $qvs; | |
} | |
/** | |
* Register our author post type and removes support for 'author' from the post post_type. | |
*/ | |
static function register_post_type() { | |
$labels = array( | |
'name' => 'Authors', | |
'singular_name' => 'Author', | |
'add_new' => 'Add Author', | |
'add_new_item' => 'Add New Author', | |
'edit_item' => 'Edit Author', | |
'new_item' => 'New Author', | |
'view_item' => 'View Author', | |
'search_items' => 'Search Authors', | |
'not_found' => 'No author found', | |
'not_found_in_trash' => 'Sorry, no authors found in the trash', | |
); | |
$args = array( | |
'labels' => $labels, | |
'public' => true, | |
'show_ui' => true, | |
'rewrite' => true, | |
'query_var' => 'author_name', | |
'has_archive' => false, | |
'hierarchical' => true, // hack, so wp_dropdown_pages() works. | |
'capability_type' => 'page', | |
'map_meta_cap' => true, | |
'supports' => array( 'title', 'editor', 'thumbnail' ), | |
'show_in_menu' => 'users.php', | |
); | |
register_post_type( 'author', $args ); | |
remove_post_type_support( 'post', 'author' ); | |
} | |
/** | |
* Add a meta box to our author post type. | |
* | |
* This box ends up holding Twitter and WP.com fields. | |
*/ | |
static function add_meta_boxes_author( $post ) { | |
add_meta_box( 'grist_author_meta', 'Grist Author Meta', array( __CLASS__, 'render_meta_box_author' ), null, 'side', 'high' ); | |
} | |
/** | |
* Adds a meta box to posts for co-author assignments. | |
*/ | |
static function add_meta_boxes_post( $post ) { | |
add_meta_box( 'grist_co_authors', 'Authors', array( __CLASS__, 'render_meta_box_post' ), null, 'side', 'low' ); | |
} | |
/** | |
* Render the co-authors meta box. | |
*/ | |
static function render_meta_box_post( $post ) { | |
$authors = get_post_meta( $post->ID, '_grist_author_id', false ); | |
$show_option_none = false; | |
if ( ! $authors ) { | |
$authors = Grist_Authors::get_author_id_by_user( get_current_user_id() ); | |
if ( ! $authors ) | |
$show_option_none = '(no author)'; | |
$authors = array( $authors ); | |
} | |
$authors[] = 0; | |
$primary = array_shift( $authors ); | |
wp_dropdown_pages( array( | |
'show_option_none' => $show_option_none, | |
'selected' => $primary, | |
'name' => 'grist_author_id[]', | |
'id' => 'grist_author_id_0', | |
'post_type' => 'author', | |
'echo' => 1, | |
'sort_column' => 'post_title', | |
) ); | |
$i = 1; | |
echo '<div class="grist-co-authors"><p>Co-authors:</p>'; | |
foreach ( $authors as $author_id ) { | |
echo '<div>'; | |
wp_dropdown_pages( array( | |
'show_option_none' => '(no co-author)', | |
'selected' => $author_id, | |
'name' => 'grist_author_id[]', | |
'id' => 'grist_author_id_' . $i, | |
'post_type' => 'author', | |
'echo' => 1, | |
'sort_column' => 'post_title', | |
) ); | |
echo '<a href="#" class="author-remove">Remove</a>'; | |
echo '</div>'; | |
$i++; | |
} | |
echo '</div><p><a href="#" id="author-add">Add</a></p>'; | |
} | |
/** | |
* Utility function fetches an author ID (post type) when given a WP.com user ID. | |
*/ | |
static function get_author_id_by_user( $user_id = null ) { | |
if ( empty( $user_id ) ) | |
$user_id = get_current_user_id(); | |
$query = new WP_Query( array( | |
'post_type' => 'author', | |
'author' => $user_id, | |
'post_status' => 'publish', | |
'posts_per_page' => 1, | |
'update_post_term_cache' => false, | |
'update_post_meta_cache' => false, | |
'no_found_rows' => true, | |
'fields' => 'ids', | |
) ); | |
if ( isset( $query->posts[0] ) ) | |
return $query->posts[0]; | |
return 0; | |
} | |
/** | |
* Renders the meta box for authors (post type) to hold Twitter and WP.com user information. | |
*/ | |
static function render_meta_box_author( $post ) { | |
$twitter = get_post_meta( $post->ID, '_grist_author_twitter', true ); | |
echo '<p><label>Twitter Handle</label> <input style="width:200px" type="text" value="' . esc_attr( $twitter ) . '" name="grist_author[twitter]" /></p>'; | |
echo '<p><label>WP.com User</label> '; | |
wp_dropdown_users( array( | |
'show_option_none' => '(No corresponding user)', | |
'name' => 'grist_author[author]', | |
// If we're adding an author or if there is no post author (0), then use -1 (which is show_option_none). | |
// We then take -1 on save and convert it back to 0. (#blamenacin) | |
'selected' => 'auto-draft' == $post->post_status || ! $post->post_author ? -1 : $post->post_author, | |
) ); | |
echo '</p>'; | |
} | |
/** | |
* Drops in some JS on the post edit screen to handle the co-authors UI. | |
*/ | |
static function footer_inline() { | |
$screen = get_current_screen(); | |
if ( 'post' !== $screen->post_type ) | |
return; | |
?> | |
<script> | |
jQuery(document).ready( function($) { | |
var coauthors = $('.grist-co-authors'); | |
$('#author-add').click( function(e) { | |
e.preventDefault(); | |
coauthors.find('select').first().parent().clone() | |
.find('select').val('').end() | |
.find('.author-remove').show().end() | |
.appendTo( coauthors ); | |
}); | |
coauthors.delegate( '.author-remove', 'click', function(e) { | |
e.preventDefault(); | |
if ( coauthors.find('select').length > 1 ) | |
$(this).parent().remove(); | |
else | |
$(this).prev().val(''); | |
}); | |
}); | |
</script> | |
<?php | |
} | |
/** | |
* Adds some information to the featured image box for authors (post type) | |
* that clarifies what size we're looking for. | |
*/ | |
static function admin_post_thumbnail_html( $content ) { | |
$content .= '<p>(50 pixels × 50 pixels, please.)</p>'; | |
return $content; | |
} | |
/** | |
* Some elegant (and less elegant) hacks for the author edit screen. | |
* | |
* - Turns off the visual editor, disables the upload/insert media button. | |
* - Auto-corrects $parent_file and $submenu_file, though changes in 3.3 should make these redundant. (See #WP19125) | |
* - Hides the delete link (it doesn't need to be prevented with a cap check, only discouraged). | |
* - Fixes positioning and styling of the editor. Not sure if late changes in 3.3 made these unnecessary. | |
*/ | |
static function header_inline() { | |
$screen = get_current_screen(); | |
switch ( $screen->post_type ) : | |
case 'author' : | |
add_filter( 'admin_post_thumbnail_html', array( __CLASS__, 'admin_post_thumbnail_html' ) ); | |
add_filter( 'user_can_richedit', '__return_false' ); | |
remove_action( 'media_buttons', 'media_buttons' ); | |
$GLOBALS['parent_file'] = 'users.php'; | |
$GLOBALS['submenu_file'] = "edit.php?post_type=author"; | |
echo '<style>.misc-pub-section, #delete-action { display: none } #content_resize { top: -2px !important } .wp-editor-container { background: #fff } </style>' . "\n"; | |
break; | |
endswitch; | |
} | |
/** | |
* Saving the author post type, specifically Twitter. The WP.com user linkage is handled by | |
* our wp_insert_post_data() method. | |
*/ | |
static function save_post_type_author( $post_id, $post ) { | |
if ( 'author' != $post->post_type ) | |
return; | |
if ( ! isset( $_POST['grist_author'] ) ) | |
return; | |
$twitter = $_POST['grist_author']['twitter']; | |
// Sanitize all kinds of possible inputs. | |
$twitter = str_replace( array( 'http://', 'https://', '#!', 'twitter.com', '/', '@' ), '', $twitter ); | |
$twitter = sanitize_text_field( $twitter ); | |
update_post_meta( $post->ID, '_grist_author_twitter', $twitter ); | |
} | |
/** | |
* Saving the post and author post types, specifically stuff related to user IDs. | |
* | |
* We need to do this on wp_insert_post_data rather than save_post so have raw, | |
* unadulterated access to post_author. | |
*/ | |
static function wp_insert_post_data( $post, $args ) { | |
switch ( $post['post_type'] ) : | |
case 'post' : | |
// New posts have an ID, this just prevents this from running on auto-draft creation. | |
if ( ! isset( $args['ID'] ) ) | |
break; | |
// If author data was submitted: | |
if ( isset( $_POST['grist_author_id'] ) ) { | |
// Make the authors unique, remove the first one, and consider that the primary. | |
$ids = array_unique( array_filter( array_map( 'absint', $_POST['grist_author_id'] ) ) ); | |
$primary = array_shift( $ids ); | |
$author_object = get_post( $primary ); | |
// If we have a primary author that has a corresponding WP.com user ID, then | |
// make the post's post_author keep the WP.com user ID. (Good karma.) | |
if ( $author_object && $author_object->post_author ) | |
$post['post_author'] = $author_object->post_author; | |
else | |
$post['post_author'] = 0; | |
// Wipe them all out so we can re-add in order. | |
delete_post_meta( $args['ID'], '_grist_author_id' ); | |
// Add the primary ID to meta, then the rest of them. | |
add_post_meta( $args['ID'], '_grist_author_id', $primary ); | |
foreach ( $ids as $id ) { | |
add_post_meta( $args['ID'], '_grist_author_id', $id ); | |
} | |
} else { | |
// Okay, no author data was submitted. | |
// Figure out what is in the DB and do not lose the linked WP.com user. | |
// This happens, say, during a quick edit. | |
$from_db = get_post( $args['ID'] ); | |
if ( $from_db ) | |
$post['post_author'] = $from_db->post_author; | |
else | |
$post['post_author'] = 0; | |
} | |
break; | |
case 'author' : | |
// Don't run for auto-drafts. (New posts have IDs.) | |
if ( ! isset( $args['ID'] ) ) | |
break; | |
// First, figure out what we have in the DB as the linked WP.com user. | |
$from_db = get_post( $args['ID'] ); | |
if ( $from_db ) | |
$post['post_author'] = intval( $from_db->post_author ); | |
else | |
$post['post_author'] = 0; | |
// If data was passed on save, then use it. But if post_author was -1 | |
// (which is what the dropdowns use for nothing selected), we can't store | |
// that in an unsigned int. Clarify we want 0 for no author. | |
if ( isset( $_POST['grist_author'] ) ) { | |
$post['post_author'] = intval( $_POST['grist_author']['author'] ); | |
if ( $post['post_author'] < 0 ) | |
$post['post_author'] = 0; | |
} | |
break; | |
endswitch; | |
return $post; | |
} | |
} | |
// Go. | |
Grist_Authors::init(); | |
/* | |
TEMPLATE TAGS | |
*/ | |
/** | |
* Grist Byline filtering. | |
* | |
* @param string $before text before the author name | |
* @param string $after text after the author name | |
* @param array|string $args optional attributes for the link | |
* @param string $prefix text before the link | |
* Use this template tag for bylines: | |
* <p class="byline"><?php grist_byline(); ?></p> | |
*/ | |
function grist_byline( $before = '', $after = '', $args = array(), $prefix = 'By ' ) { | |
$author_ids = get_post_meta( get_the_ID(), '_grist_author_id', false ); | |
if ( empty( $author_ids ) ) | |
return; | |
if( !empty( $args )) | |
{ | |
$args = _parse_html_attributes( $args ); | |
} | |
$output = array(); | |
foreach ( $author_ids as $author_id ) { | |
$author_name = $before . get_the_title( $author_id ) . $after; | |
$output[] = sprintf( '<a href="%s" title="Posts by %s" %s >%s</a>', esc_url( get_permalink( $author_id ) ), esc_attr( $author_name ), $args ,$author_name ); | |
} | |
echo $prefix . wp_sprintf( '%l', $output ); | |
} | |
function grist_the_author_bios( $before = '<div>', $after = '</div>' ) { | |
if ( get_post_type() == 'author' ) | |
$author_ids = array( get_the_ID() ); | |
else | |
$author_ids = get_post_meta( get_the_ID(), '_grist_author_id', false ); | |
foreach ( $author_ids as $author_id ) { | |
$author = get_post( $author_id ); | |
echo $before . apply_filters('the_content', $author->post_content) . $after . "\n\n"; | |
} | |
} | |
function grist_has_multiple_authors() { | |
return 1 < count( (array) get_post_meta( get_the_ID(), '_grist_author_id', false ) ); | |
} | |
function grist_get_the_author($post_id='') { | |
$id = ($post_id) ?: get_the_ID(); | |
$author_id = 'author' == get_post_type($id) ? $id : get_post_meta( $id, '_grist_author_id', true ); | |
if( !$author_id) | |
return 'Grist'; | |
return get_the_title( $author_id ); | |
} | |
function grist_get_author_feed_link($post_id='') { | |
$id = ($post_id) ?: get_the_ID(); | |
$author_id = 'author' == get_post_type($id) ? $id : get_post_meta( $id, '_grist_author_id', true ); | |
return user_trailingslashit( get_permalink( $author_id ) . '/feed' ); | |
} | |
// grist_get_author_meta( 'twitter' ) | |
function grist_get_the_author_meta( $meta, $post_id='' ) { | |
$id = ($post_id) ?: get_the_ID(); | |
$author_id = 'author' == get_post_type($id) ? $id : get_post_meta( $id, '_grist_author_id', true ); | |
return get_post_meta( $author_id, '_grist_author_' . $meta, true ); | |
} | |
function grist_get_author_post_id($post_id='') { | |
$id = ($post_id) ?: get_the_ID(); | |
return 'author' == get_post_type($id) ? $id : get_post_meta( $id, '_grist_author_id', true ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment