Create An Image Slide Plugin Part 8: Create A WordPress Slider Shortcode

In the eighth installment from this series we’ll be creating a shortcode to display multiple slides into a slider.

In the previous post we added the first shortcode for displaying a single slide. Now, we’ll grab multiple of those slides and show them anywhere we want into an animated slider.

If you want to follow along I’ve set up a GitHub repository where you’ll find the code from this series. The finished code for this post is in the part_8 branch. If you want to follow along please download the part_7 branch.

The second shortcode will be called mnsp_slider and we’ll start creating it by updating the class-monospace-slides-shortcodes.php file in the /includes folder.

So open the above mentionaed folder, look for the register_slider_shortcode() method and replace the existing code with the code below:

/**
 * Create and register the 'mnsp_slider' shortcode for displaying multiple slides in a slider.
 *
 * @since 1.0.0
 */
public function register_slider_shortcode() {

	/**
	 * Handler function for the 'mnsp_slider' shortcode.
	 *
	 * @since 1.0.0
	 * @param array $atts    Shortcode attributes.
	 */
	function slider_shortcode( $atts ) {

		$args = shortcode_atts(
			array(
				'id' => '',
			),
			$atts
		);

		$id = (int) $args['id'];

		$transition_type     = esc_attr( get_term_meta( $id, 'mnsp-slider-transition-type', true ) );
		$transition_duration = esc_attr( get_term_meta( $id, 'mnsp-slider-transition-duration', true ) );
		$timeout             = esc_attr( get_term_meta( $id, 'mnsp-slider-timeout', true ) );

		$show_nav = esc_attr( get_term_meta( $id, 'mnsp-slider-show-nav', true ) ) ? 'data-mnsp-slider-show-nav' : '';

		$pause_hover = esc_attr( get_term_meta( $id, 'mnsp-slider-pause-hover', true ) ) ? 'data-mnsp-slider-pause-hover' : '';

		$autoplay = esc_attr( get_term_meta( $id, 'mnsp-slider-autoplay', true ) ) ? 'data-mnsp-slider-autoplay' : '';

		$ratio = esc_attr( get_term_meta( $id, 'mnsp-slider-aspect', true ) );

		$args = array(
			'post_type' => 'mnsp_slide',
			'tax_query' => array( // WPCS: slow query ok.
				array(
					'taxonomy'         => 'mnsp_slider',
					'field'            => 'term_id',
					'terms'            => $id,
					'include_children' => false,
				),
			),
		);

		$slides = get_posts( $args );

		if ( ! empty( $slides ) ) :

			$slider = '<div 
				id="mnsp-slider-' . $id . '" 
				data-mnsp-slider-transition-type="' . $transition_type . '" 
				data-mnsp-slider-transition-duration="' . $transition_duration . '"
				data-mnsp-slider-timeout="' . $timeout . '"
				data-mnsp-slider-ratio="' . $ratio . '"
				' . $show_nav . '
				' . $pause_hover . '
				' . $autoplay . '
				class="mnsp-slider">';

			if ( ! empty( $show_nav ) ) {
				$slider .= '<div class="mnsp-slider-controls" data-glide-el="controls">';
				$slider .= '<button data-glide-dir="<">‹</button>';
				$slider .= '<button data-glide-dir=">">›</button>';
				$slider .= '</div>';
			}

			$slider .= '<div class="glide__track" data-glide-el="track">';

			$slider .= '<div class="glide__slides">';

			foreach ( $slides as $slide_post ) {

				$slide_id = $slide_post->ID;

				$title       = esc_html( get_post_meta( $slide_id, 'mnsp-slide-title', true ) );
				$title_color = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-title-color', true ) );

				$subtitle       = esc_html( get_post_meta( $slide_id, 'mnsp-slide-subtitle', true ) );
				$subtitle_color = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-subtitle-color', true ) );

				$desc       = esc_textarea( get_post_meta( $slide_id, 'mnsp-slide-desc', true ) );
				$desc_color = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-desc-color', true ) );

				$cta_text           = esc_html( get_post_meta( $slide_id, 'mnsp-slide-cta-text', true ) );
				$cta_text_color     = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-cta-text-color', true ) );
				$cta_bg_color       = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-cta-bg-color', true ) );
				$cta_hover_bg_color = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-cta-hover-bg-color', true ) );
				$cta_url            = esc_url( get_post_meta( $slide_id, 'mnsp-slide-cta-url', true ) );
				$cta_target         = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-cta-target', true ) ) ? '_blank' : '_self';

				$image = esc_url( get_post_meta( $slide_id, 'mnsp-slide-image', true ) );

				$horiz_align = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-horiz-align', true ) );

				// We're using the ratio from the slider taxonomy term meta.
				// The ratio of each individual slide is not necessary.
				/* $slide_ratio = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-ratio', true ) ); */ // phpcs:ignore

				$feat_img_opacity = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-featured-opacity', true ) );

				$bg_color = esc_attr( get_post_meta( $slide_id, 'mnsp-slide-bg-color', true ) );

				$has_thumbnail = has_post_thumbnail( $slide_id ) ? ' mnsp-slide-has-thumbnail' : '';

				$slide = '<div 
					id="mnsp-slide-' . $slide_id . '" 
					class="glide__slide mnsp-slide' . $has_thumbnail . '" 
					data-mnsp-slide-title-color="' . $title_color . '" 
					data-mnsp-slide-subtitle-color="' . $subtitle_color . '" 
					data-mnsp-slide-cta-text-color="' . $cta_text_color . '" 
					data-mnsp-slide-cta-bg-color="' . $cta_bg_color . '" 
					data-mnsp-slide-cta-hover-bg-color="' . $cta_hover_bg_color . '" 
					data-mnsp-slide-horiz-align="' . $horiz_align . '" 
					data-mnsp-slide-ratio="' . $ratio . '" 
					data-mnsp-slide-featured-opacity="' . $feat_img_opacity . '" 
					data-mnsp-slide-bg-color="' . $bg_color . '">';

				$slide .= '<div class="mnsp-slide-caption">';

				if ( ! empty( $image ) ) {
					$slide .= '<img class="mnsp-slide-image" src="' . $image . '" />';
				}

				if ( ! empty( $title ) ) {
					$slide .= '<p class="mnsp-slide-title">' . $title . '</p>';
				}

				if ( ! empty( $subtitle ) ) {
					$slide .= '<p class="mnsp-slide-subtitle">' . $subtitle . '</p>';
				}

				if ( ! empty( $desc ) ) {
					$slide .= '<p class="mnsp-slide-desc">' . $desc . '</p>';
				}

				if ( ! empty( $cta_text ) && ! empty( $cta_url ) ) {
					$slide .= '<a class="mnsp-slide-cta" href="' . $cta_url . '" target"' . $cta_target . '">' . $cta_text . '</a>';
				}

				$slide .= '</div>';

				$slide .= get_the_post_thumbnail( $slide_id, 'mnsp-image-' . $ratio, array( 'class' => 'mnsp-slide-featured' ) );

				$slide .= '</div>';

				$slider .= $slide;

			}

			wp_reset_postdata();

			$slider .= '</div></div></div>';

		else :

			$slider = '<p><strong>' . __( 'This slider does not contain any posts.', 'monospace-slides' ) . '</strong></p>';

		endif;

		return $slider;

	}

	add_shortcode( 'mnsp_slider', 'slider_shortcode' );

}

Let’s run through the code above and see what’s going on.

We start with getting the values of the options for the current slider. Each value is assigned to a variable, for later use.

Then, based on the id of the current slider, we get all the slides that are associated with that specific slider. A slide can be assigned to a particular slider in the Sliders meta box from the Add New Slide screen.

Next, we start building our slider and add each slide we previously got. This is where the code gets similar to the one for building a slide shortcode from the previous post.

By going through each of the slides, we get the values of the options for each slide and add the slide to the slider. We do this for all the slides by using a foreach loop.

On the last line of the method we register the mnsp_slider shortcode so WordPress knows what to do when it encounters it.

Going further, we’ll use some JavaScript to animate the slider, and some CSS to correctly display the slider. For the slider animation and functionality we’ll use the Glide.js JavaScript library.

Let’s go into the /public folder and inside the /public/js and /public/css folders add the files needed for the Glide.js library to work properly. You can those from the project’s github repo.

Next, open the file /public/class-monospace-slides-public.php and add the code below to load the newly created files from above.

Inside the enqueue_styles() method:

if( has_shortcode( $post->post_content, 'mnsp_slider') ) {
	wp_enqueue_style( 'glide', plugin_dir_url( __FILE__ ) . 'css/glide.core.min.css', array(), $this->version, 'all' );
}

Inside the enqueue_script() method:

if( has_shortcode( $post->post_content, 'mnsp_slider') ) {
	wp_enqueue_script( 'glide', plugin_dir_url( __FILE__ ) . 'js/glide.min.js', array(), $this->version, true );
}

You’ll notice that we only load these files if the current page contains out slider shortcode.

Next, open the /public/css/monospace-slides-public.css and replace the contents with the code below in order to correctly display a slider:

.mnsp-slide,
.mnsp-slider {
	position: relative;
	padding: 0 !important;
}

.mnsp-slide {
	display: flex;
	height: auto;
	justify-content: center;
	align-items: center;
	font-size: 1rem;
	overflow: hidden;
}

.mnsp-slider:hover {
	cursor: -webkit-grab;
}

.mnsp-slide::before,
.mnsp-slider::before { /* for the aspect ratio functionality */
	content: "";
	width: 1px;
	margin-left: -1px;
	float: left;
	height: 0;
}

.mnsp-slide::after,
.mnsp-slider::after { /* to clear float */
	content: "";
	display: table;
	clear: both;
}

.mnsp-slide[data-mnsp-slide-horiz-align="left"] .mnsp-slide-caption {
	text-align: left;
}

.mnsp-slide[data-mnsp-slide-horiz-align="center"] .mnsp-slide-caption {
	text-align: center;
}

.mnsp-slide[data-mnsp-slide-horiz-align="right"] .mnsp-slide-caption {
	text-align: right;
}

.mnsp-slide-caption {
	position: relative;
	width: 100%;
	padding: 5vmin;
	z-index: 2000;
}

.mnsp-slide-image {
	max-width: 15%;
}

.mnsp-slide-title {
	font-size: 1.25em;
}

.mnsp-slide-subtitle {
	font-size: 1.1em;
}

.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta,
.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta:hover,
.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta:focus,
.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta:active {
	color: white;
	text-decoration: none;
	border: 0;
	box-shadow: none;
}

.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta {
	display: inline-block;
	font-family: Raleway, "Open Sans", "Source Sans Pro", sans-serif;
	font-size: smaller;
	font-weight: bold;
	text-transform: uppercase;
	line-height: 1;
	padding: 16px 30px;
	background-color: black;
	cursor: pointer;
	transition: all 0.5s ease;
}

.mnsp-slide-caption > * + * {
	margin: 0.75rem 0 0 0;
}

.mnsp-slide-featured {
	position: absolute;
	height: 100%;
	object-fit: cover;
	z-index: 1000;
}

.mnsp-slide[data-mnsp-slide-ratio="wide"]::before,
.mnsp-slider[data-mnsp-slider-ratio="wide"]::before {
	padding-top: 56.25%;
}

.mnsp-slide[data-mnsp-slide-ratio="classic"]::before,
.mnsp-slider[data-mnsp-slider-ratio="classic"]::before {
	padding-top: 75%;
}

.mnsp-slide[data-mnsp-slide-ratio="square"]::before,
.mnsp-slider[data-mnsp-slider-ratio="square"]::before {
	padding-top: 100%;
}

.mnsp-slider-controls {
	position: absolute;
	width: 100%;
	height: 3rem;
	top: 50%;
	transform: translateY(-50%);
	z-index: 3000;
	opacity: 0;
	transition: opacity 0.5s ease;
}

.mnsp-slider:hover .mnsp-slider-controls {
	opacity: 1;
}

.mnsp-slider-controls > * {
	display: inline-block;
	position: absolute;
	width: 3rem;
	height: 3rem;
	color: white;
	font-weight: bold;
	line-height: 1;
	background: rgba(0, 0, 0, 0.25);
	padding: 0;
	border-radius: 50%;
	border: none;
	transition: all 0.5s ease;
}

.mnsp-slider-controls > *:hover {
	background: rgba(0, 0, 0, 0.35);
}

.mnsp-slider-controls > *:first-child {
	left: 2rem;
}

.mnsp-slider-controls > *:last-child {
	right: 2rem;
}

The last step is to actually start the slider by calling the Glide.js code for each slider on the current page. Open the /public/js/monospace-slides.public.js and add the code below just before the end of the file, inside the IIFE function.

sliders.forEach( function( element ) {

	var $this = element,
		slider,
		autoplay = $this.hasAttribute( 'data-mnsp-slider-autoplay' ),
		pauseHover = $this.hasAttribute( 'data-mnsp-slider-pause-hover' ),
		transitionDuration = $this.dataset.mnspSliderTransitionDuration || 1000,
		timeout = 0;

	if ( autoplay ) {
		timeout = $this.dataset.mnspSliderTimeout;
	}

	slider = new Glide( $this, {
		type: 'carousel',
		autoplay: timeout,
		hoverpause: pauseHover,
		animationDuration: transitionDuration,
		perView: 1
	} ); // eslint-disable-line

	slider.mount();

} ); // eslint-disable-line

That’s it! We created another shortcode that collects multiple slides and displays them into an animated sliders with some custom options.

To display a slider, go to the Sliders screen and just copy the content of the Shortcode column, including the square brackets and paste it in any post or page.

Slider Custom WordPress Shortcode Admin Column

At The End

The code in this post was somewhat familiar with the code from the previous post so I hope it was a littler easier to follow.

This was the last post in this series. Now you know how to create a WordPress plugin using custom post types, custom taxonomies and custom meta information.

