The WordPress Plugin Boilerplate Part 3: The Last Steps

Firdaus Zahari
Share

In the second part of this series, we prepared simple admin facing functionality for our plugin. We provided the users with an options page where they can tweak the plugin according to their preferences.

If you missed part 1 and 2, please find them below:

For this article, we’re going to implement the public facing functionality of the plugin. This means that we’re going to retrieve the user preferences, determine whether that specific post is outdated or not, and display the notice accordingly on its single post view. Let’s get started!

Preparing the Theme

We need to prepare our theme so that we can test it out later. We are going to use the Twenty Fifteen theme that is shipped by default with WordPress. Rather than populating the posts manually, we are going to use the theme unit test data provided by the Theme Review team to populate the posts.

We need to download the theme-unit-test-data.xml provided on the above Codex page and import it into our WordPress installation. The WordPress built-in import functionality can be found by navigating to Tools > Import from the sidebar.

Import

We will be presented with various choices, and since we are importing the WordPress export file, we are going to choose WordPress. Depending on the WordPress installation, a plugin popup will be displayed if we do not have the WordPress Importer plugin installed. Just go ahead and install the plugin first if you do not have it and we will proceed with the actual importing process once it is done.

Import WordPress

This is the default WordPress import page, so we will need to choose the theme-unit-test-data.xml downloaded previously and click on the “Upload file and import” button.

Import WordPress and Assign Authors

A second screen will then be presented. Don’t worry much about the “Assign Authors” section as we can leave it as default. In the “Import Attachments” section, make sure to tick the checkbox “Download and import file attachments” so that all the external attachments will be downloaded and imported into the media library.

Press the “Submit” button and wait until the importing process is done successfully.

Now we are ready to write the code that displays our notice.

Hooking into the_content Filter

Using the_content filter is the perfect way to alter the content of the posts displayed to the user. As per the previous tutorial, we are going to add the hook to the Boilerplate class loader, with the only difference being that the define_public_hooks method will be used instead of define_admin_hooks.

Add this line of code into the method:

$this->loader->add_action( 'the_content', $plugin_public, 'the_content' );

This means, we are going to need to implement a public the_content method into our Outdated_Notice_Public class. Open up public/class-outdated-notice-public.php and modify it accordingly.

Conditionally Modifying the Content

Because the the_content filter is used by various plugins, and the internal WordPress core, we need to be cautious in inserting our custom content into it.

We need to make sure that we are only inserting the outdated notice text whenever a single post is displayed, not on the blog post index. In order to do that, we can filter it by using the is_main_query function. This function is useful whenever we need to determine whether the running query is the main one or a secondary/custom query.

Another thing that we need to consider is that we are only going to display the notice text for a single view of post type post, so is_singular( 'post' ) validation will be perfect for the job.

This is the updated the_content method contained inside the Outdated_Notice_Public class.

public function the_content( $post_content ) {

		if ( is_main_query() && is_singular('post') ) {
			// do something
		}

        return $post_content;
	}

Calculating the Days Difference

Before we begin, let’s set our position for the notice to appear as “before” the post, and the number of days threshold to 30.

First, we need to retrieve the stored settings of our plugin. The get_option function will do the job, and as a safe measure, we are going to pass a default value as a second argument to get_option.

$position  = get_option( 'outdated_notice_position', 'before' );
	$days      = (int) get_option( 'outdated_notice_day', 0 );

To calculate the days difference between current time and the post last modified time, we are going to instantiate two DateTime objects. We can get an accurate measurement of time difference using the diff method that is built in to the DateTime class. Note that the DateTime class is only available on PHP >= 5.3.0.

$date_now  = new DateTime( current_time('mysql') );
	$date_old  = new DateTime( get_the_modified_time('Y-m-d H:i:s') );
	$date_diff = $date_old->diff( $date_now );

$date_diff is an instance of the DateInterval class so we are going to have a range of data available with regards to time differences. If we are to var_dump the $date_diff variable, this is what we should get, depending on the post that you are currently viewing.

object(DateInterval)#286 (8) {
	  ["y"]=>
	  int(0)
	  ["m"]=>
	  int(0)
	  ["d"]=>
	  int(12)
	  ["h"]=>
	  int(23)
	  ["i"]=>
	  int(17)
	  ["s"]=>
	  int(22)
	  ["invert"]=>
	  int(0)
	  ["days"]=>
	  int(12)
	}

We are only interested in the days value as it will be used as a comparison against our days threshold. As I said before, we are going to compare them so that we can apply the specific class to our notice div element to style it differently between posts.

if ( $date_diff->days > $days ) {
		$class = 'is-outdated';
	} else {
		$class = 'is-fresh';
	}

This simple comparison block is enough to determine which class to use for our div. The only thing left now is to actually construct the HTML to be added to the post content.

For better translation ready text, we are going to use the _n function to selectively choose the proper text to be used depending of the number of days since the post was last updated. Then, we are going to wrap the notice text in its own div so that we can style it later. Feel free to tweak the notice text to your own liking.

// Filter the text
	$notice = sprintf(
				_n(
					'This post was last updated %s day ago.',
					'This post was last updated %s days ago.',
					$date_diff->days,
					'outdated-notice'
				),
				$date_diff->days
			);

	// Add the class
	$notice = '<div class="outdated-notice %s">' . $notice . '</div>';
	$notice = sprintf( $notice, $class );

The $notice variable should now contain the markup that we are going to be inserting in to our post content. The only thing left to do is to add it in.

if ( 'after' == $position ) {
		$post_content .= $notice;
	} else {
		$post_content = $notice . $post_content;
	}

As a recap, this is how the the_content method inside Outdated_Notice_Public should finally look.

public function the_content( $post_content ) {

		if ( is_main_query() && is_singular('post') ) {
			$position  = get_option( 'outdated_notice_position', 'before' );
			$days      = (int) get_option( 'outdated_notice_day', 0 );
			$date_now  = new DateTime( current_time('mysql') );
			$date_old  = new DateTime( get_the_modified_time('Y-m-d H:i:s') );
			$date_diff = $date_old->diff( $date_now );

			if ( $date_diff->days > $days ) {
				$class = 'is-outdated';
			} else {
				$class = 'is-fresh';
			}

			// Filter the text
			$notice = sprintf(
						_n(
							'This post was last updated %s day ago.',
							'This post was last updated %s days ago.',
							$date_diff->days,
							'outdated-notice'
						),
						$date_diff->days
					);

			// Add the class
			$notice = '<div class="outdated-notice %s">' . $notice . '</div>';
			$notice = sprintf( $notice, $class );

			if ( 'after' == $position ) {
				$post_content .= $notice;
			} else {
				$post_content = $notice . $post_content;
			}
		}

        return $post_content;
	}

Try browsing to a few pages to see if our plugin is working as intended. This is what I’m seeing when browsing to the “Hello World” post on url <site-url>/2015/05/11/hello-world/.

Hello World

Styling the Notice Text

We are going to apply some simple styling to the .outdated-notice div that is going to match well with the overall theme design without being intrusive. In order to do that, we are not going to change anything with regards to the font size or font family that is already used. Plus, we are going to borrow the Bootstrap color scheme and styling for now.

Open up the outdated-notice-public.css file contained inside the public/css folder and add this rule.

.outdated-notice {
	    border: 1px solid transparent;
	    border-radius: 4px;
	    margin-bottom: 20px;
	    padding: 15px;
	}

We also need two different styles to represent the post-freshness, which is one style for .is-fresh and another one for .is-outdated. This snippet should do the trick, giving us a nice green color scheme for a fresh post, and red color scheme for an outdated post.

.outdated-notice.is-fresh {
	    background-color: #dff0d8;
	    border-color: #d6e9c6;
	    color: #3c763d;
	}
	
	.outdated-notice.is-outdated {
	    background-color: #f2dede;
	    border-color: #ebccd1;
	    color: #a94442;
	}

Let’s refresh the post view and see how it is styled. Examples of a fresh and an outdated post:

Fresh Post

Outdated Post

That is it! We now have our fully functional plugin developed based on the WordPress Plugin Boilerplate.

Improvements

There are various things that can be improved based on what we’ve done so far. Among them are:

  • Dynamic text for different contexts, where we can use different text for the outdated notice.
  • Remove the enqueuing of outdated-notice-public.js script since it is not used.
  • Dismissible notice so that it won’t interrupt the user experience when reading the post.

Conclusion

The WordPress Plugin Boilerplate provides a perfect starting point to develop any plugin from scratch. In this last part of the series, we finished up by developing the public facing functionality of our plugin. The modifications though quite simple, are done in little time, while still maintaining the OOP pattern established by the Boilerplate itself and adhering to standards and best practices.

A full copy of the code is available as usual at GitHub on the part-3 branch.