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

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.