Please feel free to share your feedback regarding what we’ve been doing so far or any issues you may have.

Create An Image Slide Plugin Part 7: Create WordPress Shortcodes

In the seventh post from this series we’re going to talk about how to create your own WordPress shortcodes in order to display the slides and sliders we created.

The previous post in which we added custom metadata to our mnsp_slider taxonomy terms, was the last post focusing only on the admin side. Starting now we’re also going to step into the public side with the ability to show the slides and sliders on different pages.

What Are WordPress Shortcodes?

The WordPress Plugin Handbook calls shortcodes, macros that can be used to perform dynamic interactions with the content.

If you ask Google, it will tell you that a macro is a single instruction that expands automatically into a set of instructions to perform a particular task.

So, combining the two definitions, we can refer to a shortcode as a small piece of code, written in brackets like [this], that performs specific functions in a website by running some code that is provided by the developer of that shortcode.

Shortcodes were introduced in WordPress 2.5 to allow interaction with the content as, for security concerns, running PHP inside WordPress content is forbidden.

The very basic shortcode is defined by a tag like this:

[shortcode_tag]

More complex shortcodes can contain attributes like this:

[shortcode_tag attr1="value1" attr2="value2"] 

Shortcodes can also contain custom content in which case they will end with a closing tag like this:

[shortcode_tag attr="value"] Some content here [/shortcode_tag] 

In order for WordPress to know what to do when it encounters a shortcode, we must provide it with a piece of code that it will then run.

How To Create WordPress Shortcodes?

If you want to follow along I’ve set up a GitHub repository where you’ll find the code from this series. The finished code for this post is in the part_7 branch. If you want to follow along please download the part_6 branch.

For this plugin, we’ll create two shortcodes, mnsp_slide and mnsp_slider, to show a single slide or a slider.

We’ll start with creating a class file to hold the shortcodes functionality. So, let’s create a new file called class-monospace-slides-shortcodes.php in the /includes  folder of our plugin.

We’re working in this folder because the shortcodes functionality is present on the admin side as well as the public facing side of WordPress.

In the new file we’re creating a class to hold all the methods that the shortcodes are using.

<?php

/**
 * The functionality for creating and managing shortcodes.
 *
 * @link       https://www.mikeinmonospace.com/
 * @since      1.0.0
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/includes
 */

/**
 * The functionality for creating and managing shortcodes.
 *
 * Defines all the functionality related to the creation and managing of shortcodes.
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/includes
 * @author     Mike In Monospace <mikeinmonospace@gmail.com>
 */
class Monospace_Slides_Shortcodes {



}

Next, we’re updating the class with two methods for registering and displaying our shortcodes. The code below goes inside the class we created earlier and we’ll go though it step by step:

/**
 * Create and register the 'mnsp_slide' shortcode for displaying a single slide.
 *
 * @since 1.0.0
 */
public function register_slide_shortcode() {

	/**
	 * Handler function for the 'mnsp_slide' shortcode.
	 *
	 * @since 1.0.0
	 * @param array $atts    Shortcode attributes.
	 */
	function slide_shortcode( $atts ) {

		$args = shortcode_atts(
			array(
				'id' => '',
			),
			$atts
		);

		$id = (int) $args['id'];

		$title       = esc_html( get_post_meta( $id, 'mnsp-slide-title', true ) );
		$title_color = esc_attr( get_post_meta( $id, 'mnsp-slide-title-color', true ) );

		$subtitle       = esc_html( get_post_meta( $id, 'mnsp-slide-subtitle', true ) );
		$subtitle_color = esc_attr( get_post_meta( $id, 'mnsp-slide-subtitle-color', true ) );

		$desc       = esc_textarea( get_post_meta( $id, 'mnsp-slide-desc', true ) );
		$desc_color = esc_attr( get_post_meta( $id, 'mnsp-slide-desc-color', true ) );

		$cta_text           = esc_html( get_post_meta( $id, 'mnsp-slide-cta-text', true ) );
		$cta_text_color     = esc_attr( get_post_meta( $id, 'mnsp-slide-cta-text-color', true ) );
		$cta_bg_color       = esc_attr( get_post_meta( $id, 'mnsp-slide-cta-bg-color', true ) );
		$cta_hover_bg_color = esc_attr( get_post_meta( $id, 'mnsp-slide-cta-hover-bg-color', true ) );
		$cta_url            = esc_url( get_post_meta( $id, 'mnsp-slide-cta-url', true ) );
		$cta_target         = esc_attr( get_post_meta( $id, 'mnsp-slide-cta-target', true ) ) ? '_blank' : '_self';

		$image = esc_url( get_post_meta( $id, 'mnsp-slide-image', true ) );

		$horiz_align = esc_attr( get_post_meta( $id, 'mnsp-slide-horiz-align', true ) );

		$ratio = esc_attr( get_post_meta( $id, 'mnsp-slide-ratio', true ) );

		$feat_img_opacity = esc_attr( get_post_meta( $id, 'mnsp-slide-featured-opacity', true ) );

		$bg_color = esc_attr( get_post_meta( $id, 'mnsp-slide-bg-color', true ) );

		$has_thumbnail = has_post_thumbnail( $id ) ? ' mnsp-slide-has-thumbnail' : '';

		$slide = '<div 
			id="mnsp-slide-' . $id . '" 
			class="mnsp-slide' . $has_thumbnail . '" 
			data-mnsp-slide-title-color="' . $title_color . '" 
			data-mnsp-slide-subtitle-color="' . $subtitle_color . '"
			data-mnsp-slide-desc-color="' . $desc_color . '" 
			data-mnsp-slide-cta-text-color="' . $cta_text_color . '" 
			data-mnsp-slide-cta-bg-color="' . $cta_bg_color . '" 
			data-mnsp-slide-cta-hover-bg-color="' . $cta_hover_bg_color . '" 
			data-mnsp-slide-horiz-align="' . $horiz_align . '" 
			data-mnsp-slide-ratio="' . $ratio . '" 
			data-mnsp-slide-featured-opacity="' . $feat_img_opacity . '" 
			data-mnsp-slide-bg-color="' . $bg_color . '">';

		$slide .= '<div class="mnsp-slide-caption">';

		if ( ! empty( $image ) ) {
			$slide .= '<img class="mnsp-slide-image" src="' . $image . '" />';
		}

		if ( ! empty( $title ) ) {
			$slide .= '<p class="mnsp-slide-title">' . $title . '</p>';
		}

		if ( ! empty( $subtitle ) ) {
			$slide .= '<p class="mnsp-slide-subtitle">' . $subtitle . '</p>';
		}

		if ( ! empty( $desc ) ) {
			$slide .= '<p class="mnsp-slide-desc">' . $desc . '</p>';
		}

		if ( ! empty( $cta_text ) && ! empty( $cta_url ) ) {
			$slide .= '<a class="mnsp-slide-cta" href="' . $cta_url . '" target="' . $cta_target . '">' . $cta_text . '</a>';
		}

		$slide .= '</div>';

		$slide .= get_the_post_thumbnail( $id, 'mnsp-image-' . $ratio, array( 'class' => 'mnsp-slide-featured' ) );

		$slide .= '</div>';

		return $slide;

	}

	add_shortcode( 'mnsp_slide', 'slide_shortcode' );

}

/**
 * Register the 'mnsp_slider' shortcode for displaying multiple slides in a slider.
 *
 * @since    1.0.0
 */
public function register_slider_shortcode() {

	function slider_shortcode( $atts ) {

		extract( shortcode_atts( array(
			'id' => ''
		), $atts ) );

		return 'id is ' . $id;

	}

	add_shortcode( 'mnsp_slider', 'slider_shortcode' );

}

These two methods contain the functionality for registering and displaying the shortcodes. The method for displaying the slide shortcode is complete and we’ll build the method for displaying a slider in a future post.

Looking at the first method for registering and displaying a single slide we can see that it contains a function declaration and a function call.

The function call to add_shortcode() tells WordPress about the mnsp_slide shortcode and it also tells it to use the function above to unpack and display that shortcode when it is encountered.

Looking at the function declaration, it starts with taking the id parameter from the shortcode declaration and making it available as a variable inside the function, by using the extract() function.

Next, we’re creating a few more variables by reading the metadata of a single slide, based on the id variable we created earlier.

Finally, we’re building the HTML code for displaying a single slide by using the variables we created earlier.

Next, we’re going to add and populate two admin columns that will contain the exact shortcode declaration that we’ll use in our posts and pages for displaying slides and sliders. So, we’ll just do a copy + paste of the content of these columns.

/**
 * Creates a new admin column on the custom post type listing screen that will display the slide shortcode.
 *
 * @since 1.0.0
 * @access public
 */
public function add_slide_columns( $columns ) {

	$columns['shortcode'] = __( 'Shortcode', 'monospace-slides' );
	
	return $columns;

}

/**
 * Displays the slide shortcode in an admin column called 'shortcode'.
 *
 * @since 1.0.0
 * @access public
 */
function add_slide_columns_content( $column, $post_id ){

	if ( 'shortcode' === $column ) {
		echo '[mnsp_slide id="' . $post_id . '"]';
	}

}

/**
 * Creates a new admin column on the custom taxonomy listing screen that will display the slider shortcode.
 *
 * @since 1.0.0
 * @access public
 */
public function add_slider_columns( $columns ) {

	$columns['shortcode'] = __( 'Shortcode', 'monospace-slides' );
	
	return $columns;

}

/**
 * Displays the slider shortcode in an admin column called 'shortcode'.
 *
 * @since 1.0.0
 * @access public
 */
function add_slider_columns_content( $content, $column_name, $term_id ){

	if ( 'shortcode' === $column_name ) {
		$content = '[mnsp_slider id="' . $term_id . '"]';
	}

	return $content;
}

The first two methods create and populate a column for the slide shortcode. The next two methods are similar but for the slider shortcode.

Next we’ll add some CSS and JS code for displaying a single slide. The code below will go in two files from the /public folder, as it loads only on the public facing side of the website. These files should already be present inside the plugin folder structure.

So, the CSS code below goes in /public/css/monospace-slides-public.css file. Put this code inslide the function that already exists in the file.

.mnsp-slide {
	position: relative;
	padding: 0 !important;
}

.mnsp-slide {
	display: flex;
	height: auto;
	justify-content: center;
	align-items: center;
	font-size: 1rem;
	overflow: hidden;
}

.mnsp-slide::before { /* for the aspect ratio functionality */
	content: "";
	width: 1px;
	margin-left: -1px;
	float: left;
	height: 0;
}

.mnsp-slide::after { /* to clear float */
	content: "";
	display: table;
	clear: both;
}

.mnsp-slide[data-mnsp-slide-horiz-align="left"] .mnsp-slide-caption {
	text-align: left;
}

.mnsp-slide[data-mnsp-slide-horiz-align="center"] .mnsp-slide-caption {
	text-align: center;
}

.mnsp-slide[data-mnsp-slide-horiz-align="right"] .mnsp-slide-caption {
	text-align: right;
}

.mnsp-slide-caption {
	position: relative;
	width: 100%;
	padding: 5vmin;
	z-index: 2000;
}

.mnsp-slide-image {
	max-width: 15%;
}

.mnsp-slide-title {
	font-size: 1.25em;
}

.mnsp-slide-subtitle {
	font-size: 1.1em;
}

.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta,
.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta:hover,
.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta:focus,
.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta:active {
	color: white;
	text-decoration: none;
	border: 0;
	box-shadow: none;
}

.mnsp-slide .mnsp-slide-caption .mnsp-slide-cta {
	display: inline-block;
	font-family: Raleway, "Open Sans", "Source Sans Pro", sans-serif;
	font-size: smaller;
	font-weight: bold;
	text-transform: uppercase;
	line-height: 1;
	padding: 16px 30px;
	background-color: black;
	cursor: pointer;
	transition: all 0.5s ease;
}

.mnsp-slide-caption > * + * {
	margin: 0.75rem 0 0 0;
}

.mnsp-slide-featured {
	position: absolute;
	height: 100%;
	object-fit: cover;
	z-index: 1000;
}

.mnsp-slide[data-mnsp-slide-ratio="wide"]::before {
	padding-top: 56.25%;
}

.mnsp-slide[data-mnsp-slide-ratio="classic"]::before {
	padding-top: 75%;
}

.mnsp-slide[data-mnsp-slide-ratio="square"]::before {
	padding-top: 100%;
}

The JS code below goes in /public/js/monospace-slides-public.js file:

var slides  = document.querySelectorAll( '.mnsp-slide' );

slides.forEach( function( slide ) {

	var $this 		= slide,
		title 		= $this.querySelector( '.mnsp-slide-title' ),
		subTitle 	= $this.querySelector( '.mnsp-slide-subtitle' ),
		desc 		= $this.querySelector( '.mnsp-slide-desc' ),
		cta 		= $this.querySelector( '.mnsp-slide-cta' ),
		featured 	= $this.querySelector( '.mnsp-slide-featured' );

	if ( title ) {
		title.style.color = $this.dataset.mnspSlideTitleColor;
	}

	if ( subTitle ) {
		subTitle.style.color = $this.dataset.mnspSlideSubtitleColor;
	}

	if ( desc ) {
		desc.style.color = $this.dataset.mnspSlideDescColor;
	}

	if ( cta ) {
		cta.style.color = $this.dataset.mnspSlideCtaTextColor;
		cta.style.backgroundColor = $this.dataset.mnspSlideCtaBgColor;

		cta.addEventListener( 'mouseover', function() {
			$this.querySelector( '.mnsp-slide-cta' ).style.backgroundColor = $this.dataset.mnspSlideCtaHoverBgColor;
		}, false );

		cta.addEventListener( 'mouseout', function() {
			$this.querySelector( '.mnsp-slide-cta' ).style.backgroundColor = $this.dataset.mnspSlideCtaBgColor;
		}, false );
	}

	$this.style.backgroundColor = $this.dataset.mnspSlideBgColor;

	if ( featured ) {
		featured.style.opacity = $this.dataset.mnspSlideFeaturedOpacity;
	}

} ); // eslint-disable-line

