Video icon 64
Learning to code? Skill up faster with our practical video courses. Start your free trial today.
Advertisement

Creating Upcoming Events Plugin in WordPress: Custom Post Type and the Dashboard

by

In this part of the series we will begin writing our code. Being more specific, we will:

  • register a custom post type for events
  • add three metaboxes for event start date, end date and event venue
  • include the jQuery UI Datepicker widget in the WordPress admin
  • write the functionality so that the contents of meta fields in our event post type get saved in the database
  • add custom columns to the post admin screen for events

This will be a code heavy tutorial so open up your favorite code editor and grab a cup of coffee!

Defining Basic Path Constants

Before we move on to writing the actual code, we will start by defining some constants for our paths. This will help us later when registering our scripts and stylesheets.

First, open the upcoming-events.php file, add the following code:

define( 'ROOT', plugins_url( '', __FILE__ ) );
define( 'IMAGES', ROOT . '/img/' );
define( 'STYLES', ROOT . '/css/' );
define( 'SCRIPTS', ROOT . '/js/' );

In the above code, we have used plugins_url() function to get our root directory path. It accepts two parameters i.e. $path and $plugin.

Since we don't need to refer to any file, but to the root directory of our plugin, we didn’t provide the $path argument and for the $plugin argument, we provided the current file. We then simply appended the names of other directories to the ROOT constant for their respective paths.

Registering a Custom Post Type for Events

WordPress is capable of holding different types of content. We can create as many content types as we need depending upon the scenario in which we are working. To add more content types (generally known as “custom post types”), WordPress provides a function register_post_type() that accepts two arguments:

  1. the post type
  2. additional arguments

In the second argument, that is, the array of additional arguments, we define the labels of the post type and other specifics regarding the visibility, capability and taxonomies etc.

In the upcoming-events.php file, add the following code to register custom post type for events:

$labels = array(
	'name'					=>	__( 'Events', 'uep' ),
	'singular_name'			=>	__( 'Event', 'uep' ),
	'add_new_item'			=>	__( 'Add New Event', 'uep' ),
	'all_items'				=>	__( 'All Events', 'uep' ),
	'edit_item'				=>	__( 'Edit Event', 'uep' ),
	'new_item'				=>	__( 'New Event', 'uep' ),
	'view_item'				=>	__( 'View Event', 'uep' ),
	'not_found'				=>	__( 'No Events Found', 'uep' ),
	'not_found_in_trash'	=>	__( 'No Events Found in Trash', 'uep' )
);

$supports = array(
	'title',
	'editor',
	'excerpt'
);

$args = array(
	'label'			=>	__( 'Events', 'uep' ),
	'labels'		=>	$labels,
	'description'	=>	__( 'A list of upcoming events', 'uep' ),
	'public'		=>	true,
	'show_in_menu'	=>	true,
	'menu_icon'		=>	IMAGES . 'event.svg',
	'has_archive'	=>	true,
	'rewrite'		=>	true,
	'supports'		=>	$supports
);

register_post_type( 'event', $args );

In the above code, we have defined the labels for our event post type and made the post type visible to public through setting the public key to true. For the new-post or post-edit screen, we have included three fields for title, content, and excerpt. 

Also note that we have marked the static text fields for translation using the text domain uep which is unique to our plugin. A 20X20 SVG icon has also been included for Events menu.

The register_post_type() function needs to be called through the init action for it to work properly. So wrap it inside a function and hook it to the init action:

function uep_custom_post_type() {
	$labels = array(
		'name'					=>	__( 'Events', 'uep' ),
		'singular_name'			=>	__( 'Event', 'uep' ),
		'add_new_item'			=>	__( 'Add New Event', 'uep' ),
		'all_items'				=>	__( 'All Events', 'uep' ),
		'edit_item'				=>	__( 'Edit Event', 'uep' ),
		'new_item'				=>	__( 'New Event', 'uep' ),
		'view_item'				=>	__( 'View Event', 'uep' ),
		'not_found'				=>	__( 'No Events Found', 'uep' ),
		'not_found_in_trash'	=>	__( 'No Events Found in Trash', 'uep' )
	);

	$supports = array(
		'title',
		'editor',
		'excerpt'
	);

	$args = array(
		'label'			=>	__( 'Events', 'uep' ),
		'labels'		=>	$labels,
		'description'	=>	__( 'A list of upcoming events', 'uep' ),
		'public'		=>	true,
		'show_in_menu'	=>	true,
		'menu_icon'		=>	IMAGES . 'event.svg',
		'has_archive'	=>	true,
		'rewrite'		=>	true,
		'supports'		=>	$supports
	);

	register_post_type( 'event', $args );
}
add_action( 'init', 'uep_custom_post_type' );

We now have our event post type ready. Navigate to the WordPress dashboard and you will see a menu for events below the comments menu:

event-cpt-menu

Adding Meta Fields for Event Dates and Venue

Having successfully registered our custom post type for events, we now need to add three meta fields for the user to add event start date, end date and the event venue. These meta fields are be contained in a custom metabox. We can conveniently add a custom metabox to the WordPress admin using the add_meta_box() function.

It accepts seven arguments that are defined below:

  • $id is that id of the metabox. It is also the id attribute for the rendered metabox on the screen.
  • $title is the title of the metabox.
  • $callback is the name of the function which will be used to render the contents of the metabox.
  • $post_type is the name of the post type to which we want to add this metabox.
  • $context is the area of the page to which we want to add the metabox. There are three contexts: normal, advance and side.
  • $priority of the metabox within the given context which can be any of high, core, default or low.
  • $callback_args is an array of arguments that can be passed to the callback function.

In the upcoming-events.php file add the following code after the uep_custom_post_type() function:

function uep_add_event_info_metabox() {
	add_meta_box(
		'uep-event-info-metabox',
		__( 'Event Info', 'uep' ),
		'uep_render_event_info_metabox',
		'event',
		'side',
		'core'
	);
}
add_action( 'add_meta_boxes', 'uep_add_event_info_metabox' );

Navigate to the Add New page under the Events menu and you will see a warning for undefined callback function. That’s because we haven’t yet defined our callback function to render the contents of the metabox.

Let’s do that now:

function uep_render_event_info_metabox( $post ) {

    // generate a nonce field
	wp_nonce_field( basename( __FILE__ ), 'uep-event-info-nonce' );

	// get previously saved meta values (if any)
	$event_start_date = get_post_meta( $post->ID, 'event-start-date', true );
	$event_end_date = get_post_meta( $post->ID, 'event-end-date', true );
	$event_venue = get_post_meta( $post->ID, 'event-venue', true );

	// if there is previously saved value then retrieve it, else set it to the current time
	$event_start_date = ! empty( $event_start_date ) ? $event_start_date : time();

	//we assume that if the end date is not present, event ends on the same day
	$event_end_date = ! empty( $event_end_date ) ? $event_end_date : $event_start_date;

	?>

<label for="uep-event-start-date"><?php _e( 'Event Start Date:', 'uep' ); ?></label>
		<input class="widefat uep-event-date-input" id="uep-event-start-date" type="text" name="uep-event-start-date" placeholder="Format: February 18, 2014" value="<?php echo date( 'F d, Y', $event_start_date ); ?>" />

<label for="uep-event-end-date"><?php _e( 'Event End Date:', 'uep' ); ?></label>
		<input class="widefat uep-event-date-input" id="uep-event-end-date" type="text" name="uep-event-end-date" placeholder="Format: February 18, 2014" value="<?php echo date( 'F d, Y', $event_end_date ); ?>" />

<label for="uep-event-venue"><?php _e( 'Event Venue:', 'uep' ); ?></label>
		<input class="widefat" id="uep-event-venue" type="text" name="uep-event-venue" placeholder="eg. Times Square" value="<?php echo $event_venue; ?>" />

	<?php <br ?>}

In the above code, we first generated a WordPress nonce field. This is important since we need to make sure that no one hijacks our form request thus compromising the site’s security.

We then retrieved our post meta for start date, end date and the event venue using the function get_post_meta(). If this is a new post, current timestamp will be used for event start and end dates. Else if the post is being edited, previously saved values will be shown in the meta fields.

For the meta fields, we rendered three labels and input fields with placeholder texts to give user a hint about the input format.

Now it’s time to add jQuery UI datepicker widget to the event start date and event end date field to grant user more ease when inputting dates.

Adding jQuery UI Datepicker in the Dashboard

Recall from the last article, we discussed that the jQuery UI and the jQuery UI Datepicker widget is already included in the WordPress JavaScript library. We also downloaded a custom build from the jQuery UI official website and dropped the stylesheet file in our css folder. If you haven’t done it yet, grab yourself a copy from the jQuery UI official website and place the stylesheet in the css folder.

Create a file named script.js inside the js folder. This is the file in which we will initialize the jQuery UI Datepicker widget for our event start and end dates.

We will now enqueue the JavaScript and the accompanying stylesheet in our admin using the action admin_enqueue_scripts:

