Skip to content

Instantly share code, notes, and snippets.

@tddewey
Last active August 29, 2015 14:18
Show Gist options
  • Save tddewey/0f19de1e7dc52e9f5bfc to your computer and use it in GitHub Desktop.
Save tddewey/0f19de1e7dc52e9f5bfc to your computer and use it in GitHub Desktop.
Feature grid builder for GeekyLibrary
<?php
/**
* Handle the CPT and related things for the featured items CPT
*
* Example of how this UI looks:
* https://cloudup.com/c3KSu4DhA-M
* https://cloudup.com/cj1lZPzRLcq
*
* Class GL_Featured_Items
*/
class GL_Featured_Items {
/**
* The only instance of the GL_Featured_Items Object
* @var GL_Featured_Items
*/
private static $instance;
/**
* Custom Post Type slug
* @var string
*/
public $slug = 'gl-feature-grids';
/**
* Holds the current Tenup_Featured_Item that is being worked on
* @var Tenup_Featured_Item
*/
private $features;
/**
* Returns the main instance.
*
* @return GL_Featured_Items
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new GL_Featured_Items;
self::$instance->setup_actions();
}
return self::$instance;
}
/**
* A dummy constructor.
*
* @return GL_Featured_Items
*/
private function __construct() {
}
/**
* Set up WordPress hooks
*/
function setup_actions() {
add_action( 'init', array( $this, 'setup_post_type' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
add_filter( 'wp_insert_post_data', array( $this, 'post_data' ), null, 2 );
add_filter( 'post_updated_messages', array( $this, 'filter_updated_messages' ) );
add_filter( 'manage_edit-' . $this->slug . '_columns', array( $this, 'add_columns' ) );
add_action( 'manage_' . $this->slug . '_posts_custom_column', array( $this, 'render_columns' ), null, 2 );
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
add_action( 'wp_ajax_feature-grid-search', array( $this, 'ajax_feature_grid_search' ) );
add_action( 'wp_ajax_feature_edit_fields', array( $this, 'ajax_feature_edit_fields' ) );
}
/**
* Set up Post Type
*/
function setup_post_type() {
register_post_type( $this->slug, array(
'labels' => array(
'name' => 'Feature Grids',
'singular_name' => 'Feature Grid',
'menu_name' => 'Feature Grids',
'all_items' => 'All Grids',
'add_new_item' => 'Add New Grid',
'edit_item' => 'Edit Grid',
'view_item' => 'View Grid',
'search_items' => 'Search Grids',
'not_found' => 'No grids found',
'not_found_in_trash' => 'No grids found in trash'
),
'description' => 'A single feature grid is used on the home page to display several items in a grid',
'public' => false,
'show_ui' => true,
'menu_icon' => 'dashicons-slides',
'supports' => false
) );
}
function add_meta_boxes() {
$this->features = new Tenup_Featured_Item( get_the_ID() );
foreach ( array( 'Secondary', 'Feature', 'Tertiary' ) as $index ) {
add_meta_box( 'featured-' . sanitize_key( $index ), $index, array( $this, 'feature_meta_box' ), $this->slug, 'normal', 'high', array( 'index' => $index ) );
}
}
/**
* Display the meta box for features
*
* @param $post
* @param $metabox
*/
function feature_meta_box( $post, $metabox ) {
$index = $metabox['args']['index'];
$index_key = sanitize_key( $index );
$post_id = $this->features->get_position_field( 'post_id', $index_key );
?>
<label for="gl-feature-<?php echo $index_key; ?>-post_id">Linked to this content:</label>
<input id="gl-feature-<?php echo $index_key; ?>-post_id" type="hidden" name="gl-feature[<?php echo $index_key; ?>][post_id]" class="select2" style="min-width: 50%" value="<?php echo $post_id; ?>" data-title="<?php echo esc_attr( get_post_field( 'post_title', $post_id, 'raw' ) ); ?>">
<div class="feature-edit-fields" data-index="<?php echo $index_key; ?>" style="<?php echo empty( $post_id ) ? 'display:none' : ''; ?>">
<?php echo $this->get_feature_edit_fields( $index ); ?>
</div>
<?php
wp_nonce_field( 'feature-edit-' . $post->ID, 'gl_feature_editor_nonce' );
}
/**
* Get the fields that can be edited.
* In a separate function so it can be ajax'ed in
*/
function get_feature_edit_fields( $position_index, $post_id_override = 0 ) {
$index_key = sanitize_key( $position_index );
if ( empty( $post_id_override ) ) {
$post_id = $this->features->get_position_field( 'post_id', $index_key );
} else {
$post_id = $post_id_override;
}
$saved_data = $this->features->get_position_data( $index_key );
$data = wp_parse_args( $saved_data, array(
'display' => 'image',
'quote' => '',
'citation' => '',
'title' => get_the_title( $post_id ),
'img_id' => get_post_thumbnail_id( $post_id ),
), $saved_data );
ob_start();
?>
<div class="tabbed-area">
<input class="tab" id="gl-feature-<?php echo $index_key; ?>-display-image" type="radio" name="gl-feature[<?php echo $index_key; ?>][display]" value="image" <?php checked( 'image', $data['display'] ); ?> data-tab="image">
<label class="tab" for="gl-feature-<?php echo $index_key; ?>-display-image">Display as Image</label>
<input class="tab" id="gl-feature-<?php echo $index_key; ?>-display-quote" type="radio" name="gl-feature[<?php echo $index_key; ?>][display]" value="quote" <?php checked( 'quote', $data['display'] ); ?> data-tab="quote">
<label class="tab" for="gl-feature-<?php echo $index_key; ?>-display-quote">Display as Quote</label>
<div class="tab-content image">
<span class="image-wrap">
<?php
$img_id = $this->features->get_position_field( 'img_id', $index_key );
if ( ! empty( $img_id ) ) {
echo wp_get_attachment_image( $img_id, 'bitty-book' );
} else {
echo '<span class="no-image">No featured image</span>';
}
?>
</span>
<input size="5" type="hidden" name="gl-feature[<?php echo $index_key; ?>][img_id]" value="<?php echo esc_attr( $data['img_id'] ); ?>">
<label>Title
<input type="text" name="gl-feature[<?php echo $index_key; ?>][title]" value="<?php echo esc_attr( $data['title'] ) ?>"></label>
</div>
<div class="tab-content quote">
<p>
<label for="gl-feature-<?php echo $index_key; ?>-quote">Quote:</label>
<textarea id="gl-feature-<?php echo $index_key; ?>-quote" name="gl-feature[<?php echo $index_key; ?>][quote]"><?php echo esc_textarea( $data['quote'] ); ?></textarea>
</p>
<p>
<label>Cite:
<input type="text" name="gl-feature[<?php echo $index_key; ?>][citation]" value="<?php echo esc_attr( $data['citation'] ); ?>"></label>
</p>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* Enqueue additional scripts needed for this class
*/
function admin_enqueue_scripts() {
if ( get_post_type() !== $this->slug ) {
return;
}
wp_enqueue_style( 'select2', get_template_directory_uri() . '/assets/js/vendor/select2/select2.css' );
}
/**
* Hook in just before saving data to the database so we can manipulate the post_content
*
* @param $data array
* @param $postarr array
*
* @return array
*/
function post_data( $data, $postarr ) {
if ( get_post_type() !== $this->slug ) {
return $data;
}
if ( ! wp_verify_nonce( $_POST['gl_feature_editor_nonce'], 'feature-edit-' . get_the_ID() ) ) {
return $data;
};
// set post_content to be some JSON from $_POST
if ( isset( $_POST['gl-feature'] ) ) {
$escaped_data = $_POST['gl-feature'];
$data['post_content'] = wp_slash( json_encode( $escaped_data ) ); // note wp_insert_post does sanitization
}
return $data;
}
/**
* Filter updated messages
* @param $messages
*
* @return mixed
*/
function filter_updated_messages( $messages ) {
global $post;
if ( $this->slug !== get_post_type( $post ) ) {
return $messages;
}
$preview_url = esc_url( add_query_arg( 'preview', $post->ID, home_url() ) );
$messages['post'] = array(
0 => '', // Unused. Messages start at index 1.
1 => sprintf( 'Grid updated. <a href="%s">View homepage with this grid</a>', $preview_url ),
2 => __( 'Custom field updated.' ),
3 => __( 'Custom field deleted.' ),
4 => 'Grid updated.',
5 => isset( $_GET['revision'] ) ? sprintf( 'Grid restored to revision from %s', wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
6 => sprintf( 'Grid published. <a href="%s">View homepage with this grid</a>', $preview_url ),
7 => __( 'Grid saved.' ),
8 => sprintf( 'Grid submitted. <a target="_blank" href="%s">Preview homepage with this grid</a>', $preview_url ),
9 => sprintf( 'Grid scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview homepage with this grid</a>',
date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), $preview_url ),
10 => sprintf( 'Post draft updated. <a target="_blank" href="%s">Preview homepage with this grid</a>', $preview_url ),
);
return $messages;
}
/**
* Filter the columns on the edit post screen
*
* @param $columns array
*
* @return array
*/
function add_columns( $columns ) {
$columns = array(
'cb' => '<input type="checkbox">',
'gl-preview' => 'Preview',
'date' => _x( 'Date', 'column name' ),
);
return $columns;
}
/**
* Display our custom column's content
*
* @param $column_name string
* @param $post_id integer
*/
function render_columns( $column_name, $post_id ) {
if ( $column_name !== 'gl-preview' ) {
return;
}
// Get all the images for the given post ID
$feature = new Tenup_Featured_Item( $post_id );
if ( $feature->featured_items ) {
$image_ids = wp_list_pluck( $feature->featured_items, 'img_id' );
foreach ( $image_ids as $img_id ) {
if ( empty( $img_id ) ) {
echo '[no preview img]';
}
echo wp_get_attachment_image( $img_id, 'bitty-book' );
}
}
/**
* Steal the bit from wp-posts-list-table for post locking
*/
$lock_holder = wp_check_post_lock( get_the_ID() );
if ( $lock_holder ) {
$lock_holder = get_userdata( $lock_holder );
}
if ( current_user_can( 'edit_post', get_the_ID() ) && get_post_status( get_the_ID() ) != 'trash' ) {
if ( $lock_holder ) {
$locked_avatar = get_avatar( $lock_holder->ID, 18 );
$locked_text = esc_html( sprintf( __( '%s is currently editing' ), $lock_holder->display_name ) );
} else {
$locked_avatar = $locked_text = '';
}
echo '<div class="locked-info"><span class="locked-avatar">' . $locked_avatar . '</span> <span class="locked-text">' . $locked_text . "</span></div>\n";
}
/**
* Steal the bit from wp-posts-list-table for post status
*/
if ( get_post_status( get_the_ID() ) === 'draft' ) {
echo '<strong> &mdash; Draft</strong>';
}
if ( get_post_status( get_the_ID() ) === 'future' ) {
echo '<strong> &mdash; Scheduled</strong>';
}
if ( get_post_status( get_the_ID() ) === 'pending' ) {
echo '<strong> &mdash; Pending Review</strong>';
}
$this->row_actions( $post_id );
}
/**
* Echo the row actions for this CPT.
*
* This code is taken from WP_List_Table. Unfortunately WP only generates the
* $actions array when it's dealing with the 'title' column since we removed the
* title column, we need to generate our own $actions array, pass it to the
* globalized $wp_list_table object's row_actions method. It's ridiculous.
*
* @param $post_id
*/
private function row_actions( $post_id ) {
global $wp_list_table;
$actions = array();
$post = get_post( $post_id );
$can_edit_post = current_user_can( 'edit_post', $post_id );
$post_type_object = get_post_type_object( $post->post_type );
$preview_url = esc_url( add_query_arg( 'preview', $post_id, home_url() ) );
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>';
$actions['inline hide-if-no-js'] = '<a href="#" class="editinline" title="' . esc_attr( __( 'Edit this item inline' ) ) . '">' . __( 'Quick&nbsp;Edit' ) . '</a>';
}
if ( current_user_can( '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 . '&amp;action=untrash', $post->ID ) ), 'untrash-post_' . $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>";
}
if ( in_array( $post->post_status, array( 'pending', 'draft', 'future' ) ) ) {
if ( $can_edit_post )
$actions['view'] = '<a href="' . $preview_url . '" rel="permalink">' . __( 'Preview' ) . '</a>';
} elseif ( 'trash' != $post->post_status ) {
$actions['view'] = '<a href="' . $preview_url . '" rel="permalink">' . __( 'View' ) . '</a>';
}
$actions = apply_filters( is_post_type_hierarchical( $post->post_type ) ? 'page_row_actions' : 'post_row_actions', $actions, $post );
echo $wp_list_table->row_actions( $actions );
}
public static function ajax_feature_grid_search() {
$posts = new WP_Query( array(
'posts_per_page' => absint( $_GET['posts_per_page'] ),
'post_type' => array( GL_CPT_Library::CPT_SLUG, 'post', 'page' ),
'paged' => absint( $_GET['paged'] ),
's' => sanitize_text_field( $_GET['s'] ),
'update_term_cache' => false,
'update_post_meta_cache' => false,
) );
$results = array();
if ( $posts->have_posts() ) {
while ( $posts->have_posts() ) {
$posts->the_post();
$results[] = array(
'id' => get_the_ID(),
'text' => get_post_field( 'post_title', get_the_ID(), 'display' )
);
}
}
$response = array(
'results' => $results,
'more' => $posts->max_num_pages > $_GET['paged']
);
wp_send_json( $response );
}
/**
* Get the default field data so it can be ajax loaded
*/
public static function ajax_feature_edit_fields() {
$index = $_GET['index'];
$postid = $_GET['postid'];
$default_response = array(
'display' => 'image',
'title' => '',
'img_id' => 0,
'img_preview' => '',
'quote' => '',
'citation' => ''
);
if ( empty( $index ) || empty( $postid ) ) {
wp_send_json_error( $default_response );
}
$response = wp_parse_args( array(
'title' => utf8_encode( get_post_field( 'post_title', $postid, 'display' ) ),
'img_id' => get_post_thumbnail_id( $postid ),
'img_preview' => utf8_encode( get_the_post_thumbnail( $postid, 'bitty-book' ) ),
), $default_response );
wp_send_json_success( $response );
}
}
/**
* Gets the main GL_Featured_Items Instance
*
* Calling this function places into motion the main functions of the class, but can also be utilized to get properties
* and run methods of the class.
*
* @return GL_Featured_Items
*/
function get_gl_featured_items() {
return GL_Featured_Items::get_instance();
}
add_action( 'after_setup_theme', 'get_gl_featured_items' );
/**
* Handle getting information related to featured items. Designed to handle one
* post of the featured items CPT.
*
* Class TenupFeaturedItem
*/
class Tenup_Featured_Item {
/**
* Post object associated with this featured item
* @var WP_Post
*/
var $item_post = '';
/**
* featured item array
* @var array
*/
var $featured_items = '';
/**
* Constructor
*
* @param $post WP_Post or Post ID
*/
function __construct( $post ) {
$this->item_post = get_post( $post );
$this->featured_items = $this->_get_data();
}
/**
* Get all the featured item data for the post
* @return array
*/
private function _get_data() {
if ( $this->item_post->post_content ) {
return stripslashes_deep( json_decode( $this->item_post->post_content, true ) );
} else {
return array();
}
}
/**
* Get an array of positions. For easy use in iterators
*/
public function get_positions() {
return array( 'Feature', 'Secondary', 'Tertiary' );
}
/**
* Get all the data for a given position
*
* @param $position_index mixed
* @param $placeholder_img_in_admin bool display a placeholder image in admin contexts
*
* @return array
*/
public function get_position_data( $position_index, $placeholder_img_in_admin = true ) {
if ( empty( $this->featured_items[sanitize_key( $position_index )] ) ) {
$position_data = array();
} else {
$position_data = $this->featured_items[sanitize_key( $position_index )];
}
return $position_data;
}
/**
* Get the data from a specific field for a specific index
*
* @param $position_field
* @param $position_index
* @param $placeholder_img_in_admin bool display a placeholder image in admin contexts
*
* @return mixed
*/
public function get_position_field( $position_field, $position_index, $placeholder_img_in_admin = true ) {
$data = $this->get_position_data( $position_index, $placeholder_img_in_admin );
if ( ! empty( $data[sanitize_key( $position_field )] ) ) {
return wp_kses_post( wptexturize( stripslashes( $data[sanitize_key( $position_field )] ) ) );
} else {
return '';
}
}
}
<?php
/**
*
* Does a query for the latest published feature grid,
* then pulls each item listed in the grid.
*
*/
<section class="curated-content">
<div class="inner">
<?php
$query_args = array(
'post_type' => get_gl_featured_items()->slug,
'posts_per_page' => 1,
'no_found_rows' => true,
'update_term_cache' => false
);
if ( isset( $_GET['preview'] ) ) {
$query_args['post__in'] = (array) $_GET['preview'];
$query_args['post_status'] = array('any');
$query_args['perm'] = 'readable';
}
$features = new WP_Query( $query_args );
if ( $features->have_posts() ){
$features->the_post();
$feature = new Tenup_Featured_Item( get_the_ID() );
foreach ( array( 'secondary', 'feature', 'tertiary' ) as $position ) {
$position = strtolower( $position );
$display = $feature->get_position_field( 'display', $position );
locate_template( array(
'template-parts/featured-' . $position . '-' . $display . '.php',
'template-parts/featured-' . $position . '.php',
'template-parts/featured-tertiary-image.php'
), true );
}
}
wp_reset_postdata();
?>
</div>
</section>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment