-
Erick Hitter authoredb8be7686
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
eth-timeline.php 14.90 KiB
<?php
/*
Plugin Name: ETH Timeline
Plugin URI: https://ethitter.com/plugins/
Description: List whereabouts by year and month
Author: Erick Hitter
Version: 0.2
Author URI: https://ethitter.com/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class ETH_Timeline {
/**
* Singleton
*/
private static $instance = null;
/**
* Class variables
*/
private $post_type = 'eth_timeline';
private $meta_start = '_eth_timeline_start';
private $meta_end = '_eth_timeline_end';
/**
* Silence is golden!
*/
private function __construct() {}
/**
* Instantiate singleton
*/
public static function get_instance() {
if ( ! is_a( self::$instance, __CLASS__ ) ) {
self::$instance = new self;
self::$instance->setup();
}
return self::$instance;
}
/**
* Register actions and filters
*
* @uses add_action
* @uses add_filter
* @return null
*/
private function setup() {
add_action( 'init', array( $this, 'action_init' ) );
add_action( 'pre_get_posts', array( $this, 'action_pre_get_posts' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) );
add_action( 'add_meta_boxes_' . $this->post_type, array( $this, 'action_add_meta_boxes' ) );
add_action( 'save_post', array( $this, 'action_save_post' ) );
add_filter( 'manage_' . $this->post_type . '_posts_columns', array( $this, 'filter_list_table_columns' ) );
add_action( 'manage_' . $this->post_type . '_posts_custom_column', array( $this, 'do_list_table_columns' ), 10, 2 );
add_filter( 'enter_title_here', array( $this, 'filter_editor_title_prompt' ), 10, 2 );
}
/**
* Register post type and shortcode
*
* @uses register_post_type
* @uses add_shortcode
* @action init
* @return null
*/
public function action_init() {
register_post_type( $this->post_type, array(
'label' => __( 'Timeline', 'eth-timeline' ),
'labels' => array(
'name' => __( 'Timeline', 'eth-timeline' ),
'singular_name' => __( 'Timeline', 'eth-timeline' ),
'menu_name' => __( 'Timeline', 'eth-timeline' ),
'all_items' => __( 'All Entries', 'eth-timeline' ),
'add_new' => __( 'Add New', 'eth-timeline' ),
'add_new_item' => __( 'Add New', 'eth-timeline' ),
'edit_item' => __( 'Edit Entry', 'eth-timeline' ),
'new_item' => __( 'New Entry', 'eth-timeline' ),
'view_item' => __( 'View Entry', 'eth-timeline' ),
'items_archive' => __( 'Entries List', 'eth-timeline' ),
'search_items' => __( 'Search Timeline Entries', 'eth-timeline' ),
'not_found' => __( 'No entries found', 'eth-timeline' ),
'not_found_in_trash' => __( 'No trashed entries', 'eth-timeline' ),
'parent_item_colon' => __( 'Entries:', 'eth-timeline' ),
),
'public' => true,
'has_archive' => false,
'exclude_from_search' => true,
'show_in_nav_menus' => false,
'show_in_admin_bar' => true,
'rewrite' => false,
'supports' => array(
'title',
'editor',
'author',
)
) );
add_shortcode( 'eth-timeline', array( $this, 'do_shortcode' ) );
}
/**
* Force all timeline queries to be sorted by start date.
* Doesn't interfere with admin list table sorting.
*
* @param object $query
* @uses is_admin
* @action pre_get_posts
* @return null
*/
public function action_pre_get_posts( $query ) {
if ( $query->is_main_query() && $this->post_type == $query->get( 'post_type' ) ) {
if ( is_admin() && isset( $_GET['orderby'] ) ) {
return;
}
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_key', $this->meta_start );
}
}
/**
** ADMINISTRATION
*/
/**
* Enqueue admin assets
*
* @uses get_current_screen
* @uses is_wp_error
* @uses wp_enqueue_script
* @uses plugins_url
* @uses wp_enqueue_style
* @action admin_enqueue_scripts
* @return null
*/
public function action_admin_enqueue_scripts() {
$screen = get_current_screen();
if ( is_object( $screen ) && ! is_wp_error( $screen ) && $this->post_type = $screen->post_type ) {
wp_enqueue_script( 'eth-timeline-admin', plugins_url( 'js/admin.js', __FILE__ ), array( 'jquery', 'jquery-ui-datepicker' ), 20130721, false );
wp_enqueue_style( 'eth-timeline-admin', plugins_url( 'css/smoothness.min.css', __FILE__ ), array(), 20130721, 'screen' );
}
}
/**
* Register custom date metabox
*
* @uses add_meta_box
* @action add_meta_boxes
* @return null
*/
public function action_add_meta_boxes() {
add_meta_box( 'eth-timeline-dates', __( 'Dates', 'eth-timeline' ), array( $this, 'meta_box_dates' ), $this->post_type, 'normal', 'high' );
}
/**
* Render dates metabox
*
* @param object $post
* @uses get_post_meta
* @uses _e
* @uses wp_nonce_field
* @uses ths::get_field_name
* @uses this::get_nonce_name
* @action add_meta_boxes_{$this->post_type}
* @return string
*/
public function meta_box_dates( $post ) {
$times = $this->get_times( $post->ID );
?>
<p id="eth-timeline-startbox">
<label for="eth-timeline-start"><?php _e( 'Start:', 'eth-timeline' ); ?></label>
<input type="text" name="eth-timeline[start]" id="eth-timeline-start" class="regular-text" style="width: 11em;" value="<?php echo date( 'F j, Y', $times['start'] ); ?>" />
</p>
<p id="eth-timeline-endbox">
<label for="eth-timeline-end"><?php _e( 'End:', 'eth-timeline' ); ?></label>
<input type="text" name="eth-timeline[end]" id="eth-timeline-end" class="regular-text" style="width: 11em;" value="<?php echo date( 'F j, Y', $times['end'] ); ?>" />
</p>
<?php
wp_nonce_field( $this->get_field_name( 'date' ), $this->get_nonce_name( 'date' ), false );
}
/**
* Save custom dates
*
* @param int $post_id
* @uses get_post_type
* @uses this::get_nonce_name
* @uses this::get_field_name
* @uses update_post_meta
* @uses delete_post_meta
* @action save_post
* @return null
*/
public function action_save_post( $post_id ) {
if ( $this->post_type != get_post_type( $post_id ) ) {
return;
}
if ( isset( $_POST[ $this->get_nonce_name( 'date' ) ] ) && wp_verify_nonce( $_POST[ $this->get_nonce_name( 'date' ) ], $this->get_field_name( 'date' ) ) ) {
$dates = isset( $_POST['eth-timeline'] ) ? $_POST['eth-timeline'] : array();
if ( empty( $dates ) ) {
return;
}
foreach ( $dates as $key => $date ) {
if ( ! in_array( $key, array( 'start', 'end' ) ) ) {
continue;
}
// Timestamp comes from JS
if ( empty( $date ) ) {
$timestamp = false;
} else {
$timestamp = strtotime( $date );
}
if ( $timestamp ) {
update_post_meta( $post_id, $this->{'meta_' . $key}, $timestamp );
} else {
delete_post_meta( $post_id, $this->{'meta_' . $key} );
}
}
}
}
/**
* Add new date columns to list table
*
* @param array $columns
* @uses __
* @filter manage_{$this->post_type}_posts_columns
* @return array
*/
public function filter_list_table_columns( $columns ) {
$after = array_splice( $columns, 2 );
$new_columns = array(
'eth_timeline_start' => __( 'Start Date', 'eth-timeline' ),
'eth_timeline_end' => __( 'End Date (Optional)', 'eth-timeline' ),
);
$columns = $columns + $new_columns + $after;
return $columns;
}
/**
* Display start and end dates in the post list table
*
* @param string $column
* @param int $post_id
* @uses get_post_meta
* @uses get_option
* @action manage_{$this->post_type}_posts_custom_column
* @return string or null
*/
public function do_list_table_columns( $column, $post_id ) {
if ( in_array( $column, array( 'eth_timeline_start', 'eth_timeline_end' ) ) ) {
$key = str_replace( 'eth_timeline_', '', $column );
$date = get_post_meta( $post_id, $this->{'meta_' . $key}, true );
if ( is_numeric( $date ) ) {
echo date( get_option( 'date_format', 'F j, Y' ), $date );
}
}
}
/**
** PRESENTATION
*/
/**
* Render list of timeline entries
*
* @global $post
* @param mixed $atts
* @uses shortcode_atts
* @uses WP_Query
* @uses this::get_times
* @uses the_ID
* @uses this::format_date
* @uses get_the_ID
* @uses the_title
* @uses get_the_content
* @uses remove_filter
* @uses the_content
* @uses add_filter
* @uses wp_reset_query
* @return string or null
*/
public function do_shortcode( $atts ) {
// Parse and sanitize atts
$atts = shortcode_atts( array(
'posts_per_page' => 100,
'order' => 'DESC',
'year' => null,
), $atts );
$atts['posts_per_page'] = min( 200, max( (int) $atts['posts_per_page'], -1 ) );
$atts['order'] = 'ASC' == $atts['order'] ? 'ASC' : 'DESC';
$atts['year'] = is_numeric( $atts['year'] ) ? (int) $atts['year'] : null;
// Build query
$query = array(
'post_type' => $this->post_type,
'posts_per_page' => $atts['posts_per_page'],
'post_status' => 'publish',
'order' => $atts['order'],
'orderby' => 'meta_value_num',
'meta_key' => $this->meta_start
);
if ( $atts['year'] ) {
$query['meta_query'] = array(
array(
'key' => $this->meta_start,
'value' => array( strtotime( $atts['year'] . '-01-01 00:00:00' ), strtotime( $atts['year'] . '-12-31 23:59:59' ) ),
'type' => 'numeric',
'compare' => 'BETWEEN'
)
);
}
// Run query and build output, if possible
$query = new WP_Query( $query );
if ( $query->have_posts() ) {
ob_start();
global $post;
echo '<div class="eth-timeline">';
$year = $month = null;
while ( $query->have_posts() ) {
$query->the_post();
$times = $this->get_times( $post->ID );
// Deal with grouping by year
if ( $year != date( 'Y', $times['start'] ) ) {
if ( null !== $year ) {
echo '</ul><!-- ' . $year . '-' . $month . ' --></ul><!-- ' . $year . ' -->' . "\n";
$month = null;
}
$year = (int) date( 'Y', $times['start'] );
echo '<div class="eth-timline-year-label">' . $year . '</div>' . "\n";
echo '<ul class="eth-timeline-year eth-timeline-' . $year . '">' . "\n";
}
// Deal with grouping by month
if ( $month != date( 'n', $times['start'] ) ) {
if ( null !== $month ) {
echo '</ul><!-- ' . $year . '-' . $month . ' --></li>' . "\n";
}
$month = (int) date( 'n', $times['start'] );
echo '<li class="eth-timeline-month eth-timeline-month-' . $month . '">';
echo '<div class="eth-timeline-month-label">' . date( 'F', $times['start'] ) . '</div>' . "\n";
echo '<ul class="eth-timeline-month-items eth-timeline-' . $year . '-' . $month . '">' . "\n";
}
// Info about the item
?>
<li class="eth-timeline-item" id="eth-timeline-<?php the_ID(); ?>">
<span class="eth-timeline-date"><?php echo $this->format_date( get_the_ID(), $year, $month ); ?>:</span>
<span class="eth-timeline-location"><?php the_title(); ?></span>
<?php
$content = get_the_content();
if ( ! empty( $content ) ) {
$removed = remove_filter( 'the_content', 'wpautop' );
echo ' <span class="eth-timeline-sep">—</span> <span class="eth-timeline-body">';
the_content();
echo '</span>';
if ( $removed ) {
add_filter( 'the_content', 'wpautop' );
}
}
?>
</li><!-- .eth-timeline-item#eth-timeline-<?php the_ID(); ?> -->
<?php
}
// Ensure our tags are balanced!
echo '</ul><!-- ' . $year . '-' . $month . ' -->';
echo '</ul><!-- ' . $year . ' -->';
echo '</div><!-- .eth-timeline -->';
wp_reset_query();
return ob_get_clean();
}
}
/**
** HELPERS
*/
/**
* Retrieve timestamps for a given entry
*
* @param int $post_id
* @uses get_post_meta
* @return array
*/
private function get_times( $post_id ) {
$post_id = (int) $post_id;
$start = get_post_meta( $post_id, $this->meta_start, true );
$start = is_numeric( $start ) ? (int) $start : '';
$end = get_post_meta( $post_id, $this->meta_end, true );
$end = is_numeric( $end ) ? (int) $end : '';
return compact( 'start', 'end' );
}
/**
* Format entry dates for display
*
* @param int $post_id
* @param int $loop_year
* @param int $loop_month
* @uses this::get_times
* @uses this::format_single_date
* @return string
*/
private function format_date( $post_id, $loop_year, $loop_month ) {
$times = $this->get_times( $post_id );
if ( empty( $times['end'] ) || $times['end'] <= $times['start'] ) {
return $this->format_single_date( $times['start'], false, true );
} else {
$start_year = date( 'Y', $times['start'] );
$end_year = date( 'Y', $times['end'] );
$end_month = date( 'n', $times['end'] );
$show_start_year = ( $start_year != $end_year ) || ( $start_year != $loop_year );
$show_end_year = ( $start_year != $end_year ) || ( $end_year != $loop_year );
$show_end_month = $show_end_year || ( $end_month != $loop_month );
return $this->format_single_date( $times['start'], $show_start_year, true ) . '–' . $this->format_single_date( $times['end'], $show_end_year, $show_end_month );
}
}
/**
* Format timestamp into display date.
*
* @param int $timestamp
* @param bool $show_year
* @param bool $show_month
* @uses apply_filters
* @return string
*/
private function format_single_date( $timestamp, $show_year = false, $show_month = false ) {
$format = 'j';
if ( $show_year ) {
$format .= ', Y';
}
if ( $show_month ) {
$format = 'F ' . $format;
}
$format = apply_filters( 'eth_timeline_date_format', $format, $show_year, $show_month, $timestamp );
return date( $format, $timestamp );
}
/**
* Provide better prompt text for the editor title field
*
* @param string $text
* @param object $post
* @uses get_post_type
* @uses __
* @filter enter_title_here
* @return string
*/
public function filter_editor_title_prompt( $text, $post ) {
if ( $this->post_type == get_post_type( $post ) ) {
$text = __( 'Enter destination here', 'eth-timeline' );
}
return $text;
}
/**
* Return formatted field name
*
* @param string $field
* @return string
*/
private function get_field_name( $field ) {
return $this->post_type . '_' . $field;
}
/**
* Return formatted nonce name
*
* @param string $field
* @uses this::get_field_name
* @return string
*/
private function get_nonce_name( $field ) {
return $this->get_field_name( $field ) . '_nonce';
}
}
ETH_Timeline::get_instance();