In the final part of this tutorial, we are going to tell WordPress to use all the previous code to create the custom shortcodes functionality.

We’ll start by adding the custom shortcodes class file as a dependency in the Monospace_Slides class located in the /includes/class-monospace-slides.php file.

Add the following code inside the load_dependencies() method, after requiring the taxonomy term meta class file.

/**
 * The class responsible for the functionality of the shortcodes.
 */
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-monospace-slides-shortcodes.php';

This makes sure that the methods inside the Monospace_Slides_Shortcodes are present and ready to be called when WordPress needs them.

Next, add the method below, after the define_term_meta_hooks() method within that same file:

/**
 * Register all the hooks needed for shortcodes functionality
 *
 * @since 		1.0.0
 * @access 		public
 */
public function define_shortcodes_hooks() {

	$plugin_shortcodes = new Monospace_Slides_Shortcodes();
	
	$this->loader->add_action( 'init', $plugin_shortcodes, 'register_slide_shortcode' );
	$this->loader->add_action( 'init', $plugin_shortcodes, 'register_slider_shortcode' );

	$this->loader->add_filter( 'manage_edit-mnsp_slide_columns', $plugin_shortcodes, 'add_slide_columns' );
	$this->loader->add_filter( 'manage_mnsp_slide_posts_custom_column', $plugin_shortcodes, 'add_slide_columns_content', 10, 2 );

	$this->loader->add_filter( 'manage_edit-mnsp_slider_columns', $plugin_shortcodes, 'add_slider_columns' );
	$this->loader->add_filter( 'manage_mnsp_slider_custom_column', $plugin_shortcodes, 'add_slider_columns_content', 10, 3 );

}

The method above uses the methods from the Monospace_Slides_Shortcodes class to register the shortcodes and create the custom admin columns.

Lastly, at the end of the __construct() method call the method we created above as so:

$this->define_shortcodes_hooks();

Now, WordPress knows about the new shortcodes which are ready to be used.

Provided you added the code from this tutorial to the plugin in your WordPress installation and have created some slides, the slides listing screen should look like this:

Slide Custom WordPress Shortcode Admin Column

To display a slide just copy the content of the Shortcode column, including the square brackets and paste it in any post or page.

At The End

We went through a lot of code in this tutorial. We now know hot to create custom WordPress shortcodes as well as display custom content using them.

In the next tutorial from this series we’ll build the functionality for displaying multiple slides into an animated slider. As you remember we only built the basic function for doing that earlier in the start of this tutorial.

In the meantime, please feel free to share your thoughts and opinions on the contents of this tutorial and I’ll make sure to address all the issues.

Create An Image Slide Plugin Part 6: Create Taxonomy Term Meta

In this post we’ll be adding a few pieces of metadata to the sliders we’ll be creating using the taxonomy we added to our custom post type in an earlier post.

In the previous post we talked about metadata and we created a custom meta box in which we collect additional information about each slide we create.

The metadata we’ll be adding in this post will concern each slider that will be created for collecting multiple slides. The new metadata will affect the behavior of each slider in various ways.

The metadata for each slider will consist of:

  • Transition type
  • Transition duration
  • Timeout between slides
  • Show navigation
  • Pause slider on hover over slide
  • Autoplay
  • Aspect ratio

To add the term metadata we’ll be extending the term edit form which you can see in the screenshot:

WordPress taxonomy term meta

Create Taxonomy Term Meta In WordPress

If you want to follow along I’ve set up a GitHub repository where you’ll find the code from this series. The finished code for this post is in the part_6 branch. If you want to follow along please download the part_5 branch.

In this post we’re gonna be working in two files: /includes/class-monospace-slides.php and /admin/class-monospace-slides-taxonomy-term-meta.php which doesn’t exist so we’ll have to create it.

Adding metadata to taxonomy terms is done by using dynamically built hooks. So, we’ll be using a hook like {$taxonomy}_add_form_fields to add the meta fields. For our mnsp_slider custom taxonomy this will be mnsp_lider_add_form_fields.

In order to save the term metadata we’ll hook into the action that fires when the term is being saved, that hook being created_{$taxonomy}. For our specific taxonomy that is created_mnsp_slider.

When we want to update the term meta we’ll need to use yet another hook in the form of {$taxonomy}_edit_form_fields. In the case of our slider taxonomy we’ll be using the mnsp_slider_edit_form_fields hook.

The last scenario is for saving the updated fields which uses a hook in the form of edited_{$taxonomy}. Applied to our taxonomy, the hook becomes edited_mnsp_slider.

Now, let’s get on to coding the functionality we’ve outlined above.

Starting in the first file we mentioned earlier, we’re gonna tell the constructor of the class to run a function that defines the hooks needed for our taxonomy term metadata.

We’ll do this on the last line from the function below in the same way we added the hooks for the custom meta box.

/**
 * Define the core functionality of the plugin.
 *
 * Set the plugin name and the plugin version that can be used throughout the plugin.
 * Load the dependencies, define the locale, and set the hooks for the admin area and
 * the public-facing side of the site.
 *
 * @since    1.0.0
 */
public function __construct() {
	if ( defined( 'PLUGIN_NAME_VERSION' ) ) {
		$this->version = PLUGIN_NAME_VERSION;
	} else {
		$this->version = '1.0.0';
	}
	$this->plugin_name = 'monospace-slides';

	$this->load_dependencies();
	$this->set_locale();
	$this->define_admin_hooks();
	$this->define_public_hooks();

	$this->define_metabox_hooks();
	$this->define_term_meta_hooks();

}

Next, we need to create the define_term_meta_hooks()  function in which we’ll define the hooks needed to create the term metadata. The function below contains that code.

Add the code below after the define_metabox_hooks() function, near the end of the file.

/**
 * Register all of the hooks related to taxonomy term meta
 *
 * @since 		1.0.0
 * @access 		private
 */
private function define_term_meta_hooks() {

	$plugin_term_meta = new Monospace_Slides_Taxonomy_Term_Meta( $this->get_plugin_name(), $this->get_version() );

	if ( is_admin() ) {
		$this->loader->add_action( 'mnsp_slider_add_form_fields', $plugin_term_meta, 'create_fields', 10, 2 );
		$this->loader->add_action( 'mnsp_slider_edit_form_fields', $plugin_term_meta, 'edit_fields', 10, 2 );
		$this->loader->add_action( 'created_mnsp_slider', $plugin_term_meta, 'save_fields', 10, 1 );
		$this->loader->add_action( 'edited_mnsp_slider', $plugin_term_meta, 'save_fields', 10, 1 );
	}

}

As you can see, all those dynamic hooks we talked about earlier are used in this function.

This is all the code that we’ll be adding in this file.

Next, we’re moving on to add the code in the newly created /admin/class-monospace-slides-admin-taxonomy-term-meta.php file.

Here is the entire contents of the file which we’ll examine closely.

<?php

/**
 * The admin-specific functionality for the slider taxonomy term metadata.
 *
 * @link       https://www.mikeinmonospace.com/
 * @since      1.0.0
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/admin
 */

/**
 * The admin-specific functionality for the slider taxonomy term metadata.
 *
 * Defines all the functionality related to the slide options metabox.
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/admin
 * @author     Mike In Monospace <mikeinmonospace@gmail.com>
 */

class Monospace_Slides_Taxonomy_Term_Meta {

	/**
	 * The ID of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string    $plugin_name    The ID of this plugin.
	 */
	private $plugin_name;

	/**
	 * The version of this plugin.
	 *
	 * @since    1.0.0
	 * @access   private
	 * @var      string    $version    The current version of this plugin.
	 */
	private $version;

	public function meta_fields() {

		return
			array(
				array(
					'label' => esc_html__( 'Transition Type', 'monospace-slides' ),
					'id' => 'mnsp-slider-transition-type',
					'default' => 'slide',
					'type' => 'select',
					'options' => array(
						'slide' => esc_html__( 'Slide', 'monospace-slides' ),
						'fade' => esc_html__( 'Fade', 'monospace-slides' ),
					),
				),
				array(
					'label' => esc_html__( 'Transition Duration', 'monospace-slides' ),
					'id' => 'mnsp-slider-transition-duration',
					'default' => 300,
					'type' => 'number',
				),
				array(
					'label' => esc_html__( 'Timeout Between Slides', 'monospace-slides' ),
					'id' => 'mnsp-slider-timeout',
					'default' => 3000,
					'type' => 'number',
				),
				array(
					'label' => esc_html__( 'Show Navigation Controls', 'monospace-slides' ),
					'id' => 'mnsp-slider-show-nav',
					'default' => '1',
					'type' => 'checkbox',
				),
				array(
					'label' => esc_html__( 'Pause Slider On Hover Over Slide', 'monospace-slides' ),
					'id' => 'mnsp-slider-pause-hover',
					'default' => '1',
					'type' => 'checkbox',
				),
				array(
					'label' => esc_html__( 'Autoplay', 'monospace-slides' ),
					'id' => 'mnsp-slider-autoplay',
					'default' => '1',
					'type' => 'checkbox',
				),
				array(
					'label' => esc_html__( 'Aspect Ratio', 'monospace-slides' ),
					'id' => 'mnsp-slider-aspect',
					'default' => 'wide',
					'type' => 'radio',
					'options' => array(
						'wide' => esc_html__( 'Wide 16:9', 'monospace-slides' ),
						'classic' => esc_html__( 'Classic 4:3', 'monospace-slides' ),
						'square' => esc_html__( 'Square 1:1', 'monospace-slides' ),
					),
				),
			);

	}

	public function __construct() {

		$this->plugin_name = $plugin_name;
		$this->version = $version;

	}

	public function create_fields( $taxonomy ) {

		$output = '';

		foreach ( $this->meta_fields() as $meta_field ) {

			$label = '<label for="' . $meta_field['id'] . '">' . $meta_field['label'] . '</label>';

			$meta_value = $meta_field['default'];			

			switch ( $meta_field['type'] ) {
				case 'checkbox':
					$input = sprintf(
						'<input %s id="%s" name="%s" type="checkbox" value="1">',
						$meta_value === '1' ? 'checked' : '',
						$meta_field['id'],
						$meta_field['id']
						);
					break;
				case 'radio':
					$input = '<fieldset>';
					$input .= '<legend class="screen-reader-text">' . $meta_field['label'] . '</legend>';
					$i = 0;
					foreach ( $meta_field['options'] as $key => $value ) {
						$meta_field_value = !is_numeric( $key ) ? $key : $value;
						$input .= sprintf(
							'<label><input %s id="%s" name="%s" type="radio" value="%s"> %s</label>%s',
							$meta_value === $meta_field_value ? 'checked' : '',
							$meta_field['id'],
							$meta_field['id'],
							$meta_field_value,
							$value,
							$i < count( $meta_field['options'] ) - 1 ? '<br>' : ''
						);
						$i++;
					}
					$input .= '</fieldset>';
					break;
				case 'select':
					$input = sprintf(
						'<select id="%s" name="%s">',
						$meta_field['id'],
						$meta_field['id']
					);
					foreach ( $meta_field['options'] as $key => $value ) {
						$meta_field_value = !is_numeric( $key ) ? $key : $value;
						$input .= sprintf(
							'<option %s value="%s">%s</option>',
							$meta_value === $meta_field_value ? 'selected' : '',
							$meta_field_value,
							$value
						);
					}
					$input .= '</select>';
					break;
				case 'number':
					$input = sprintf(
						'<input id="%s" name="%s" type="%s" value="%s">',
						$meta_field['id'],
						$meta_field['id'],
						$meta_field['type'],
						$meta_value
					);
					break;
				default:
					$input = sprintf(
						'<input id="%s" name="%s" type="%s" value="%s">',
						$meta_field['id'],
						$meta_field['id'],
						$meta_field['type'],
						$meta_value
					);
			}

			$output .= '<div class="form-field">'.$this->format_rows( $label, $input ).'</div>';

		}

		echo $output;
	}

	public function edit_fields( $term, $taxonomy ) {

		$output = '';

		foreach ( $this->meta_fields() as $meta_field ) {

			$label = '<label for="' . $meta_field['id'] . '">' . $meta_field['label'] . '</label>';

			$meta_value = get_term_meta( $term->term_id, $meta_field['id'], true );

			switch ( $meta_field['type'] ) {
				case 'checkbox':
					$input = sprintf(
						'<input %s id=" % s" name="% s" type="checkbox" value="1">',
						$meta_value === '1' ? 'checked' : '',
						$meta_field['id'],
						$meta_field['id']
						);
					break;
				case 'radio':
					$input = '<fieldset>';
					$input .= '<legend class="screen-reader-text">' . $meta_field['label'] . '</legend>';
					$i = 0;
					foreach ( $meta_field['options'] as $key => $value ) {
						$meta_field_value = !is_numeric( $key ) ? $key : $value;
						$input .= sprintf(
							'<label><input %s id=" % s" name="% s" type="radio" value="% s"> %s</label>%s',
							$meta_value === $meta_field_value ? 'checked' : '',
							$meta_field['id'],
							$meta_field['id'],
							$meta_field_value,
							$value,
							$i < count( $meta_field['options'] ) - 1 ? '<br>' : ''
						);
						$i++;
					}
					$input .= '</fieldset>';
					break;
				case 'select':
					$input = sprintf(
						'<select id="%s" name="%s">',
						$meta_field['id'],
						$meta_field['id']
					);
					foreach ( $meta_field['options'] as $key => $value ) {
						$meta_field_value = !is_numeric( $key ) ? $key : $value;
						$input .= sprintf(
							'<option %s value="%s">%s</option>',
							$meta_value === $meta_field_value ? 'selected' : '',
							$meta_field_value,
							$value
						);
					}
					$input .= '</select>';
					break;
				case 'number':
					$input = sprintf(
						'<input id="%s" name="%s" type="%s" value="%s">',
						$meta_field['id'],
						$meta_field['id'],
						$meta_field['type'],
						$meta_value
					);
					break;
				default:
					$input = sprintf(
						'<input id="%s" name="%s" type="%s" value="%s">',
						$meta_field['id'],
						$meta_field['id'],
						$meta_field['type'],
						$meta_value
					);
			}

			$output .= $this->format_rows( $label, $input );

		}

		echo '<div class="form-field">' . $output . '</div>';

	}

	public function format_rows( $label, $input ) {

		return '<tr class="form-field"><th>'.$label.'</th><td>'.$input.'</td></tr>';

	}

	public function save_fields( $term_id ) {

		foreach ( $this->meta_fields() as $meta_field ) {

			if ( isset( $_POST[ $meta_field['id'] ] ) ) {
				switch ( $meta_field['type'] ) {
					case 'email':
						$_POST[ $meta_field['id'] ] = sanitize_email( $_POST[ $meta_field['id'] ] );
						break;
					case 'text':
						$_POST[ $meta_field['id'] ] = sanitize_text_field( $_POST[ $meta_field['id'] ] );
						break;
				}

				update_term_meta( $term_id, $meta_field['id'], $_POST[ $meta_field['id']] );
			} else if ( $meta_field['type'] === 'checkbox' ) {
				update_term_meta( $term_id, $meta_field['id'], '0' );
			}

		}

	}

}

Taking a closer look at this file, we notice that it looks a lot like the file we created for the custom meta box in the previous post. The functionality is pretty much the same, it just applies to different content.

In the previous post we added metadata to the mnsp_slide custom content type. In this post we’re adding metadata to the mnsp_slider custom taxonomy of that custom content type.

So, let’s go through this file and break it down so we can better understand what’s going on.

Just like in the previous post, we’re creating a class to house of the functionality for the taxonomy term meta.

Next, the meta_fields() function defines all the fields that are added to every taxonomy term and then come the functions for creating, editing and saving the metadata fields.

Now, if you add these changes to the plugin in your WordPress installation and go to Slides > Sliders, you should see something like the first image in this post, that is all the new metadata fields in the term edit form.

At The End

There’s been a lot of code in this post, but it has been very familiar because it was very similar to the code in the previous post so you should not have any major issues.

In a future post we’ll use all these meta fields values to create a slider on the front facing side of our WordPress site by reading and outputting the values from the database.

While the next post is in the making, please feel free to share your thoughts regarding this post or the entire series in general as to what we’ve done so far or where you’re having trouble, if any.

Create An Image Slide Plugin Part 5: Create A Custom Meta Box

Here we are with another post of our series in which we’re gonna create a custom meta box for our slide post type in which we’ll be able set some custom options that we’ll be defining later in this post.

In the previous post we created a custom taxonomy for grouping multiple slides into a slider, which actually added the Sliders custom meta box to the Add New Slide screen.

The custom meta box that we are building in the post will be visible on every ‘Add New Slide’ admin screen and will provide some custom fields for setting options and additional content for each slide.

In WordPress, these additional pieces of information that we’re setting are referred to as metadata.

What Is WordPress Metadata?

We’ve talked about metadata in a previous post and we’ve defined it as information about information. If you think about it a bit you’ll start to get it 🙂

Here are some helpful questions to make it easier to understand.

What are we saying in the Sliders meta box from the previous post?
We’re saying to which slider a slide belongs.

What is that, actually? Isn’t it a piece of information about the slide post?
Yes, it is.

Other examples of metadata from a default WordPress post are the post ID, the post author or the post date. This is data that describes a post in some way.

So, there you have it.
Metadata is information about information.

In WordPress, metadata exists in the form of key / value pairs. For example, the post_id key has a value of 123 or post_author key has a value of John Doe.

What Are Custom Meta Boxes?

We can see meta boxes all over the admin screens.

WordPress categories meta box

When creating posts we can see such fields and boxes like the Post Title, Categories, Tags and Featured Image boxes that contain fields for setting those specific options.

They are just a nice way of organizing some fields or metadata for setting options for specific WordPress content. The custom meta boxes are defined by users, usually developers, depending on their needs for a specific plugin or theme.

The Sliders box that appears on the ‘Add New Slide’ screen is such a meta box.

As we’ve said before, meta boxes are made up of fields that are used to set different options. These fields can be any HTML form field type you can think of.

Meta boxes are very flexible entities in that they can be hidden or showed to different users and they can be easily rearranged on the screen giving users control over their editing workflow.

Custom meta boxes really shine when used together with custom content types because they provide a way of integrating custom options into the native look and feel of the WordPress interface.

Using WordPress Custom Meta Boxes:

Using this piece of functionality involves a series of steps:

  • Establishing the custom fields that the meta box contains.
    This is usually done with an array that contains the label, type and ID of each field of the meta box.
  • Adding and displaying the meta box in the correct place.
    To achieve this we use the add_meta_box() built in WordPress function in which we have to say where the meta box should appear.
  • Handling data validation for the fields in the meta box.
    In this step we check the content entered in each field to make sure it is what we asked for and to impose some security measures.
  • Saving the metadata that the user enters in the fields of the meta box.
    This is done using the update_post_meta() built in WordPress function that saves or updates the data into the database.
  • Retrieving and displaying the custom metadata on the front end.

In this post we’ll be talking about the first four steps while the fifth step will be the subject of another installment.

Create A Custom Meta Box

If you want to follow along I’ve set up a GitHub repository where you’ll find the code from this series. The finished code for this post is in the part_5 branch. If you want to follow along please download the part_4 branch.

In this post we’re gonna be working in three files: /includes/class-monospace-slides.php/admin/class-monospace-slides-admin-metabox.php and /admin/js/wp-media.js. The last two files don’t exist so we’ll create them at the appropriate times.

Starting in the first file, we’re gonna tell the constructor of the class to run a function that defines a few hooks for our custom meta box functionality.

We’re adding the last line like in the code below which is pretty self explanatory.

/**
 * Define the core functionality of the plugin.
 *
 * Set the plugin name and the plugin version that can be used throughout the plugin.
 * Load the dependencies, define the locale, and set the hooks for the admin area and
 * the public-facing side of the site.
 *
 * @since    1.0.0
 */
public function __construct() {
	if ( defined( 'PLUGIN_NAME_VERSION' ) ) {
		$this->version = PLUGIN_NAME_VERSION;
	} else {
		$this->version = '1.0.0';
	}
	$this->plugin_name = 'monospace-slides';

	$this->load_dependencies();
	$this->set_locale();

	$this->define_admin_hooks();
	$this->define_cpt_hooks();
	$this->define_tax_hooks();
	$this->define_metabox_hooks();

	$this->define_public_hooks();
}

Next, we need to create the define_metabox_hooks()  functions in which we’ll define the hooks needed by the meta box to work.

Add the code below after the define_public_hooks() function.

/**
 * Register all of the hooks related to metaboxes
 *
 * @since 		1.0.0
 * @access 		private
 */
private function define_metabox_hooks() {

	$plugin_metaboxes = new Monospace_Slides_Admin_Metabox( $this->get_plugin_name(), $this->get_version() );

	$this->loader->add_action( 'admin_enqueue_scripts', $plugin_metaboxes, 'enqueue_styles' );
	$this->loader->add_action( 'admin_enqueue_scripts', $plugin_metaboxes, 'enqueue_scripts' );

	$this->loader->add_action( 'add_meta_boxes', $plugin_metaboxes, 'add_meta_boxes' );
	
	$this->loader->add_action( 'save_post', $plugin_metaboxes, 'save_fields' );

}

That’s it in this file.

We’re moving on to add the code in the newly created /admin/class-monospace-slides-admin-metabox.php file.

Here is the entire contents of the file which we’ll examine closely.

<?php
/**
 * The admin-specific functionality for the slide options metabox.
 *
 * @link       https://www.mikeinmonospace.com/
 * @since      1.0.0
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/admin
 */

/**
 * The admin-specific functionality for the slide options metabox.
 *
 * Defines all the functionality related to the slide options metabox.
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/admin
 * @author     Mike In Monospace <mikeinmonospace@gmail.com>
 */
class Monospace_Slides_CPT_Meta {

	/**
	 * The current version of the plugin.
	 *
	 * @since    1.0.0
	 * @access   protected
	 * @var      string    $version    The current version of the plugin.
	 */
	protected $version;

	/**
	 * The post type screen on which the metabox will be visible.
	 *
	 * @since  1.0.0
	 * @access private
	 * @var    array   $version    The post type screen on which the metabox will be visible.
	 */
	private $screen = array( 'mnsp_slide' );

	/**
	 * Initialize the class and set its properties.
	 *
	 * @since 1.0.0
	 * @param string $version    The version of this plugin.
	 */
	public function __construct( $version ) {

		$this->version = $version;

	}

	/**
	 * Register the stylesheets for the admin-facing side of the custom post type meta box.
	 *
	 * @since 1.0.0
	 */
	public function enqueue_styles() {

		// CSS rules for Color Picker.
		wp_enqueue_style( 'wp-color-picker' );

	}

	/**
	 * Register the Javascript for the admin-facing side of the custom post type meta box.
	 *
	 * @since 1.0.0
	 */
	public function enqueue_scripts() {

		// The Javascript for the Media Library.
		wp_enqueue_script( 'wp-media', plugin_dir_url( __FILE__ ) . 'js/wp-media.js', array( 'jquery' ), $this->version, true );

	}

	/**
	 * Add the meta box on the screens defined in the 'screen' propery.
	 *
	 * @since 1.0.0
	 */
	public function add_meta_boxes() {

		foreach ( $this->screen as $single_screen ) {
			add_meta_box(
				'monospace_slides_admin_metabox',
				esc_html__( 'Slide Options', 'monospace-slides' ),
				array( $this, 'meta_box_callback' ),
				$single_screen,
				'normal',
				'high'
			);
		}

	}

	/**
	 * Generate the fields of the custom meta box.
	 *
	 * @since 1.0.0
	 * @param object $post    The post object.
	 */
	public function meta_box_callback( $post ) {

		wp_nonce_field( 'monospace_slides_admin_metabox_data', 'monospace_slides_admin_metabox_nonce' );
		esc_html_e( 'Set the options for the current slide.', 'monospace-slides' );
		$this->field_generator( $post );

	}

	/**
	 * Defines the fields of the meta box.
	 *
	 * @since 1.0.0
	 */
	public function meta_fields() {

		return array(
			array(
				'label'       => esc_html__( 'Title', 'monospace-slides' ),
				'id'          => 'mnsp-slide-title',
				'placeholder' => esc_html__( 'The title for the current slide.', 'monospace-slides' ),
				'type'        => 'text',
			),
			array(
				'label'   => esc_html__( 'Title Text Color', 'monospace-slides' ),
				'id'      => 'mnsp-slide-title-color',
				'default' => '#000000',
				'type'    => 'color',
			),
			array(
				'label'       => esc_html__( 'Subtitle', 'monospace-slides' ),
				'id'          => 'mnsp-slide-subtitle',
				'placeholder' => esc_html__( 'The subtitle for the current slide.', 'monospace-slides' ),
				'type'        => 'text',
			),
			array(
				'label'   => esc_html__( 'Subtitle Text Color', 'monospace-slides' ),
				'id'      => 'mnsp-slide-subtitle-color',
				'default' => '#000000',
				'type'    => 'color',
			),
			array(
				'label'       => esc_html__( 'Description', 'monospace-slides' ),
				'id'          => 'mnsp-slide-desc',
				'placeholder' => esc_html__( 'The description of the current slide.', 'monospace-slides' ),
				'type'        => 'textarea',
			),
			array(
				'label'   => esc_html__( 'Description Text Color', 'monospace-slides' ),
				'id'      => 'mnsp-slide-desc-color',
				'default' => '#000000',
				'type'    => 'color',
			),
			array(
				'label'       => esc_html__( 'Call To Action Text', 'monospace-slides' ),
				'id'          => 'mnsp-slide-cta-text',
				'placeholder' => esc_html__( 'The text for the Call To Action button.', 'monospace-slides' ),
				'type'        => 'text',
			),
			array(
				'label'   => esc_html__( 'Call To Action Text Color', 'monospace-slides' ),
				'id'      => 'mnsp-slide-cta-text-color',
				'default' => '#ffffff',
				'type'    => 'color',
			),
			array(
				'label'   => esc_html__( 'Call To Action Background Color', 'monospace-slides' ),
				'id'      => 'mnsp-slide-cta-bg-color',
				'default' => '#000000',
				'type'    => 'color',
			),
			array(
				'label'   => esc_html__( 'Call To Action Hover Background Color', 'monospace-slides' ),
				'id'      => 'mnsp-slide-cta-hover-bg-color',
				'default' => '#777777',
				'type'    => 'color',
			),
			array(
				'label'       => esc_html__( 'Call To Action URL', 'monospace-slides' ),
				'id'          => 'mnsp-slide-cta-url',
				'placeholder' => esc_html__( 'http://', 'monospace-slides' ),
				'type'        => 'url',
			),
			array(
				'label'   => esc_html__( 'Open Call To Action URL In A New Window', 'monospace-slides' ),
				'id'      => 'mnsp-slide-cta-target',
				'default' => 'target',
				'type'    => 'checkbox',
			),
			array(
				'label' => esc_html__( 'Image', 'monospace-slides' ),
				'id'    => 'mnsp-slide-image',
				'type'  => 'media',
			),
			array(
				'label'   => esc_html__( 'Horizontal Alignment', 'monospace-slides' ),
				'id'      => 'mnsp-slide-horiz-align',
				'default' => 'center',
				'type'    => 'select',
				'options' => array(
					'left'   => esc_attr__( 'Left', 'monospace-slides' ),
					'center' => esc_attr__( 'Center', 'monospace-slides' ),
					'right'  => esc_attr__( 'Right', 'monospace-slides' ),
				),
			),
			array(
				'label'   => esc_html__( 'Aspect Ratio', 'monospace-slides' ),
				'id'      => 'mnsp-slide-ratio',
				'default' => 'wide',
				'type'    => 'select',
				'options' => array(
					'wide'    => esc_attr__( 'Wide', 'monospace-slides' ),
					'classic' => esc_attr__( 'Classic', 'monospace-slides' ),
					'square'  => esc_attr__( 'Square', 'monospace-slides' ),
				),
			),
			array(
				'label'   => esc_html__( 'Featured Image Opacity', 'monospace-slides' ),
				'id'      => 'mnsp-slide-featured-opacity',
				'default' => '1',
				'type'    => 'range',
				'options' => array(
					'min'  => '0.1',
					'max'  => '1',
					'step' => '0.1',
				),
			),
			array(
				'label'   => esc_html__( 'Background Color', 'monospace-slides' ),
				'id'      => 'mnsp-slide-bg-color',
				'default' => '#000000',
				'type'    => 'color',
			),
		);

	}

