Code Your Own CommentLuv

Code your own Comment Love in WordPress. Let your commenters show off their latest blog post.

When I last redesigned my website I went through this weird phase where I wanted to consolidate all my website features. I wanted to stop using plugins and integrate everything into the one “Nose Graze plugin” I have that’s full of my site’s features.

I guess this was my way of limiting my JavaScript files and other assets, since everything would be combined on my end.

Maybe it was a silly idea, but as part of that process I coded my own version of the CommentLuv plugin for WordPress. I do like how mine is a bit slimmer and prettier, so I thought I’d share that with you today!

This plugin is super simple! Don’t expect bells and whistles.

Since my goal for this plugin was to strip everything down, there is no settings panel. I included only the features that I’m interested in. If you want more options and customization, CommentLuv is a better choice than what I’m going to show you today. But if you’re interested in coding something custom (and building off of my code) or you just want something incredibly simple, then by all means keep reading. πŸ˜‰ If you’re only interested in the final files, you can download them at the end.

This code is completely backwards compatible with CommentLuv in case you want to migrate over.

For my version, I just integrated the code into my existing site plugin, but for the sake of this tutorial I’ll be walking you through how to build a standalone plugin from scratch.

The theory behind our plugin.

In case you’re not familiar with CommentLuv, I’ll give you a quick run down of how our plugin will work:

  • Person fills out the form to submit a comment.
  • If they enter in a website URL, then we perform an ajax call to get some more data from that URL.
  • We use SimplePie to detect the RSS feed from that website URL and fetch a list of the blog posts.
  • We send the list back to the comment form, allowing a user to choose one of those blog posts to display with their comment.
  • When they click “Post Comment”, we send the chosen blog post URL and title back to WordPress and save that data with the comment.
  • When we display the comment, we fetch that data back again and display the blog post title with the URL below the comment text.
WordPress comment form with a list of your recent blog posts

Create your new plugin folder and file.

I named my plugin project ng-comment-love so that’s the name of my folder. Inside that folder, I created my ng-comment-love.php class.

At the top, I have the standard WordPress plugin header:

<?php
/*
 * Plugin Name: NG Comment Love
 * Plugin URI: https://www.nosegraze.com
 * Description: Fetches the commenter's most recent post to display below their comment
 * Version: 1.0.0
 * Author: Nose Graze
 * Author URI: https://www.nosegraze.com
 * License: GPL2
 * 
 * @package ng-comment-love
 * @copyright Copyright (c) 2015, Nose Graze Ltd.
 * @license GPL2+
*/

Add the JavaScript file to the front-end.

Still in the ng-comment-love.php file, we add this below the comment block:

/**
 * Adds JavaScript to the front-end.
 *
 * @since 1.0.0
 * @return void
 */
function ng_comment_love_scripts() {
	wp_enqueue_script( 'ng-comment-love', plugins_url( 'js/commentlove.js', __FILE__ ), array( 'jquery' ), '1.0.0', true );
	$data = array(
		'ajaxurl'  => admin_url( 'admin-ajax.php' ),
		'nonce'    => wp_create_nonce( 'ng_comment_love_ajax_nonce' ),
		'cl_fired' => false,
		'cl_url'   => ''
	);
	wp_localize_script( 'ng-comment-love', 'NGLOVE', $data );
}

add_action( 'wp_enqueue_scripts', 'ng_comment_love_scripts' );

This code adds our JavaScript file (not yet created) to the front-end of the site.

Before we move any further, let’s actually create that file.

As you can see in the code, my JavaScript path is js/commentlove.js. So based on that, create a new folder called js and inside that folder, create a new file called commentlove.js.

We’ll work with that JavaScript file a bit later.

Add a few divs to the comment form.

Back in our ng-comment-love.php file, we need to add a few empty divs to the comment form. Since they’re empty, they don’t actually show anything. We’ll populate these later with JavaScript.

/**
 * Adds Comment Love fields to the comment form.
 *
 * @since 1.0.0
 * @return void
 */
function ng_comment_love_fields() {
	?>
	<div id="commentlove">
		<div id="cl_messages"></div>
		<div id="cl_latest_posts"></div>
	</div>
	<?php
}

add_action( 'comment_form', 'ng_comment_love_fields' );

Add the JavaScript code.

I kind of hate JavaScript so I’m not going to go over every single piece of this code. I’ll break down a few key parts, but not all of it. It’s decently well commented if you want to wade through it entirely.

Here’s the commentlove.js code in its entirety:

jQuery.noConflict();

jQuery(document).ready(function ($) {

    var websiteField = $('#url');
    websiteField.focusout(function () {
        ng_get_latest_blog_post();
    });

});

/**
 * Checks that the URL field is filled out and a valid URL.
 *
 * @returns {string}
 */
function ng_check_url_field() {
    var urlField = jQuery('#url');

    // There's something in the URL field.
    if (urlField.val().length > 1) {
        // Is the http:// missing?
        if (urlField.val().toLowerCase().substring(0, 7) != 'http://' && urlField.val().toLowerCase().substring(0, 8) != 'https://') {
            return;
        }
    } else {
        return;
    }

    return 'ok';
}

/**
 * Gets the latest blog post from a URL.
 */
function ng_get_latest_blog_post() {
    // If the URL isn't valid - bail.
    if (ng_check_url_field() != 'ok') {
        return;
    }

    var siteURL = jQuery('#url').val();

    // Check to see if we've already gotten the results.
    if (NGLOVE.cl_fired == true) {
        if (NGLOVE.cl_url == siteURL) {
            return;
        }
        jQuery('#cl_latest_posts').empty();
    }

    jQuery('#cl_messages').empty().append('<i class="fa fa-spinner fa-spin"></i>').show();

    var data = {
        action: 'ng_get_latest_blog_post',
        url: siteURL,
        nonce: NGLOVE.nonce
    };

    // Make our ajax call.
    jQuery.post(NGLOVE.ajaxurl, data, function (response) {
        var messageDiv = jQuery('#cl_messages');
        messageDiv.empty();
        if (response.success != true) {
            return;
        }

        // Remove the spinner.
        messageDiv.empty().append('<p>Choose one of your favourite posts to add to your comment.</p>');

        var latestPosts = jQuery('#cl_latest_posts');

        latestPosts.empty();
        latestPosts.append('<li><input type="radio" id="no-love" name="cl_post_title" value="" data-url=""><label for="no-love">None</label></li>');

        // Add each item in the result to our list.
        jQuery.each(response.data, function (j, item) {
            var title = item.title;
            var link = item.link;

            var selected = '';

            if (j == 0) {
                selected = ' checked';
            }

            latestPosts.append('<li><input type="radio" id="' + title + '" name="cl_post_title" value="' + title + '" data-url="' + link + '"' + selected + '><label for="' + title + '">' + title + '</label></li>');
        });

        latestPosts.wrapInner('<ul></ul>');
        latestPosts.append('<input type="hidden" name="cl_post_url" id="cl_post_url">');

        // Set up the hidden fields with the first result.
        jQuery('#cl_post_url').val(response.data[0].link);

        // Handle the selection of the field.
        ng_select_latest_post();
    });

    NGLOVE.cl_fired = true;
    NGLOVE.cl_url = siteURL;
}

/**
 * Fills the hidden URL field based on the new selection.
 */
function ng_select_latest_post() {
    jQuery('input[name="cl_post_title"]').change(function () {
        var url = jQuery(this).data('url');
        jQuery('#cl_post_url').val(url);
    });
}

And here’s a tough explanation of a few key parts:

jQuery(document).ready(function ($) {

    var commentField = $('#comment');
    commentField.focus(function () {
        ng_get_latest_blog_post();
    });

});

This code gets fired when the document finishes loading. It basically says: when the person clicks inside the comment text box, fire all of our other code to fetch the blog post. It calls the ng_get_latest_blog_post() function, which comes later.

Further down, the ng_get_latest_blog_post() function performs a few checks.

  • It checks to see if the URL box has been filled out. If not, it doesn’t do anything.
  • It adds a progress bar spinner (via Font Awesome, which you must include separately) to one of our empty divs from before. This shows the user that we’re working!
  • It makes an ajax call, passing through the site URL that was entered.
  • Then it spits the response back (like the list of found blog posts).

You can see the ajax data we’re passing through here:

var data = {
    action: 'ng_get_latest_blog_post',
    url: siteURL,
    nonce: NGLOVE.nonce
};

Working with ajax

Now let’s go back into our main ng-comment-love.php file to set up the ajax function.

/**
 * Get latest blog posts from a URL - Ajax CB
 *
 * @since 1.0.0
 * @return void
 */
function ng_comment_love_get_blog_post() {
	// Security check.
	check_ajax_referer( 'ng_comment_love_ajax_nonce', 'nonce' );

	require_once plugin_dir_path( __FILE__ ) . '/includes/class-ng-comment-love.php';

	$commentlove = new NG_Comment_Love();
	$commentlove->set_url( strip_tags( $_POST['url'] ) );
	$posts = $commentlove->get_posts();

	// If it's not an array, we have a problem.
	if ( ! is_array( $posts ) || empty( $posts ) ) {
		wp_send_json_error( __( 'No posts found', 'ng-comment-love' ) );
	}

	wp_send_json_success( $posts );

	exit;
}

add_action( 'wp_ajax_ng_get_latest_blog_post', 'ng_comment_love_get_blog_post' );
add_action( 'wp_ajax_nopriv_ng_get_latest_blog_post', 'ng_comment_love_get_blog_post' );

First, we do a security check. If the nonce doesn’t match what we passed through then nothing will happen.

Then we include a new file (which we’ll create in a minute) and use that to create a new instance of NG_Comment_Love. This NG_Comment_Love class handles all the fetching of post information.

Once we have results from NG_Comment_Love, we send a response back to the JavaScript.

So let’s work on NG_Comment_Love next.

Set up the NG_Comment_Love class.

Create a new folder called includes and create a new file inside that called class-ng-comment-love.php.

Here’s the entirety of my class-ng-comment-love.php file:

<?php

/**
 * Class that fetches the most recent blog post from a given URL.
 *
 * @package   ng-comment-love
 * @copyright Copyright (c) 2015, Nose Graze Ltd.
 * @license   GPL2+
 */
class NG_Comment_Love {

	/**
	 * URL to get posts from.
	 *
	 * @var string
	 */
	public $url;

	/**
	 * Number of posts to get.
	 *
	 * @var int
	 */
	public $number_posts;

	/**
	 * Constructor
	 *
	 * Sets the number of posts to get.
	 *
	 * @access public
	 * @return void
	 */
	public function __construct() {
		$this->set_number_posts();
	}

	/**
	 * Sets the URL.
	 *
	 * @param string $url
	 *
	 * @access public
	 * @return void
	 */
	public function set_url( $url ) {
		$this->url = esc_url( $url );
	}

	/**
	 * Sets the number of posts we want to get.
	 *
	 * @param int $number
	 *
	 * @access public
	 * @return void
	 */
	public function set_number_posts( $number = 10 ) {
		$this->number_posts = intval( $number );
	}

	/**
	 * Get recent blog posts from the URL.
	 *
	 * @access public
	 * @return array|bool False on failure
	 */
	public function get_posts() {
		include_once( ABSPATH . WPINC . '/feed.php' );

		// Get posts from current URL.
		if ( strstr( $this->url, home_url() ) ) {
			return $this->get_current_blog_posts();
		}

		$return = array();

		$rss = fetch_feed( $this->url );

		// If there was an error - try something else or bail.
		if ( is_wp_error( $rss ) ) {
			$rss = $this->get_specific_feeds();

			if ( $rss === false || is_wp_error( $rss ) ) {
				return false;
			}
		}

		// Limit the results.
		$maxitems = $rss->get_item_quantity( $this->number_posts );

		// If there are no items - bail.
		if ( $maxitems == 0 ) {
			return false;
		}

		// Build an array of all the items, starting with element 0 (first element).
		$rss_items = $rss->get_items( 0, $maxitems );

		// Loop through each item and add it to our array.
		foreach ( $rss_items as $item ) {
			$return[] = array(
				'title' => esc_html( $item->get_title() ),
				'link'  => esc_url( $item->get_permalink() )
			);
		}

		return $return;
	}

	/**
	 * Build platform-specific URLs and get those feeds.
	 *
	 * @access public
	 * @return bool|SimplePie|WP_Error
	 */
	public function get_specific_feeds() {
		/**
		 * First try to get a WordPress RSS feed.
		 */
		$wordpress_feed = trailingslashit( $this->url ) . 'feed/';
		$rss            = fetch_feed( $wordpress_feed );

		// We successfully got a WordPress feed - return it.
		if ( ! is_wp_error( $rss ) ) {
			return $rss;
		}

		/**
		 * Next, try to get a Blogspot RSS feed.
		 */
		$blogspot_feed = trailingslashit( $this->url ) . 'feeds/posts/default';
		$rss           = fetch_feed( $blogspot_feed );

		// We successfully got a Blogspot feed - return it.
		if ( ! is_wp_error( $rss ) ) {
			return $rss;
		}

		return false;
	}

	/**
	 * Gets the latest blog posts from the current blog.
	 *
	 * @access public
	 * @return array|bool False on failure.
	 */
	public function get_current_blog_posts() {
		$recent_posts = get_posts( array(
			'numberposts' => $this->number_posts
		) );

		$return = array();

		// No posts - return an error.
		if ( ! $recent_posts || ! is_array( $recent_posts ) || ! count( $recent_posts ) ) {
			return false;
		}

		// Loop through each post and add it to our array.
		foreach ( $recent_posts as $recent_post ) {
			$return[] = array(
				'title' => strip_tags( $recent_post->post_title ),
				'link'  => get_permalink( $recent_post->ID )
			);
		}

		return $return;
	}

}

I’ll highlight a few things.

First, we have a set_number_posts method. This is used to set the number of posts that are fetched and listed out. It defaults to 10 but you could change that.

Back in ng-comment-love.php in our ng_comment_love_get_blog_post() function, you remember these lines:

$commentlove = new NG_Comment_Love();
$commentlove->set_url( strip_tags( $_POST['url'] ) );
$posts = $commentlove->get_posts();

If you wanted to change the number of blog posts to say, 5, you would add this line right before $posts = $commentlove->get_posts();:

$commentlove->set_number_posts( 5 );

So the final block would be:

$commentlove = new NG_Comment_Love();
$commentlove->set_url( strip_tags( $_POST['url'] ) );
$commentlove->set_number_posts( 5 );
$posts = $commentlove->get_posts();

Anyway, let’s go back to our NG_Comment_Love class file.

Our main method is get_posts(). This is what fetches the actual blog posts. It uses SimplePie to do all the heavy lifting.

If the site’s RSS feed cannot be automatically detected, then we use the get_specific_feeds() method. This guesses the RSS feeds for WordPress and Blogspot blogs, then tries to get posts from there.

Then, assuming we found an RSS feed, we collect the titles and URLs for the recent posts and send them back.

Save the selected URL with the comment.

So now we have a list of posts that the commenter can choose from, but we need to actually save their selection in the database.

For this, go back into our ng-comment-love.php file and add this function:

/**
 * Add Comment Love meta to the newly inserted comment.
 *
 * @param int    $id           Comment ID number
 * @param object $comment_data Comment object
 *
 * @since 1.0.0
 * @return void
 */
function ng_insert_comment_love( $id, $comment_data ) {
	// If our fields aren't set - bail.
	if ( ! isset( $_POST['cl_post_title'] ) || empty( $_POST['cl_post_title'] ) || ! isset( $_POST['cl_post_url'] ) || empty( $_POST['cl_post_url'] ) ) {
		return;
	}

	$title = strip_tags( $_POST['cl_post_title'] );
	$link  = esc_url_raw( strip_tags( $_POST['cl_post_url'] ) );

	$data = array(
		'cl_post_title' => $title,
		'cl_post_url'   => $link
	);

	add_comment_meta( $id, 'cl_data', $data, true );
}

add_action( 'wp_insert_comment', 'ng_insert_comment_love', 1, 2 );

This code runs when a new comment is inserted. It just sanitizes and saves the chosen blog post URL and title in the database (as comment meta).

Display the post name and URL with the comment.

Finally, the last step is to actually display the link to the post.

In my code, I actually edited the comment template in my theme so I could have more control over the position of the link. But for the sake of wrapping this up with a plugin and not entering theme territory, we’re just going to append it to the comment text with this function:

/**
 * Display Comment Love
 *
 * Adds the latest post name and URL after the comment text.
 *
 * @param string $comment_text The comment text.
 * @param object $comment      Comment object.
 * @param array  $args         An array of arguments.
 *
 * @since 1.0.0
 * @return string
 */
function ng_comment_love_display( $comment_text ) {
	$comment_id = get_comment_ID();
	$comment    = get_comment( $comment_id );
	$love_data  = get_comment_meta( get_comment_ID(), 'cl_data', true );

	if ( ! $love_data || ! is_array( $love_data ) ) {
		return $comment_text;
	}

	$love_string = '<div class="ng-comment-love">';
	$love_string .= sprintf(
		__( '%1$s recently posted: %2$s', 'ng-comment-love' ),
		$comment->comment_author,
		'<a href="' . esc_url( $love_data['cl_post_url'] ) . '" target="_blank" rel="nofollow">' . esc_html( $love_data['cl_post_title'] ) . '</a>'
	);
	$love_string .= '</div>';

	return $comment_text . $love_string;
}

add_filter( 'comment_text', 'ng_comment_love_display', 100 );

This checks to see if the Comment Love data exists, and if so, it appends the post URL and title to the comment text.

Download the final plugin

I’ve given you everything you need above to code this up yourself if you want. But if you want a made-for-you solution, I’ve got that too. πŸ™‚

The final plugin you get below can be activated in WordPress and should work right out of the box! There’s a small settings panel in Settings > Comment Love.

Last updated 9 May 2016

Photo of Ashley
I'm a 27 year old California girl living in England (I fell in love with a Brit!). I like to inject a little #girlpower into the WordPress development community by teaching women how to be coding badasses. more »

Don't miss my next post!

Sign up to get my blog posts sent directly to your inbox (plus exclusive store discounts!).

You might like these

Leave a Reply

(Enter your URL then click here to include a link to one of your blog posts.)