function uep_admin_script_style( $hook ) {

	if ( 'post.php' == $hook || 'post-new.php' == $hook ) {
		wp_enqueue_script(
			'upcoming-events',
			SCRIPTS . 'script.js',
			array( 'jquery', 'jquery-ui-datepicker' ),
			'1.0',
			true
		);

		wp_enqueue_style(
			'jquery-ui-calendar',
			STYLES . 'jquery-ui-1.10.4.custom.min.css',
			false,
			'1.10.4',
			'all'
		);
	}
}
add_action( 'admin_enqueue_scripts', 'uep_admin_script_style' );

You might have noticed that we haven’t enqueued the jQuery and the jQuery UI datepicker separately but rather added them as a dependency for the script.js file.

By doing so, WordPress will make sure that jQuery and jQuery UI Datepicker (along with its own dependencies) gets enqueued BEFORE the script.js file.

The callback function accepts an argument for the page hook i.e. the current page of the dashboard. Since we don’t want to include this JavaScript file on every single page of the dashboard, we first check through the condition if the current page is post.php or post-new.php.

This way, we have restricted the JavaScript only to be included on post edit or new post screens, but what if someone creates or updates a regular post or page? The above JavaScript file will also get included on those pages since the hook is same for those two as well.

For that purpose, we will further check if the post being edited is an event post type:

function uep_admin_script_style( $hook ) {
	global $post_type;

	if ( ( 'post.php' == $hook || 'post-new.php' == $hook ) && ( 'event' == $post_type ) ) {
		wp_enqueue_script(
			'upcoming-events',
			SCRIPTS . 'script.js',
			array( 'jquery', 'jquery-ui-datepicker' ),
			'1.0',
			true
		);

		wp_enqueue_style(
			'jquery-ui-calendar',
			STYLES . 'jquery-ui-1.10.4.custom.min.css',
			false,
			'1.10.4',
			'all'
		);
	}
}
add_action( 'admin_enqueue_scripts', 'uep_admin_script_style' );

Now the above JavaScript file will only be included if the page hook is post.php or post-new.php and the post being created or edited is of type event.

Let’s initialize the jQuery UI Datepicker. Open the script.js file from the js directory and add the following code:

(function( $ ) {

	$( '#uep-event-start-date' ).datepicker({
		dateFormat: 'MM dd, yy',
		onClose: function( selectedDate ){
			$( '#uep-event-end-date' ).datepicker( 'option', 'minDate', selectedDate );
		}
	});
	$( '#uep-event-end-date' ).datepicker({
		dateFormat: 'MM dd, yy',
		onClose: function( selectedDate ){
			$( '#uep-event-start-date' ).datepicker( 'option', 'maxDate', selectedDate );
		}
	});

})( jQuery );

Note that we have restricted the event start date to not be greater than the event end date and vice-versa.

Check if the initialization has been successful by going to the Add New event page and clicking on either of event start or end dates. You should see the datepicker window appear and you can input the date by navigating through the calendar and clicking the desired date.

jquery-ui-calendar

So far, we have written a fair amount of code but our plugin is still lacking the most fundamental functionality: To save and update the event meta field values.

Getting Event Meta Field Values Saved in the Database

Now that we have created our custom post type and added three meta fields for event start date, end date, and event venue, we need to make sure that the values of these meta fields get saved in the database.

WordPress provides a hook for this purpose that fires every time a post is being saved. The hook is save_post and its callback function accepts an argument for the ID of the post being saved. Using this hook, we can save the values of our meta fields in the database along with the regular post fields such as the title and the content.

function uep_save_event_info( $post_id ) {

}
add_action( 'save_post', 'uep_save_event_info' );

But again, we need to check if the post being saved is of type event. For that purpose, we will check the $_POST global variable:

function uep_save_event_info( $post_id ) {
	// checking if the post being saved is an 'event',
	// if not, then return
	if ( 'event' != $_POST['post_type'] ) {
		return;
	}

Now we will check for the save status i.e. if the post is being auto saved or it’s a revision. WordPress provides two conditional tags for that purpose:

  • wp_is_post_autosave( $post_id )
  • wp_is_post_revision( $post_id )

We can use these two conditional tags as follows:

$is_autosave = wp_is_post_autosave( $post_id );
$is_revision = wp_is_post_revision( $post_id );

But we also need to make sure that the nonce is valid. Remember that we defined a nonce field using the wp_nonce_field() function when rendering the metabox? We will check the value of the same nonce:

$is_valid_nonce = ( isset( $_POST['uep-event-info-nonce'] ) && ( wp_verify_nonce( $_POST['uep-event-info-nonce'], basename( __FILE__ ) ) ) ) ? true : false;

Put all those three conditions in a single statement and we are good to go:

if ( $is_autosave || $is_revision || ! $is_valid_nonce ) {
	return;
}

Having performed the necessary operations before saving the meta values, we are now ready to insert them into the database:

function uep_save_event_info( $post_id ) {

	// checking if the post being saved is an 'event',
	// if not, then return
	if ( 'event' != $_POST['post_type'] ) {
		return;
	}

	// checking for the 'save' status
	$is_autosave = wp_is_post_autosave( $post_id );
	$is_revision = wp_is_post_revision( $post_id );
	$is_valid_nonce = ( isset( $_POST['uep-event-info-nonce'] ) && ( wp_verify_nonce( $_POST['uep-event-info-nonce'], basename( __FILE__ ) ) ) ) ? true : false;

	// exit depending on the save status or if the nonce is not valid
	if ( $is_autosave || $is_revision || ! $is_valid_nonce ) {
		return;
	}

	// checking for the values and performing necessary actions
	if ( isset( $_POST['uep-event-start-date'] ) ) {
		update_post_meta( $post_id, 'event-start-date', strtotime( $_POST['uep-event-start-date'] ) );
	}

	if ( isset( $_POST['uep-event-end-date'] ) ) {
		update_post_meta( $post_id, 'event-end-date', strtotime( $_POST['uep-event-end-date'] ) );
	}

	if ( isset( $_POST['uep-event-venue'] ) ) {
		update_post_meta( $post_id, 'event-venue', sanitize_text_field( $_POST['uep-event-venue'] ) );
	}
}
add_action( 'save_post', 'uep_save_event_info' );

Here, we saved the meta values in the database using the function update_post_meta() that accepts four arguments of which we have passed the first three:

  1. $post_id is the id of the post to which the meta value belongs.
  2. $meta_key is the key of the custom meta field.
  3. $meta_value is the new meta value.
  4. $prev_value is the previous meta value to be replaced.

Note that we are not saving the date in the textual description but we are first converting it to the UNIX timestamp using the PHP strtotime() function. This way, it will become far easier when comparing dates against each other while performing the meta query on the front end.

At this point, the most fundamental part of our tutorial has been completed. It’s time to add some custom columns to the post dashboard screen of events.

Adding Custom Columns to the Post Admin Screen

Since the events have their own specific information contained in their custom fields, it’s appropriate to show them in the custom columns in the post admin screen for better accessibility by the user. 

By default, WordPress shows the title and the date columns for the custom post type but we will add three more columns for the event start date, end date and the venue.

We can control what columns to add and which ones to hide by the WordPress hook manage_edit-$post_columns where $post is the name of the custom post type. The callback function accepts an array for the columns that are already being shown.

function uep_custom_columns_head( $defaults ) {
	unset( $defaults['date'] );

	$defaults['event_start_date'] = __( 'Start Date', 'uep' );
	$defaults['event_end_date'] = __( 'End Date', 'uep' );
	$defaults['event_venue'] = __( 'Venue', 'uep' );

	return $defaults;
}
add_filter( 'manage_edit-event_columns', 'uep_custom_columns_head', 10 );

In the above code, we have unset the default date column and instead, we have added our three custom columns.

But the content of the respective columns will not be shown until we define it using the manage_$post_posts_custom_column hook where $post is the name of the custom post type.

function uep_custom_columns_content( $column_name, $post_id ) {

	if ( 'event_start_date' == $column_name ) {
		$start_date = get_post_meta( $post_id, 'event-start-date', true );
		echo date( 'F d, Y', $start_date );
	}

	if ( 'event_end_date' == $column_name ) {
		$end_date = get_post_meta( $post_id, 'event-end-date', true );
		echo date( 'F d, Y', $end_date );
	}

	if ( 'event_venue' == $column_name ) {
		$venue = get_post_meta( $post_id, 'event-venue', true );
		echo $venue;
	}
}
add_action( 'manage_event_posts_custom_column', 'uep_custom_columns_content', 10, 2 );

We have first checked the column that is being shown and then echoed the content accordingly. The last parameter in the add_action() function call is the number of arguments that are accepted by the callback function – in our case – it's two.

post-admin-columns

What’s Next?

In this tutorial, we successfully registered a custom post type for events. We also learned to include a custom metabox and integrated jQuery UI datepicker widget in the WordPress dashboard. Furthermore, to grant the user greater accessibility, we added custom columns in our post admin screen.

While we have almost completed the dashboard, there's still something left for us to tackle — the front-end. 

In the next installment of the series, we will make a custom widget to display the list of upcoming events in the sidebar. We will look at the WP_Query class more closely to retrieve posts based on their meta values.

Advertisement