	/**
	 * Loops through all the defined fields and builds the HTML needed to show each field.
	 *
	 * @since 1.0.0
	 * @param object $post    The post object.
	 */
	public function field_generator( $post ) {

		$output = '';

		foreach ( $this->meta_fields() as $meta_field ) {
			$label = '<label for="' . $meta_field['id'] . '">' . $meta_field['label'] . '</label>';

			$meta_value = get_post_meta( $post->ID, $meta_field['id'], true );

			if ( 'checkbox' !== $meta_field['type'] && empty( $meta_value ) ) {
				if ( isset( $meta_field['default'] ) ) {
					$meta_value = $meta_field['default'];
				} else {
					$meta_value = '';
				}
			}

			switch ( $meta_field['type'] ) {
				case 'media':
					$input = sprintf(
						'<input id="%s" name="%s" type="text" value="%s"><input class="button monospace_slides_admin_metabox-media" id="%s_button" name="%s_button" type="button" value="Upload" />',
						$meta_field['id'],
						$meta_field['id'],
						$meta_value,
						$meta_field['id'],
						$meta_field['id']
					);
					break;
				case 'checkbox':
					$input = sprintf(
						'<input id="%s" name="%s" type="checkbox" value="%s" %s>',
						$meta_field['id'],
						$meta_field['id'],
						$meta_field['default'],
						checked( $meta_field['default'], $meta_value, false )
					);
					break;
				case 'select':
					$input = sprintf(
						'<select id="%s" name="%s">',
						$meta_field['id'],
						$meta_field['id']
					);

					foreach ( $meta_field['options'] as $key => $value ) {
						$input .= sprintf(
							'<option value="%s" %s>%s</option>',
							$key,
							selected( $meta_value, $key, false ),
							$value
						);
					}

					$input .= '</select>';
					break;
				case 'range':
					$input = sprintf(
						'<input id="%s" name="%s" type="%s" min="%s" max="%s" step="%s" value="%s">',
						$meta_field['id'],
						$meta_field['id'],
						$meta_field['type'],
						$meta_field['options']['min'],
						$meta_field['options']['max'],
						$meta_field['options']['step'],
						$meta_value
					);
					break;
				case 'textarea':
					$input = sprintf(
						'<textarea %s id="%s" name="%s" rows="5" placeholder="%s">%s</textarea>',
						'style="width: 100%"',
						$meta_field['id'],
						$meta_field['id'],
						isset( $meta_field['placeholder'] ) ? $meta_field['placeholder'] : '',
						$meta_value
					);
					break;
				case 'color':
					$input = sprintf(
						'<input id="%s" name="%s" type="%s" value="%s">',
						$meta_field['id'],
						$meta_field['id'],
						$meta_field['type'],
						$meta_value
					);
					break;
				default:
					$input = sprintf(
						'<input %s id="%s" name="%s" type="%s" placeholder="%s" value="%s">',
						'style="width: 100%"',
						$meta_field['id'],
						$meta_field['id'],
						$meta_field['type'],
						isset( $meta_field['placeholder'] ) ? $meta_field['placeholder'] : '',
						$meta_value
					);
			}

			$output .= $this->format_rows( $label, $input );

		}

		echo '<table class="monospace-slides-admin-metabox-form form-table"><tbody>' . wp_kses( $output, Monospace_Slides::expanded_alowed_tags() ) . '</tbody></table>';

	}

	/**
	 * Formats each row of the metabox
	 *
	 * @since    1.0.0
	 * @param string $label    The label of the field.
	 * @param string $input    The input of the field.
	 */
	public function format_rows( $label, $input ) {

		return '<tr><th>' . $label . '</th><td>' . $input . '</td></tr>';

	}

	/**
	 * Saves the information from the meta box fields into the database.
	 *
	 * @since 1.0.0
	 * @param int $post_id    The id of the post.
	 */
	public function save_fields( $post_id ) {

		if ( ! isset( $_POST['monospace_slides_admin_metabox_nonce'] ) ) {
			return $post_id;
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['monospace_slides_admin_metabox_nonce'] ) ), 'monospace_slides_admin_metabox_data' ) ) {
			return $post_id;
		}

		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return $post_id;
		}

		foreach ( $this->meta_fields() as $meta_field ) {
			if ( isset( $_POST[ $meta_field['id'] ] ) ) {
				switch ( $meta_field['type'] ) {
					case 'text':
						$meta_value = sanitize_text_field( wp_unslash( $_POST[ $meta_field['id'] ] ) );
						break;
					case 'textarea':
						$meta_value = sanitize_textarea_field( wp_unslash( $_POST[ $meta_field['id'] ] ) );
						break;
					default:
						$meta_value = sanitize_meta( $meta_field['id'], wp_unslash( $_POST[ $meta_field['id'] ] ), 'post' );
				}

				update_post_meta( $post_id, $meta_field['id'], $meta_value );
			} elseif ( 'checkbox' === $meta_field['type'] ) {
				delete_post_meta( $post_id, $meta_field['id'] );
			}
		}

	}

}

That’s a lot of code 🙂

Let’s go through it and break it down into smaller pieces to make it easier to understand.

First, you’ll notice we’re creating a class to contain all the functionality of the custom meta box.

The first important thing to notice is the $screen variable which defines all the content types for which the meta box will be available. In our case it will be available only for our custom content type we created earlier.

Next, we have two functions for loading CSS styles and Javascript functionality.

The next function uses the add_meta_box() built in WordPress function to add the meta box in the correct place, our custom content type in this case.

The meta_fields() functions defines all the fields that the meta box contains and the next function builds the HTML needed to show the meta box on screen.

The save_fields() function is a very important one. Besides doing what its name says, that is saving the values provided in the meta box fields in the database, it also secures that input against mistakes or malicious attempts.

Most notably, it passes each value entered in the meta box fields through built in WordPress data validation functions which validate and ‘correct’ that data. After doing that it updates the values in the database or creates them if they don’t exist.

The last step is to create the new file /admin/js/wp-media.js which will handle the opening and closing of the WordPress Media Library so we can include an additional image in our slide.

Here is the code for this file:

( function( $ ) {
    'use strict';

    /**
	 * Functionality used for the WordPress Media Library window.
	 */
    var customMedia        = true,
        origSendAttachment = '';

	if ( 'undefined' !== typeof wp.media ) {
        origSendAttachment = wp.media.editor.send.attachment;

        $( '.monospace_slides_admin_metabox-media' ).on( 'click', function() {
            var button = $( this ),
                id     = button.attr( 'id' ).replace( '_button', '' );

            customMedia = true;

            wp.media.editor.send.attachment = function( props, attachment ) {
                if ( customMedia ) {
                    $( 'input#' + id ).val( attachment.url );
                } else {
                    return origSendAttachment.apply( this, [ props, attachment ] ); // eslint-disable-line
                };
            };

            wp.media.editor.open( button );

            return false;
        } ); // eslint-disable-line

        $( '.add_media' ).on( 'click', function() {
            customMedia = false;
        } ); // eslint-disable-line
    }
} ( jQuery ) );

Now, if you add these changes to the plugin in your WordPress installation and go to Slides > Add New, you should see something like below:

At The End

There’s been a lot of code in this post as we created a custom meta box for our slide custom content type in which we’ll be able to set some options and the behavior of each slide we create.

In a future post we’ll use all these options to create a slide on the front facing side of our WordPress site by reading and outputting the values from the database.

Until our next post in this series please feel free to share your thoughts and opinions on what we did in this post as well as other topics you’d like to see covered.

Create An Image Slide Plugin Part 4: Create A Custom Taxonomy

Picking off where we left off, in this post we’ll be talking about grouping two or more of our slides into a slider by creating a custom taxonomy.

In the previous post we talked about creating a custom content type for our slides an as we’ve seen, it is a pretty straight forward process involving naming and some settings for the new content we want to create.

What Is A Taxonomy?

Seriously, how many times have you heard the word taxonomy? 🙂 It’s one of those words that you rarely hear and don’t know what they mean.

I hadn’t heard the word until I started working with WordPress plugins. Basically, a taxonomy is a classification, a grouping of some sorts. So it is in WordPress that it uses taxonomies to classify or group things together.

Taxonomies are used widely on any type of site or web application. We might use them to group types of animals on a zoo’s website or the many sports on a TV channel’s site. In a music app we might use taxonomies for music genres or the year of release for songs and albums.

Taxonomies can be hierarchical or not. In hierarchical taxonomies, the elements can have a parent and multiple children while in the non-hierarchical ones, the elements are arranged in a flat structure, each on the same level.

What Is A Taxonomy Term?

To actually group things together, a taxonomy must have some identifiers that we can assign to different pieces of content so that they relate to each other in some way.

In a WordPress taxonomy, those identifiers are called, terms.

On this blog we have a ‘Category’ taxonomy that has multiple terms like: ‘WordPress’, ‘Plugins’ or ‘Tutorials’.

The hierarchy that we’ve talked about in the previous section refers to the relationships between the terms of a taxonomy. So, they can have parents and children or they can be laid out flat, at the same level.

Default WordPress Taxonomies

Out of the box, WordPress offers two taxonomies:

  • categories
  • tags

A third taxonomy can be used by adding support for it: post formats.

Categories are usually a broad way of grouping posts together and most of the times are defined before adding the content to a site, with the possibility of adding new ones, of course.

Tags, on the other hand, tend to be more specific and are usually set at the time of writing the content. They are more specific to the topic that you’ve written about.

A common practice is to have few categories like one, two or three at the most while tags can be much more numerous.

Creating A Custom Taxonomy

If you want to follow along I’ve set up a GitHub repository where you’ll find the code from this series. The finished code for this post is in the part_4 branch. If you want to follow along please download the part_3 branch.

Just like in the previous post where we created a custom content type, the process of creating a custom taxonomy is pretty straightforward.

Here is the process we’ll go through:

  • We’ll use the register_taxonomy() built-in function to tell WordPress about the new taxonomy.
  • We’ll use the init hook as the the moment in the WordPress loading sequence to make the new taxonomy available.
  • The new taxonomy name will be mnsp_slider, as we’ll group multiple slides into an animated slideshow or slider.

As with the custom content type, we’ll create a new file called /admin/class-monospace-slides-tax.php. As you can see it is located in the /admin folder.

Register A Custom Taxonomy In WordPress

Open the newly created file and add the following code:

<?php
/**
 * The admin-specific functionality for creating a custom taxonomy.
 *
 * @link       https://www.mikeinmonospace.com/
 * @since      1.0.0
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/admin
 */

/**
 * The admin-specific functionality for creating a custom taxonomy.
 *
 * Defines all the functions needed to register and manage a custom taxonomy.
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/admin
 * @author     Mike In Monospace <mikeinmonospace@gmail.com>
 */
class Monospace_Slides_TAX {

	/**
	 * Registers a new taxonomy for a custom post type.
	 *
	 * @since  1.0.0
	 * @access public
	 * @uses   register_taxonomy()
	 */
	public static function new_tax_slider() {

		$tax_name = 'mnsp_slider';

		$labels = array(
			'name'              => esc_html_x( 'Sliders', 'taxonomy general name', 'monospace-slides' ),
			'singular_name'     => esc_html_x( 'Slider', 'taxonomy singular name', 'monospace-slides' ),
			'search_items'      => esc_html__( 'Search Sliders', 'monospace-slides' ),
			'all_items'         => esc_html__( 'All Sliders', 'monospace-slides' ),
			'parent_item'       => esc_html__( 'Parent Slider', 'monospace-slides' ),
			'parent_item_colon' => esc_html__( 'Parent Slider:', 'monospace-slides' ),
			'edit_item'         => esc_html__( 'Edit Slider', 'monospace-slides' ),
			'update_item'       => esc_html__( 'Update Slider', 'monospace-slides' ),
			'add_new_item'      => esc_html__( 'Add New Slider', 'monospace-slides' ),
			'new_item_name'     => esc_html__( 'New Slider Name', 'monospace-slides' ),
			'menu_name'         => esc_html__( 'Sliders', 'monospace-slides' ),
		);

		$args = array(
			'labels'             => $labels,
			'description'        => esc_html__( 'Group multiple slides into a slider', 'monospace-slides' ),
			'hierarchical'       => true,
			'public'             => true,
			'publicly_queryable' => false,
			'show_ui'            => true,
			'show_in_menu'       => true,
			'show_in_nav_menus'  => false,
			'show_in_rest'       => true,
			'show_tagcloud'      => false,
			'show_in_quick_edit' => true,
			'show_admin_column'  => true,
		);

		$args = apply_filters( 'monospace_slides_taxonomy_options', $args );

		register_taxonomy( $tax_name, array( 'mnsp_slide' ), $args );

	}

}