46 comments

    1. Sorry but it will not work on Blogspot. πŸ™ On a free platform like Blogspot you don’t have enough permission or access to do complex server code like this.

  1. Way too cool! I’m definitely going to be trying this tutorial and seeing how to implement it on my site. I’m so over Disqus these days, because it’s totally messing up. I’ll log in to comment and then it stays showing me as not logged in, then on some sites I see myself as logged in and can comment but then on others it doesn’t show that. Weird issue, and considering my past history with Disqus and messing up my comments, I think it’s time to move back to the regular Comment System (or try something else).

    Sasha-Shae recently posted: Target Beauty Box – The Unboxing
  2. This is great!

    I recently started trying to use CommentLuv, but it’s completely bloated and obnoxious. This is EXACTLY what I was looking for. I changed it to allow a simple opt-in to display only someone’s latest post, rather than choose from the most recent. Works perfectly!

    I only wish there was a way to get it to play nice with Jetpack comments. Any ideas?

    Kristian Bland recently posted: The time I met The Bloggess
    1. I’m so glad you found this useful! πŸ™‚

      Sadly I’m not sure about making it work with Jetpack comments, as I don’t use Jetpack. You’d have to do some digging into the code to learn more about their comment display.

  3. This is perfect! I cringe every time I start using a new plugin because most of them seem to have so much more code than necessary. And I’m ridiculously perfectionist about having everything just so, and altering plugins is such a pain because you lose the work when they update. I’d love to code my own, but js and ajax is beyond my knowledge. Idk if I’ve ever commented, but I do read your blog a lot, and when I saw that you had coded your own commentluv-type plugin, I kid you not I was wondering how you did it because I wanted to do it, too, haha. So I’m definitely going to come back and do this when I have the time to actually concentrate. I’m a learn-by-doing person, so I’m sure it will be helpful!

    1. I’m so glad you find this useful Kristen!

      I totally agree with you 100%. I don’t like using a lot of plugins because they usually have:

      1) Large settings panels
      2) A bunch of different styles to choose from (bloated CSS files)
      3) More JavaScript files loaded

      … and such.

      I prefer to consolidate everything. I’d rather have a simple, stripped down Comment Love that does exactly what I need than use a more bloated plugin that has settings for various possible combinations.

      1. Yay! Ok, so I’ve got it working and all prettified (please don’t hate me, I may have used the centered-under-the-comment idea because it looks so nice), but I have a minor issue. In my comments panel where I read/respond to comments, my css for the comment love part is showing up, borders and everything. Is that just something I have to deal with? Or is that happening because, instead of creating a css file, I just added some html to the ng-comment-love div in your php file?

        Kristen Burns recently posted: Updates & Info: The Weekly Update (3)
            1. The quickest solution would be to install a plugin like “Simple Custom CSS”. That will add a new page under “Appearance” where you can add your custom CSS code. πŸ™‚

    1. I don’t have any plans to add that, but it’s not a bad idea. πŸ™‚ You can use a similar method to add additional comment fields for whatever you like.

  4. This is awesome! I’ve been trying to find ways to decrease my plugin count and minimize space…plus, having a lot of plugins can lead to a lot of updates to keep up with. πŸ˜’

    Quick question (though probably a longer answer): Do you have any tips on combining things into one custom plugin?

    I’d love to combine a few features I want/use into a plugin so I don’t have to keep carrying them over with me, plus just kind of have one plugin I have to keep up with for the things like this. I’ve spent several hours searching, actually, for some kind of tutorial on this, but all tutorials wind up being how to create your own plugin—not how to combine them into one. (For example, creating a single plugin that works for/with this, but then also adds various coding for functions.php that would be added to the theme, but is in a plugin instead.)

    (You also don’t have to answer.)

    Liz recently posted: My coding history
    1. You can simply put each feature in a different file and use include in the main plugin file to include each other file.

      However, I’d actually advise against doing this. I’d suggest putting each big feature into its own plugin instead.

      At first I went the route of having one big “Nose Graze” plugin that had all the features I wanted (including this Comment Love one), and I massively regret it. I’m actually in the process of splitting everything back up into separate plugins.

      If you end up having another site where you want to use one of those features, you then have to go through a lot of effort to extract that one feature from the big plugin.

      I just think it makes a lot more sense from an organizational standpoint to have separate plugins. Then if you no longer want one of the features you simply have to deactivate one plugin rather than digging through a bunch of code to remove certain files/functions/etc.

  5. Back again. :p

    I installed the plugin, and it’s working for some previous comments, but I can’t seem to get the list (or even “None”) of posts to appear. Is there something I’m supposed to add to my theme’s comments file?

    Liz recently posted: Liz Lately #22
      1. Sort of…the theme is manually coded—no frameworks. I’ll be receiving my new theme soon, so it’s probably not totally worth stressing over right now, in the event that it works well with the new theme, but in the event that it doesn’t, is there a way to manually add it into the comments.php file? I’m not sure if the code part below the DIVs to add to the comment form would break it or not. :s

        1. comment_form() isn’t a framework – it’s the WordPress function you’re supposed to use to display the comment form. The plugin gets hooked into there.

          If you’re not using comment_form() then you should switch to using it. That’s best practice for a theme.

          1. I know it’s not a framework; I meant a lot of the code has been inserted manually. I’ll see if my new theme (considering my current one is three years old) works with it.

  6. Will this display the comment count for the post? I recently switched themes and I really like it – minus the fact that the post info (Date, Author Category area) does not have that little comment bubble with the # total. I have been googling to find a way to display the comment count for each post again so visitors can see the engagement. I googled so much that it made me verify that I was in fact human. lol So long story short will adding this make my bubble come back? Thanks!

Recent Posts

    Random Posts