eth-timeline.php 14.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
/*
Plugin Name: ETH Timeline
Plugin URI: http://www.ethitter.com/plugins/
Description: List travel destinations
Author: Erick Hitter
Version: 0.1
Author URI: http://www.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 $taxonomy = 'eth_timeline_event';

37
38
	private $meta_start = '_eth_timeline_start';
	private $meta_end = '_eth_timeline_end';
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
	/**
	 * 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;
	}

	/**
59
	 * Register actions and filters
60
	 *
61
62
63
	 * @uses add_action
	 * @uses add_filter
	 * @return null
64
65
66
	 */
	private function setup() {
		add_action( 'init', array( $this, 'action_init' ) );
67

68
69
		add_action( 'pre_get_posts', array( $this, 'action_pre_get_posts' ) );

70
71
		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' ) );
72
		add_action( 'save_post', array( $this, 'action_save_post' ) );
73

74
75
76
		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 );

Erick Hitter's avatar
Erick Hitter committed
77
		add_filter( 'enter_title_here', array( $this, 'filter_editor_title_prompt' ), 10, 2 );
78
79
80
	}

	/**
81
	 * Register post type and shortcode
82
	 *
83
84
85
86
	 * @uses register_post_type
	 * @uses add_shortcode
	 * @action init
	 * @return null
87
88
89
	 */
	public function action_init() {
		register_post_type( $this->post_type, array(
90
			'label'               => __( 'Timeline', 'eth-timeline' ),
91
			'labels'              => array(
92
93
94
95
96
97
98
99
100
101
102
103
104
105
				'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' ),
106
107
			),
			'public'              => true,
108
			'has_archive'         => false,
109
110
111
			'exclude_from_search' => true,
			'show_in_nav_menus'   => false,
			'show_in_admin_bar'   => true,
112
			'rewrite'             => false,
113
114
115
116
117
118
			'supports'            => array(
				'title',
				'editor',
				'author',
			)
		) );
119
120

		add_shortcode( 'eth-timeline', array( $this, 'do_shortcode' ) );
121
122
	}

123
124
125
126
127
128
129
130
131
132
	/**
	 * 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 ) {
133
		if ( $query->is_main_query() && $this->post_type == $query->get( 'post_type' ) ) {
134
135
136
137
138
139
140
141
			if ( is_admin() && isset( $_GET['orderby'] ) )
				return;

			$query->set( 'orderby', 'meta_value_num' );
			$query->set( 'meta_key', $this->meta_start );
		}
	}

142
143
144
145
	/**
	 ** ADMINISTRATION
	 */

146
	/**
147
	 * Enqueue admin assets
148
	 *
149
150
151
152
153
154
155
	 * @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
156
	 */
157
	public function action_admin_enqueue_scripts() {
158
159
		$screen = get_current_screen();

160
		if ( is_object( $screen ) && ! is_wp_error( $screen ) && $this->post_type = $screen->post_type ) {
161
			wp_enqueue_script( 'eth-timeline-admin', plugins_url( 'js/admin.js', __FILE__ ), array( 'jquery', 'jquery-ui-datepicker' ), 20130721, false );
162
163

			wp_enqueue_style( 'eth-timeline-admin', plugins_url( 'css/smoothness.min.css', __FILE__ ), array(), 20130721, 'screen' );
164
165
166
167
		}
	}

	/**
168
	 * Register custom date metabox
169
	 *
170
171
172
	 * @uses add_meta_box
	 * @action add_meta_boxes
	 * @return null
173
174
175
176
177
178
	 */
	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' );
	}

	/**
179
	 * Render dates metabox
180
	 *
181
182
183
184
185
186
187
188
	 * @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
189
190
	 */
	public function meta_box_dates( $post ) {
191
		$times = $this->get_times( $post->ID );
192
193

		?>
194
195
		<p id="eth-timeline-startbox">
			<label for="eth-timeline-start"><?php _e( 'Start:', 'eth-timeline' ); ?></label>
196
			<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'] ); ?>" />
197
198
199
200
		</p>

		<p id="eth-timeline-endbox">
			<label for="eth-timeline-end"><?php _e( 'End:', 'eth-timeline' ); ?></label>
201
			<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'] ); ?>" />
202
		</p>
203
		<?php
204
205

		wp_nonce_field( $this->get_field_name( 'date' ), $this->get_nonce_name( 'date' ), false );
206
207
	}

208
	/**
209
	 * Save custom dates
210
	 *
211
212
213
214
215
216
217
218
	 * @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
219
220
221
222
223
	 */
	public function action_save_post( $post_id ) {
		if ( $this->post_type != get_post_type( $post_id ) )
			return;

224
		if ( isset( $_POST[ $this->get_nonce_name( 'date' ) ] ) && wp_verify_nonce( $_POST[ $this->get_nonce_name( 'date' ) ], $this->get_field_name( 'date' ) ) ) {
225
			$dates = isset( $_POST['eth-timeline'] ) ? $_POST['eth-timeline'] : array();
226

227
228
229
			if ( empty( $dates ) )
				return;

230
			foreach ( $dates as $key => $date ) {
231
232
233
				if ( ! in_array( $key, array( 'start', 'end' ) ) )
					continue;

234
				// Timestamp comes from JS
235
				if ( empty( $date ) )
236
					$timestamp = false;
237
238
				else
					$timestamp = strtotime( $date );
239

240
241
242
243
244
				if ( $timestamp )
					update_post_meta( $post_id, $this->{'meta_' . $key}, $timestamp );
				else
					delete_post_meta( $post_id, $this->{'meta_' . $key} );
			}
245
246
247
		}
	}

248
249
250
251
252
253
254
255
256
257
258
259
260
	/**
	 * 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' ),
261
			'eth_timeline_end'   => __( 'End Date (Optional)', 'eth-timeline' ),
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
		);

		$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 );
		}
	}

289
290
291
292
293
	/**
	 ** PRESENTATION
	 */

	/**
294
	 * Render list of timeline entries
295
	 *
296
297
298
299
300
301
302
	 * @global $post
	 * @param mixed $atts
	 * @uses shortcode_atts
	 * @uses WP_Query
	 * @uses this::get_times
	 * @uses the_ID
	 * @uses this::format_date
303
	 * @uses get_the_ID
304
305
306
307
308
309
310
	 * @uses the_title
	 * @uses get_the_content
	 * @uses remove_filter
	 * @uses the_content
	 * @uses add_filter
	 * @uses wp_reset_query
	 * @return string or null
311
312
313
	 */
	public function do_shortcode( $atts ) {
		// Parse and sanitize atts
314
		$atts = shortcode_atts( array(
315
316
317
			'posts_per_page' => 100,
			'order'          => 'DESC',
			'year'           => null,
318
		), $atts );
319

320
		$atts['posts_per_page'] = min( 200, max( (int) $atts['posts_per_page'], -1 ) );
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
		$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;

353
354
			echo '<div class="eth-timeline">';

355
356
			$year = $month = null;

357
358
359
			while ( $query->have_posts() ) {
				$query->the_post();

360
361
362
363
364
				$times = $this->get_times( $post->ID );

				// Deal with grouping by year
				if ( $year != date( 'Y', $times['start'] ) ) {
					if ( null !== $year ) {
365
						echo '</ul><!-- ' . $year . '-' . $month . ' --></ul><!-- ' . $year . ' -->' . "\n";
366
367
368
369
370
371
						$month = null;
					}

					$year = (int) date( 'Y', $times['start'] );

					echo '<div class="eth-timline-year-label">' . $year . '</div>' . "\n";
372
					echo '<ul class="eth-timeline-year eth-timeline-' . $year . '">' . "\n";
373
374
375
376
377
				}

				// Deal with grouping by month
				if ( $month != date( 'n', $times['start'] ) ) {
					if ( null !== $month )
378
						echo '</ul><!-- ' . $year . '-' . $month . ' --></li>' . "\n";
379
380
381

					$month = (int) date( 'n', $times['start'] );

382
					echo '<li class="eth-timeline-month eth-timeline-month-' . $month . '">';
383
					echo '<div class="eth-timeline-month-label">' . date( 'F', $times['start'] ) . '</div>' . "\n";
384
					echo '<ul class="eth-timeline-month-items eth-timeline-' . $year . '-' . $month . '">' . "\n";
385
386
387
388
				}

				// Info about the item
				?>
389
				<li class="eth-timeline-item" id="eth-timeline-<?php the_ID(); ?>">
390
					<span class="eth-timeline-date"><?php echo $this->format_date( get_the_ID(), $year, $month ); ?>:</span>
Erick Hitter's avatar
Erick Hitter committed
391
					<span class="eth-timeline-location"><?php the_title(); ?></span>
392
393
394
395
396
397
398

					<?php
						$content = get_the_content();

						if ( ! empty( $content ) ) {
							$removed = remove_filter( 'the_content', 'wpautop' );

Erick Hitter's avatar
Erick Hitter committed
399
							echo ' <span class="eth-timeline-sep">&mdash;</span> <span class="eth-timeline-body">';
400
							the_content();
Erick Hitter's avatar
Erick Hitter committed
401
							echo '</span>';
402
403
404
405
406

							if ( $removed )
								add_filter( 'the_content', 'wpautop' );
						}
					?>
407
				</li><!-- .eth-timeline-item#eth-timeline-<?php the_ID(); ?> -->
408
				<?php
409
410
			}

411
			// Ensure our tags are balanced!
412
413
414
415
			echo '</ul><!-- ' . $year . '-' . $month . ' -->';
			echo '</ul><!-- ' . $year . ' -->';

			echo '</div><!-- .eth-timeline -->';
416

417
418
419
420
421
422
423
424
425
			wp_reset_query();
			return ob_get_clean();
		}
	}

	/**
	 ** HELPERS
	 */

426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
	/**
	 * 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' );
	}

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
	/**
	 * 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'], $loop_year, $loop_month );
		} else {
			return $this->format_single_date( $times['start'], $loop_year, $loop_month ) . '&ndash;' . $this->format_single_date( $times['end'], $loop_year, $loop_month, false );
		}
	}

465
	/**
466
	 * Determine appropriate date format for display start and end dates together.
467
	 *
468
469
470
471
472
	 * @param int $timestamp
	 * @param int $loop_year
	 * @param int $loop_month
	 * @param bool $start
	 * @return string
473
	 */
474
	private function format_single_date( $timestamp, $loop_year, $loop_month, $start = true ) {
475
476
477
478
479
480
481
482
483
484
485
486
487
488
		$ts_year = date( 'Y', $timestamp );
		$ts_month = date( 'n', $timestamp );

		$format = 'j';

		if ( $loop_year != $ts_year )
			$format .= ', Y';

		if ( $start || $loop_month != $ts_month )
			$format = 'F ' . $format;

		return date( $format, $timestamp );
	}

Erick Hitter's avatar
Erick Hitter committed
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
	/**
	 * 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;
	}

506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
	/**
	 * 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();