Looking at the code you’ll notice a method declaration, new_tax_slider() that registers the new custom taxonomy.

Just like in the previous post, the method uses two array variables to define all the necessary stuff for the new taxonomy. At the end it uses the buil-in register_taxonomy() function to tell WordPress about the taxonomy.

Next, we have to tell WordPress about the new file we created as it doesn’t know it exists yet.

So, open the /includes/class-monospace-slides.php file and in the load_dependencies() method add the following code:

/**
 * The class responsible for the functionality of the custom taxonomy.
 */
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-monospace-slides-tax.php';

This tells WordPress to load that file as we’ll use functionality from there.

Next, we’ll create a function in this same file that will contain all the hooks for the the new custom content type.

In the __construct method of this file add one line of code add the end:

$this->define_tax_hooks();

This tells WordPress to look for a method called define_cpt_hooks() in this file and execute that code.

For the new custom taxonomy to actually get registered we have to tell WordPress to run the new_tax_slider() method we created earlier in the Monospace_Slides_TAX class.

So, in this same file let’s create the define_tax_hooks() method we declared above. Add the following code after the define_cpt_hooks() method:

/**
 * Register all of the hooks related to registering a custom taxonomy.
 *
 * @since    1.0.0
 * @access   private
 */
private function define_tax_hooks() {

	$plugin_tax = new Monospace_Slides_TAX();

	$this->loader->add_action( 'init', $plugin_tax, 'new_tax_slider' );

}

The code above tells WordPress to look or a function called new_tax_slider inside the Monospace_Slides_TAX class and run it at the init moment in it’s initialization process.

Now, if we add the new code to our installed plugin and we pop in on the admin side we can see a new menu item for our custom content type, just like in the screenshot below:

Also each slide post will have a new meta box that will allow us to assign a slide to a specific slider.

So, there you have it. An easy way to group multiple slides into a slider.

Next, we’ll declare the new taxonomy in the /includes/class-monospace-slides-activator.php file and flush the rewrite rules afterwards.

So, open that file and in the activate() method, after the Monospace_Slides_CPT::new_cpt_slide() method call, add the following code:

Monospace_Slides_TAX::new_tax_slider();

That’s it, now we have a complete custom taxonomy functionality in our plugin.

At The End

We’ve gone through another post in which we covered quite a lot of ground. We’ve learned how to classify the posts from our newly created content type, the mnsp_slide, into sliders with the help of taxonomies.

Using the mnsp_slider taxonomy we’ll later be able to display multiple slides into an animated slideshow and by using different taxonomy terms we can have multiple slideshows with different sides in them.

Until the next post in this series please share your feedback or thoughts on what you’d like to see covered in more depth or things I may have missed.

Create An Image Slide Plugin Part 3: Create A Custom Post Type

In this post from the series of tutorials on creating a WordPress plugin for displaying image slides we’ll learn how to create a custom post type which will house the functionality, content and metadata information of our image slides.

In the previous post we set things up using the WordPress Plugin Boilerplate which gave us a solid start with a lot of best practice and standards compliant code already available to us. We also looked at some of the important files and folders that make up our plugin.

In this post we’ll start creating the image slide using a piece of functionality that WordPress offers, called custom post types, often referred to as custom content types. Before we get to that let’s dig into more details about this built-in WordPress functionality.

What Is A Custom Post Type?

In WordPress, a ‘post’ can mean two things:

  1. an entry or piece of content that a user creates or WordPress generates and that has a post / content type. A blog post or page are some common entries that users create
  2. a piece of content that has a type of 'post'

WordPress has several post or content types readily available to be used for your content. These are often called the default post types:

  • Post (Post Type: 'post')
  • Page (Post Type: 'page')
  • Attachment (Post Type: 'attachment')
  • Revision (Post Type: 'revision')
  • Navigation Menu (Post Type: 'nav_menu_item')
  • Custom CSS (Post Type: 'custom_css')
  • Changesets (Post Type: 'customize_changeset')

As the name suggests, a custom content type is a post type with content and functionality defined by users.

With custom content types we have much more control over the information that is provided and how it is displayed. This functionality is of huge importance to the WordPress core as well as themes and plugins.

Custom Content Type Prerequisites

The action of creating a custom content type is called ‘registering’. The registering consists of some information we must provide in order for WordPress to know and handle the content type.

Looking through the official documentation on registering a custom post type, we can create a list of things to do in order to create our custom content type:

  • We need to use the register_post_type() built-in function to tell WordPress about the new content type.
  • We need to use the init hook as the the moment in the WordPress loading sequence that the new content type should become available.
  • We need to come up with a name for our custom content type.

Aside from what we got from looking through the documentation we also need to establish a place in our plugin’s files where we’ll put the actual code for creating the content type.

We’ll start by creating a new class file that will house the bulk of our custom post type functionality.

So, create a new file called /admin/class-monospace-slides-cpt.php. As you can see it resides in the /admin folder.

The most important piece of information about a custom content is its ‘type’ or the ‘slug’ that the content goes by internally in WordPress core, themes and plugins.

The type of our custom content will be mnsp_slide.

Register A Custom Content Type

If you want to follow along I’ve set up a GitHub repository where you’ll find the code from this series. The finished code for this post is in the part_3 branch. If you want to follow along please download the part_2 branch.

Now we have some solid knowledge about custom content types. Let’s get on with actually creating our slide custom content type in the admin of WordPress. As we’ve established before, this means registering the content type.

Let’s do this in stages.

Open the newly created file and add the following code:

<?php
/**
 * The admin-specific functionality for creating a custom post type.
 *
 * @link       https://www.mikeinmonospace.com/
 * @since      1.0.0
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/admin
 */

/**
 * The admin-specific functionality for creating a custom post type.
 *
 * Defines all the functionality related to creating and managing a custom post type.
 *
 * @package    Monospace_Slides
 * @subpackage Monospace_Slides/admin
 * @author     Mike In Monospace <mikeinmonospace@gmail.com>
 */
class Monospace_Slides_CPT {

	/**
	 * Creates a new custom post type
	 *
	 * @since 1.0.0
	 * @access public
	 * @uses register_post_type()
	 */
	public static function new_cpt_slide() {

		$cap_type = 'post';
		$cpt_name = 'mnsp_slide';

		$labels = array(
			'name'                  => esc_html_x( 'Slides', 'Post Type General Name', 'monospace-slides' ),
			'singular_name'         => esc_html_x( 'Slide', 'Post Type Singular Name', 'monospace-slides' ),
			'menu_name'             => esc_html__( 'Slides', 'monospace-slides' ),
			'name_admin_bar'        => esc_html__( 'Slide', 'monospace-slides' ),
			'archives'              => esc_html__( 'Slide Archives', 'monospace-slides' ),
			'attributes'            => esc_html__( 'Slide Attributes', 'monospace-slides' ),
			'parent_item_colon'     => esc_html__( 'Parent Slide:', 'monospace-slides' ),
			'all_items'             => esc_html__( 'All Slides', 'monospace-slides' ),
			'add_new_item'          => esc_html__( 'Add New Slide', 'monospace-slides' ),
			'add_new'               => esc_html__( 'Add New', 'monospace-slides' ),
			'new_item'              => esc_html__( 'New Slide', 'monospace-slides' ),
			'edit_item'             => esc_html__( 'Edit Slide', 'monospace-slides' ),
			'update_item'           => esc_html__( 'Update Slide', 'monospace-slides' ),
			'view_item'             => esc_html__( 'View Slide', 'monospace-slides' ),
			'view_items'            => esc_html__( 'View Slides', 'monospace-slides' ),
			'search_items'          => esc_html__( 'Search Slide', 'monospace-slides' ),
			'not_found'             => esc_html__( 'Not found', 'monospace-slides' ),
			'not_found_in_trash'    => esc_html__( 'Not found in Trash', 'monospace-slides' ),
			'featured_image'        => esc_html__( 'Featured Image', 'monospace-slides' ),
			'set_featured_image'    => esc_html__( 'Set featured image', 'monospace-slides' ),
			'remove_featured_image' => esc_html__( 'Remove featured image', 'monospace-slides' ),
			'use_featured_image'    => esc_html__( 'Use as featured image', 'monospace-slides' ),
			'insert_into_item'      => esc_html__( 'Insert into Slide', 'monospace-slides' ),
			'uploaded_to_this_item' => esc_html__( 'Uploaded to this Slide', 'monospace-slides' ),
			'items_list'            => esc_html__( 'Slides list', 'monospace-slides' ),
			'items_list_navigation' => esc_html__( 'Slides list navigation', 'monospace-slides' ),
			'filter_items_list'     => esc_html__( 'Filter Slides list', 'monospace-slides' ),
		);

		$args = array(
			'label'               => esc_html__( 'Slide', 'monospace-slides' ),
			'description'         => esc_html__( 'A content type for creating image slides', 'monospace-slides' ),
			'labels'              => $labels,
			'menu_icon'           => 'dashicons-welcome-view-site',
			'supports'            => array( 'title', 'thumbnail', 'revisions' ),
			'taxonomies'          => array( 'mnsp_slider' ),
			'public'              => true,
			'show_ui'             => true,
			'show_in_menu'        => true,
			'menu_position'       => 5,
			'show_in_admin_bar'   => true,
			'show_in_nav_menus'   => false,
			'can_export'          => true,
			'has_archive'         => false,
			'hierarchical'        => false,
			'exclude_from_search' => true,
			'show_in_rest'        => true,
			'publicly_queryable'  => false,
			'capability_type'     => $cap_type,
		);

		$args = apply_filters( 'monospace_slides_cpt_options', $args );

		register_post_type( strtolower( $cpt_name ), $args );

	}

}

As we can see this file declares a class with a method in it, new_cpt_slide() that registers a new custom content type.

Taking a closer look at the method, we can see it declares two array variables containing a set of labels and some arguments. The last line of the method uses the register_post_type() built-in function to tell WordPress about the new content type.

Next, we have to tell WordPress about the new file we created as it doesn’t know it exists yet.

So, open the /includes/class-monospace-slides.php file and in the load_dependencies() method add the following code:

/**
 * The class responsible for the functionality of the custom post type.
 */
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-monospace-slides-cpt.php';

This tells WordPress to load that file as we’ll use functionality from there.

Next, we’ll create a function in this same file that will contain all the hooks for the the new custom content type.

In the __construct method of this file add one line of code add the end:

$this->define_cpt_hooks();

This tells WordPress to look for a method called define_cpt_hooks() in this file and execute that code.

Add a custom function on the init hook

For the new custom post type to actually get registered we have to tell WordPress to run the new_cpt_slide() method we created earlier in the Monospace_Slides_CPT class.

So, in this same file let’s create the define_cpt_hooks() method we declared above. Add the following code after the define_public_hooks() method:

/**
 * Register all of the hooks related to registering a custom post type
 * as well as customizing the admin columns.
  *
 * @since    1.0.0
 * @access   private
 */
private function define_cpt_hooks() {

	$plugin_cpt = new Monospace_Slides_CPT();

	$this->loader->add_action( 'init', $plugin_cpt, 'new_cpt_slide' );

}

The code above tells WordPress to look or a function called new_cpt_slide inside the Monospace_Slides_CPT class and run it at the init moment in it’s initialization process.

Now, if we add the new code to our installed plugin and we pop in on the admin side we can see a new menu item for our custom content type, just like in the screenshot below:

Slide Custom Post Type Admin Menu

So, there you have it. An easy way to add our image slides in WordPress.

Next, let’s make sure that WordPress will correctly handle the permalinks and URLs for the posts that will have the new content type. This is done by flushing the rewite rules.

So, open the /includes/class-monospace-slides-activator.php file and in the activate() method add the following code:

/**
 * The class responsible for defining the behavior of the custom post type.
 */
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-monospace-slides-cpt.php';

Monospace_Slides_CPT::new_cpt_slide();

flush_rewrite_rules();

Customize the WordPress Admin Columns

We’ll now add a column to display the slide’s featured image on the Slides > All Slides admin screen.

Open the /admin/class-monospace-slides-cpt.php file and add these two functions at the end of the class:

/**
 * Creates a new admin column on the custom post type listing screen.
 *
 * @since 1.0.0
 * @param string $columns    The name of the column to add.
 * @access public
 */
public function add_slide_columns( $columns ) {

	$columns['image'] = __( 'Image', 'monospace-slides' );

	return $columns;

}

/**
 * Displays the featured image of a post in an admin column called 'image'.
 *
 * @since 1.0.0
 * @param string $column     The name of the column.
 * @param int    $post_id    The id of the post.
 * @access public
 */
public function add_slide_columns_content( $column, $post_id ) {

	if ( 'image' === $column ) {
		echo get_the_post_thumbnail( $post_id, array( 80, 80 ) );
	}

}

Now we’ll add two filters that will use these two functions to add and display the image column. In the /includes/class-monospace-slides.php file add the following code at the end of the define_cpt_hooks() method:

$this->loader->add_filter( 'manage_edit-mnsp_slide_columns', $plugin_cpt, 'add_slide_columns' );
$this->loader->add_filter( 'manage_mnsp_slide_posts_custom_column', $plugin_cpt, 'add_slide_columns_content', 10, 2 );

There, now, whenever you go to see all your slides it will be easier to identify them as each one will show it’s featured image.

Slide Custom Post Type Admin Columns

At The End

This is the first post in which we added some custom code for creating content that WordPress does not support by default.

Let’s recap some key points from this post:

  • We can add custom content by creating a custom post / content type.
  • To add a custom content type we need to tell WordPress about it.
  • We let WordPress know about the new content type by registering it using a custom function and a hook that runs that function at the init moment in the WordPress loading sequence.

Check back for the next post in this series where we’ll create a taxonomy, which is a way of grouping things together.

In the mean time please feel free to comment with your thoughts and opinions on how this content can be improved or extended. All feedback is welcomed 🙂

Create An Image Slide Plugin Part 2: Setup

In the second post post from the series of tutorials on creating a WordPress plugin for displaying image slides we’ll learn how to setup a WordPress plugin that is more complex and using best practice techniques and code.

In the previous post of the series we’ve briefly introduced the basic concept of creating WordPress plugins. We’ve also created an example plugin to kick things off and see just how simple it can be to get going.

Although the example plugin was working fine, it was a very basic plugin that didn’t do much. Most WordPress plugins are more complex than that and that’s why they need a certain organisational feature that would make the code more extensible in the future should we and want to expand on it.

Starting with this post we’ll get more organised by using a piece of software called The WordPress Plugin Boilerplate that will help us keep our code nice and tidy.

In this part we’ll explore the benefits of using this boilerplate code and we’ll get into the structure that it offers us by looking at all the files and folders where we can place our code.

The WordPress Plugin Boilerplate

So, what is a boilerplate?

In computer programming, boilerplate or boilerplate code refers to repetitive pieces of code that are included in software projects with little or no change. It’s reusable code.

What is The WordPress Plugin Boilerplate?

The simple answer: it is a plugin, that, as is, does nothing.

According to the official website it is a standardized, organized, object-oriented foundation for building high-quality WordPress Plugins. As its name suggests it is made up of boilerplate code that is meant to help programmers write plugins in a way that is common among developers.

Create An Image Slide Plugin

Here is an overview of the functionality of our plugin:

  • The plugin will have a custom post type called mnsp_slide. This will be like a normal blog post only we’ll be able to pick and choose what goes inside it.
  • We’ll be able to group several slides into a  mnsp_slider with the help of a taxonomy.
  • Each slide will have some metadata information that the user will be able to fill in to alter the look and functionality of each slide. The metadata information for each slide will consist of:
    • Title
    • Subtitle
    • Description
    • Call To Action
    • Image
    • Aspect ratio
    • Alignment
    • Background color
    • Featured image opacity
  • The slider taxonomy will also have some metadata information which will be used to alter the behavior of the the sliders we create. The metadata for each slider will consist of:
    • Transition type
    • Transition duration
    • Timeout between slides
    • Show navigation
    • Pause slider on hover over slide
    • Autoplay
    • Aspect ratio
  • The plugin will offer two shortcodes for displaying the slides and sliders inside posts and pages.

As we can see in the list above, we’ll be creating a “mnsp_slide” custom post type and a “mnsp_slider” taxonomy.

Each slide will have additional metadata information used to describe it further and add additional functionality. The slider taxonomy will be used to gather multiple slides into sliders.

Now that we know what the key pieces functionality are, let’s get on with creating the plugin. We’ll start with analyzing how the code is organized.

The File And Folder Structure Of A WordPress Plugin

If you want to follow along I’ve set up a GitHub repository where you’ll find the code from this series. The finished code for this post is in the part_2 branch.

The main plugin folder has the following structure:

Folder structure for the Monospace Slides WordPress Plugin by mikeinmonospace.com

As we can see in the screenshot above, our plugin contains two folders for admin and public side functionality as well as some other folders and files which we’ll get into later on.

Here is a breakdown of the folders and their contents:

  1. /admin: as the name suggests, inside this folder lives all the functionality that we’ll use on the admin side. This folder breaks down even more into additional folders to make the code as streamlined as possible.
  2. /includes: this folder contains files related to the main functions that the plugin has as well as common code for the admin and public sides.
  3. /languages: as the name implies, this folder contains files related to the internationalization of the plugin.
  4. /public: similar to the admin folder, here lives all the functionality that will be used on the public side. It also breaks down into additional folders.

Now let’s take a look at some of the files that reside in the different folders we’ve seen above:

1. /README.md: this file contains information that people can read for a quick overview of the plugin. You can find more info about creating such a file, here.

2. /monospace-slides.php: this is the main file of the plugin. It contains the plugin header, as well as the code for initializing and running the plugin. It registers hooks for activating and deactivating the plugin and loads the main plugin class.

<?php

/**
 * The plugin bootstrap file
 *
 * This file is read by WordPress to generate the plugin information in the plugin
 * admin area. This file also includes all of the dependencies used by the plugin,
 * registers the activation and deactivation functions, and defines a function
 * that starts the plugin.
 *
 * @link              https://www.mikeinmonospace.com/
 * @since             1.0.0
 * @package           Monospace_Slides
 *
 * @wordpress-plugin
 * Plugin Name:       Monospace Slides
 * Plugin URI:        https://github.com/mikeinmonospace/Monospace-Slides
 * Description:       A WordPress plugin for displaying image slides.
 * Version:           1.0.0
 * Author:            Mike In Monospace
 * Author URI:        https://www.mikeinmonospace.com/
 * License:           GPL-2.0+
 * License URI:       http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain:       monospace-slides
 * Domain Path:       /languages
 */
 
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
 die;
}
 
/**
 * Currently plugin version.
 * Start at version 1.0.0 and use SemVer - https://semver.org
 * Rename this for your plugin and update it as you release new versions.
 */
define( 'MONOSPACE_SLIDES_VERSION', '1.0.0' );
 
/**
 * The code that runs during plugin activation.
 * This action is documented in includes/class-monospace-slides-activator.php
 */
function activate_monospace_slides() {
 require_once plugin_dir_path( __FILE__ ) . 'includes/class-monospace-slides-activator.php';
 Monospace_Slides_Activator::activate();
}
 
/**
 * The code that runs during plugin deactivation.
 * This action is documented in includes/class-monospace-slides-deactivator.php
 */
function deactivate_monospace_slides() {
 require_once plugin_dir_path( __FILE__ ) . 'includes/class-monospace-slides-deactivator.php';
 Monospace_Slides_Deactivator::deactivate();
}
 
register_activation_hook( __FILE__, 'activate_monospace_slides' );
register_deactivation_hook( __FILE__, 'deactivate_monospace_slides' );
 
/**
 * The core plugin class that is used to define internationalization,
 * admin-specific hooks, and public-facing site hooks.
 */
require plugin_dir_path( __FILE__ ) . 'includes/class-monospace-slides.php';
 
/**
 * Begins execution of the plugin.
 *
 * Since everything within the plugin is registered via hooks,
 * then kicking off the plugin from this point in the file does
 * not affect the page life cycle.
 *
 * @since    1.0.0
 */
function run_monospace_slides() {
 
 $plugin = new Monospace_Slides();
 $plugin->run();
 
}
run_monospace_slides();  

3. /uninstall.php: the functionality in this file is called when the plugin is uninstalled.

Looking at the code in the /monospace-slides.php file above, we can see that it branches out in the /includes folder. Here is what each of the files, in that folder, will be used for:

1. /class-monospace-slides-activator.php: this file contains functionality used during the activation process of our plugin.

2. /class-monospace-slides-deactivator.php: here we’ll put the code used during the deactivation process of the plugin.

3. /class-monospace-slides-i18n.php: this file deals with translation functionality.

4. /class-monospace-slides-loader.php: this is a key file responsible for registering with WordPress all the actions and filters of the plugin.

5. /class-monospace-slides.php: this file contains most of the action. The class contains two important methods for adding front and back end hooks.

/**
 * Register all of the hooks related to the admin area functionality
 * of the plugin.
 *
 * @since    1.0.0
 * @access   private
 */
private function define_admin_hooks() {
 
 $plugin_admin = new Monospace_Slides_Admin( $this->get_plugin_name(), $this->get_version() );
 
 $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
 $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
 
}
 
/**
 * Register all of the hooks related to the public-facing functionality
 * of the plugin.
 *
 * @since    1.0.0
 * @access   private
 */
private function define_public_hooks() {
 
 $plugin_public = new Monospace_Slides_Public( $this->get_plugin_name(), $this->get_version() );
 
 $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_styles' );
 $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_scripts' );
 
}  

In a future post we’ll be registering our own admin and public hooks inside these methods. We’ll be adding the functionality for these registered hooks inside the admin and public folders.

At the end

We’ve covered quite a bit of ground here and we’re off with a solid start.

Now, we have a solid foundation upon which we’ll build in the upcoming posts of this tutorial series. Our plugin follows best practice guidelines and has a clear organization of it’s files and folders. It is very clear where we’ll put the code related to any functionality we’ll want to build.

In the next part we’ll begin writing code for the custom functionality we’ve outlined earlier.

In the mean time please feel free to comment with your progress, things you want to see covered in more depth as well as any other suggestions you may have.

Create An Image Slide Plugin Part 1: Intro

This is the first post from a series of tutorials in which we’ll create a WordPress plugin for displaying image slides. This first part is an introduction to creating WordPress plugins.

Plugins, alongside themes, have been a driving force for the huge success of  WordPress’ which now powers some 30% of the websites on the internet.

I first started working with WordPress by modifying themes to make them look the way I wanted. Later, when I needed to introduce new functionality in a website I started to tackle the inner workings of plugins.

Off the bat, I can tell you it was a bit daunting at the beginning but eventually I got the hang of it. If you want to learn how to create WordPress plugins you just have to stick with the process.

Putting in the time, that’s the key to success 🙂

You can add new functions to WordPress either by using a theme or a plugin. One of the reasons to create a plugin for the functionality you need is the separation of concerns. This principle means that themes should be used for styling purposes while functionality should be added with plugins.

Using a plugin keeps our functionality available no matter what theme we use and we can even make it available on another website that needs the same functionality.

Introduction To Creating WordPress Plugins

The internet is abounding with useful resources for learning how to create WordPress plugins. The thing I wish someone had told me when I started my learning process was to begin with the official docs: The WordPress Plugin Handbook.

According to the handbook, plugins are packages of code that extend the core functionality of WordPress. It provides a great starting point with respect to best practices but it doesn’t enforce any standards, that’s why, even today, creating plugins brings with it a sense of uncertainty.

When I first started to create WordPress plugins I was copy-pasting code from the Codex or from other developers’ blogs. They were one-file plugins that worked as expected but that lacked a certain quality of the code I was writing, especially if the file grew in the number of lines of code.

If that’s what you’re doing it’s OK, for now 🙂

Creating A Basic Plugin

From here on you’ll need a working WordPress installation to follow along.

I’m assuming that you’re comfortable working with the WordPress environment for such things as selecting and installing WordPress plugins and themes. Also, understanding of PHP programming language will make it easier.

So, let’s get to it. Let’s create a basic WordPress plugin.

In WordPress, the normal plugins have their place in the /wp-content/plugins folder.

Go into this location and create a new folder for our test plugin. Let’s call it /testing123. Inside this new folder create a file and name it testing123.php. Alternatively, you can create the folder and file structure anywhere, on your computer, archive it as a .zip file and install it through the WordPress admin.

You can read more about getting started with creating a plugin, here.

In order for WordPress to see this new file as a plugin we’ll need to add a plugin header comment. This is a piece of metadata written as a PHP comment that contains various information about our plugin.

The header must contain at least the plugin name, like so:

<?php
/*
Plugin Name: Testing 123
*/

Only one file in the plugin’s folder should contain this header comment.

With the code above in the newly created testing123.php file, our testing plugin is ready to be displayed in the WordPress admin. Even though this is a working plugin file there’s not much here to give it a sense of identity 🙂

Let’s add some more info to the plugin header so that we can get to know it better. A complete plugin header might look like this:

<?php
/*
Plugin Name:  Testing 123
Plugin URI:   https://www.mikeinmonospace.com
Description:  A basic plugin for testing purposes
Version:      1.0
Author:       Mike In Monospace
Author URI:   https://www.mikeinmonospace.com
License:      GPL2
License URI:  https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:  monospace-test
Domain Path:  /languages
*/

You can find out more about the header requirements in the plugin handbook.

Now that we’ve added a bit more information about our basic plugin, let’s activate it if you haven’t already. Upon activation, your screen should look the image below. You can see an entry for our plugin with all the information from the header comment we created above.

Create An Image Slide Plugin Part 1- basic plugin activation

Now let’s add some functionality to our plugin so that it actually does something. Further down in the testing123.php file, after the plugin header comment, add the following code:

function mnsp_testing_content($content) { 
 return '<p>Testing 123.</p>' . $content;
}
 
add_filter( 'the_content', 'mnsp_testing_content' );

In the code above we’re creating a function which prepends a testing phrase to the content of every page we’ll be viewing. In order to tell WordPress to use this function when it displays content we’re using a filter, which is a built in WordPress function used to pass data through. Filters allow us to modify the default behavior of specific WordPress functions, in our case the_content.

Now, if you visit your website you’ll see the text “Testing 123.” on every page, like in the image below which is actually taken from the contact page of my website while I was testing this plugin.

Create An Image Slide Plugin Part 1 plugin result

So, here it is, your introduction to creating WordPress plugins. You now have created a plugin with some basic functionality. See below for the complete contents of the testing123.php file.

<?php
/*
Plugin Name:  Testing 123
Plugin URI:   https://www.mikeinmonospace.com
Description:  A basic plugin for testing purposes
Version:      1.0
Author:       Mike In Monospace
Author URI:   https://www.mikeinmonospace.com
License:      GPL2
License URI:  https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:  monospace-test
Domain Path:  /languages
*/

function mnsp_testing_content($content) { 
 return '<p>Testing 123.</p>' . $content;
}
 
add_filter( 'the_content', 'mnsp_testing_content' );

WordPress Image Plugins

In this tutorial series we’ll work with image slides.

We can think of these slides as more than just simple images. we can see a slide as a media object that can contain images, titles and subtitles, descriptions and call-to-action instructions.

The landscape of WordPress images and slides plugins is very “colorful”, so to speak. There are a dime a dozen of these plugins, each more appropriate than the next, depending on what you need.

For creating our image slide plugin we’ll be using the WordPress Plugin Boilerplate. It is a standardized way of writing plugins that provides an object oriented foundation and a high quality start for extending WordPress with new functionality.

Depending on what you need to do, the boilerplate can be appropriate for more complex functionality while being an overkill for things that require only a few lines of code. We’re using it here because our plugin will introduce a lot of functionality and we need a way to keep the code from becoming overwhelming as well as keeping up with best practices.

By the end of this series we’ll be able to create the media object described above, display it wherever we want in a WordPress site as well as display multiple slides in a slider. Ultimately, we’ll be able to use this functionality on any number of sites because we’ll have created a plugin.

At The End

Stay tuned for the next post in this series in which we’ll set up and discuss the structure of our image slides plugin.

Please feel free to share your thoughts and feedback, things we might have missed or plugin basics topics you’d like to be covered in more depth.

WordPress Internals: Loading Sequence

Whenever I start learning something new I prefer to have a deep understanding of the process that goes on behind the scene of whatever I’m trying to learn.

From experience, I can tell you that this can be both good and bad, because you can often get stuck and feel like you’re never gonna get it. Ultimately, a deeper understanding is a good thing. It just takes a few tries before you get there 🙂

When I first made contact with WordPress, I was modifying existing themes or doing front-end customization through plugins.

The more I worked with WordPress and the more of an advanced user I became, the more I felt like I was missing something. That something was the deeper understanding of the process that WordPress goes through to show content to the end user.

So, here it is.

The WordPress Loading Sequence

These are the steps that WordPress goes through from when you type an URL into the browser and until it presents you with whatever content you requested. Unfortunately, as it goes through these steps for every single page view, sometimes it can be a little slow.

First, we’ll take a look at a top level view of the process, after that we’ll get into an extended view and we’ll finish off with an almost complete loading sequence. At the end you’ll also find a chart of the entire process.

WordPress Loading Sequence: Basic

  1. Create base configuration for WordPress
  2. Set up version variables
  3. Set initial default constants
  4. Check if maintenance mode is enabled
  5. Check if debug mode is enabled
  6. Load early functionality files
  7. Load the database class file and instantiate the global variable
  8. Set up the default filters and actions for most of the WordPress hooks
  9. Initialize multi-site if enabled
  10. Load the localization library
  11. Load most of the remaining WordPress functionality
  12. Load must-use plugins
  13. Load multi-site plugins
  14. Create initial taxonomies and post types
  15. Load normal plugins
  16. Load pluggable functions
  17. Instantiate global query, rewrite and widget objects
  18. Load the active theme
  19. Set up the current user
  20. Build the query to get the requested data
  21. Select the correct template from the hierarchy
  22. The result of the request is sent to the browser

WordPress Loading Sequence: Extended

As with most PHP applications, the loading sequence starts with a index.php file and so is the case with WordPress.

  1. It all starts in the WordPress installation root folder with /index.php
  2. Set up MySQL settings
  3. Set up authentication data
  4. Set up database table prefix
  5. Set up debugging mode
  6. Load functions needed to load WordPress
  7. Load constants and global variables file that can be overridden, generally in /wp-config.php which was loaded earlier in /index.php
  8. Load the Plugin API
  9. Set up version variables
  10. Set up initial default constants
  11. Check if maintenance mode is enabled
  12. Check if debug mode is enabled
  13. Set up advanced caching functionality
  14. Load early core utility and class files
  15. Load the database class file and instantiate the $wpdb global variable
  16. Connect to MySQL and select database
  17. Start the object cache
  18. Set up the default filters and actions for most of the WordPress hooks
  19. Initialize multi-site if enabled
  20. Check if only the basics are needed and stop most of WordPress from being loaded
  21. Load the localization library
  22. Do a final check to see if the site needs to be installed
  23. Load most of the remaining WordPress functionality
  24. Load multi-site specific files
  25. Load must-use plugins
  26. Load network activated plugins
  27. do_action( 'muplugins_loaded' )
  28. Create initial taxonomies and post types
  29. Set up the default theme directory root
  30. Load normal plugins
  31. Load pluggable functions which you can easily overwrite from your themes or plugins
  32. do_action( 'plugins_loaded' )
  33. Instantiate global objects WP_Query, WP_Rewrite, WP and WP_Widget_Factory
  34. do_action( 'setup_theme' ) fires before the active theme is loaded
  35. Load the default text localization domain
  36. Load the functions.php file for the active theme
  37. do_action( 'after_setup_theme' ) fires after the theme is loaded
  38. Set up the current user
  39. do_action( 'init' ). Many themes and plugins use this hook as it is safe for running code
  40. do_action( 'widgets_init' )
  41. do_action( 'wp_loaded' )
  42. Run wp() to get the requested data
  43. do_action( 'template_redirect' ) fires before determining which template to load
  44. Select the correct template from the hierarchy
  45. apply_filters( 'template_include' )
  46. do_action( 'shutdown' )
  47. The result of the request is sent to the browser

WordPress Loading Sequence: Detailed

Whenever you type an address in the browser bar you make a request to a web hosting server. From there, the loading sequence of WordPress starts with the /index.php file in the root installation folder. Here is a detailed account of what is going on in the WordPress boot process.

1. A request is made

When you type in an address, the HTTP client, your browser, sends an HTTP request to a web server in the form of a request message.

2. /index.php

WordPress starts the loading sequence with this file. Two things are happening here:

  1. WP_USE_THEMES is defined which tells WordPress to load the active theme. If you are do not need to display the content stored in WordPress you can set this constant to false. Additionally you can display the WordPress content on a page from an existing website. To do this, use the following code on the page where you want to integrate WordPress:
    <?php define('WP_USE_THEMES'false); require('./wp-blog-header.php'); ?>
  2. Require /wp-blog-header.php which loads the WordPress environment

If you are requesting an admin page the process starts with the /wp-admin/index.php file.

3. /wp-blog-header.php

There are three things happening here:

  1. Checks if initial HTTP headers have been sent via the $wp_did_header variable. This is done so that the functionality in this file is executed only once
  2. Require /wp-load.php which sets up the entire WordPress environment
  3. Execute wp() function from /wp-includes/functions.php file, which sets up and runs the WordPress query to get the requested data
  4. Require /wp-includes/template-loader.php which loads the correct template from the hierarchy, based on the requested URL

4. /wp-load.php

This is a bootstrap file which contains two main pieces of functionality:

  1. Define the ABSPATH constant as this file’s directory. This is the root directory of the WordPress installation
  2. Require /wp-config.php which contains the base configuration for the WordPress environment. If it doesn’t find such a file, the WordPress installation process will start.

5. /wp-config.php

This is what happens in this file:

  1. Set up MySQL settings
  2. Set up authentication data. This is done though a few constants which you can change at any point in time to invalidate all existing cookies. This will force all users to have to log in again
  3. Set up database $table_prefix
  4. Set up WordPress debugging mode by defining the WP_DEBUG constant. Defaults to false. You can set this to true to show PHP errors
  5. Require /wp-settings.php which sets up WordPress variables and includes other files

At this point, WordPress has all the information it needs to securely access the database and load the requested data.

6. /wp-settings.php

This is the most important file in the loading sequence as it sets up most of the WordPress functionality and includes other needed files. Here is what’s going on and the most important steps:

  1. Define the WP_INC constant which stores the location of the WordPress directory of functions, classes and core content. This points to /wp-includes in the root folder
  2. Require /wp-includes/load.php which contains functions used for further initialization and loading WordPress. The functions in this file will be used further down.
  3. Require /wp-includes/default-constants.php which defines constants and global vars that can be overridden in /wp-config.php
  4. Require /wp-includes/plugin.php which loads the Plugin API. The functionality from installed plugins is not yet available.
  5. Set up version variables: $wp_version, $wp_db_version, $tinymce_version, $required_php_version, $required_mysql_version, $wp_local_package
  6. Set up $blog_id global variable which defaults to 1 in single site configuration. In multi-site, it will be overridden by default in /wp-includes/ms-settings.php
  7. Set initial default constants: WP_MEMORY_LIMIT, WP_MAX_MEMORY_LIMIT, WP_DEBUG, SCRIPT_DEBUG, WP_CONTENT_DIR and WP_CACHE
  8. Check if maintenance mode is enabled. This is true if there is a file .maintenance in the root folder or if the WordPress installation mode is active
  9. Check if WP_DEBUG mode is enabled
  10. Set up advanced caching functionality
  11. Define the WP_LANG_DIR constant which sets up the location of the language directory
  12. Require /wp-includes/compat.php file adds functions  missing from older PHP versions
  13. Require /wp-includes/wp-class-list-util.php file which contains a utility class to handle operations on an array of objects
  14. Require /wp-includes/functions.php file which contains the main WordPress API and the most important functionality to run a website
  15. Require /wp-includes/class-wp-matchesmapregex.php file which contains a helper class related to string operations
  16. Require /wp-includes/class-wp.php file which contains the object for building the WordPress query. This object is instantiated further down
  17. Require /wp-includes/class-wp-error.php file which contains a class for handling errors in WordPress
  18. Require /wp-includes/pomo/mo.php file which contains a class for working with translation files
  19. Set up $wpdb global variable and load the database class file and object cache. This functionality provides database connections and caching. It also allows the use of drop-ins which can be used to substitute the default classes and functionality here.
  20. Require /wp-includes/default-filters.php file which sets up default actions and filters for most of the WordPress hooks
  21. Initialize multi-site if enabled. The multi site functionality can be changed by loading a /wp-content/sunrise.php drop-in file
  22. End the loading of basic functionality
  23. Check if only basic WordPress functionality is needed by checking the SHORTINIT constant. This constant can be used in themes and plugins to only load the very basics of WordPress.
  24. Load the Translation API
  25. Do a final check to see if the site needs to be installed
  26. Load most of the remaining WordPress functionality by requiring some more files
  27. Require multi-site specific files
  28. Define plugin directory WordPress constant: WP_CONTENT_URL, WP_PLUGIN_DIR, WP_PLUGIN_URL, PLUGINDIR, WPMU_PLUGIN_DIR, WPMU_PLUGIN_URL, MUPLUGINDIR
  29. Load must-use plugins
  30. Load network activated plugins
  31. do_action( 'muplugins_loaded' ) This is the earliest hook you can use. There are no normal plugins loaded at this point
  32. Require /wp-includes/vars.php file which creates common global vars for the rest of WordPress
  33. Create initial post types and taxonomies
  34. Load normal plugins
  35. Require /wp-includes/pluggable.php which loads functions that can be replaced via plugins or themes
  36. do_action( 'plugins_loaded' )
  37. Define constants that affect functionality: AUTOSAVE_INTERVAL, EMPTY_TRASH_DAYS, WP_POST_REVISIONS, WP_CRON_LOCK_TIMEOUT
  38. Instantiate the global objects WP_Query, WP_Rewrite, WP and WP_Widget_Factory which were loaded earlier
  39. do_action( 'setup_theme' ) fires before the active theme is loaded
  40. Define the template related constants: TEMPLATEPATH, STYLESHEETPATH, WP_DEFAULT_THEME
  41. Load default translated strings based on locale
  42. Load the functions for the active theme, for both parent and child theme if applicable. This is done by checking if functions.php file exists in STYLESHEETPATH and TEMPLATEPATH which were defined earlier in the wp_templating_constants() function from /wp-includes/default-constants.php
  43. do_action( 'after_setup_theme' ) fires after the theme is loaded
  44. set up the current user
  45. do_action( 'init' ) fires after WordPress has finished loading but before any headers are sent. Most of WP is loaded at this stage, and the user is authenticated. This hook can be safely used for running code. Plugins use this hook to instantiate themselves for reasons like user, taxonomy and post type availability
  46. do_action( 'wp_loaded' ) fires once WP, all plugins, and the theme are fully loaded and instantiated.

This is the end of the /wp-settings.php file and we continue in the /wp-blog-header.php file.

7. wp()

This function is located in the /wp-includes/functions.php file which was loaded earlier in the /wp-settings.php file as part of the early WordPress files. This function sets up the WordPress query by checking the requested URL in order to get the requested data.

8. /wp-includes/template-loader.php

WordPress has arrived at the final part of its loading process. All of the WordPress functions are available through the /wp-settings.php file and the requested data is loaded by the wp() function. It’s time to load the correct template from the WordPress template hierarchy. This is what’s going on:

  1. do_action( 'template_redirect' ) fires before determining which template to load. This hook can be used to load an alternative template if the loaded data doesn’t need the default one
  2. Check if the current request is a header request and if so, exit before the page content loads. This provides a significant performance bump.
  3. Check for 3 special kinds of requests: robots, feeds, trackbacks. If these are detected, a template file does not need to be loaded
  4. Go through a list of checks and determine the template file to be loaded. If the file exists it is stored in the $template variable, else the default index template is stored
  5. Filter the $template variable before including the template file

9. do_action( 'shutdown' )

This is the last action that is being fired, just before ending all PHP execution. WordPress has run all the code needed to build the requested page and it stops working at this point.

10. The result of the request is sent to the browser

At this point, the web hosting server replies to the HTTP client request by sending it the web page generated by WordPress and comprised of HTML, CSS and JavaScript code which tells the browser how to display the page.

There it is. The WordPress boot process in three flavors: basic, extended and detailed. I’ve also made a chart of the whole process which you can see below.

Please comment with your thoughts, things I may have missed or steps that you think are important and I have overlooked.

We Have Liftoff

Hi there,

Thank you very much for visiting my website. This is my first post letting you know what to expect, should you decide to come back. Make sure you do come back as I will make it worth your while.

I created this blog in order to become better at what I do and also to give back to the community at large from which I learned everything I know.

I’ll be writing mostly about web development and user interface and user experience design.

My main focus will be tutorials on WordPress and adjacent topics like the LAMP stack, JavaScript, CSS and HTML.