Adding Comments in your Site with the Facebook API

Now that I have jumped almost 2 weeks without a post, as I have been super busy, this should have been a real easy item to post, but I wanted to make sure that this is done correctly. This is probably one of the easiest methods to add some great Facebook functionality in your site. This revolves around comments to a page. In my example, I am posting topics to discuss. This is mainly just a small little blurb that I will enter via an admin form on the site, and then list the different topics for everyone to select one. Once they select it, they can view the details of the topic and then comment on it using the Facebook API/Social plugin. So as always, lets go through a basic plan for this idea.

1. The model is Topic, with a table in the DB labeled “topics”
2. Only the admin has access to add or edit the topics
3. All comments on this topic will be done through the Facebook API/Social Plugin Comments
4. Topics will have a title that will also double as the Unique ID (to be explained later)
5. Topic titles, or themes, will not be allowed to be edited, to be explained why later
6. Administration of the comments will be done by the Facebook Application admins, which differs from the site admins
7. Start Dates will determine if the topic is allowed to be visible yet
8. End dates are optional, and will be built upon later with more advanced FBML/FB JS libraries

And there it is, some basic ideas behind the whole idea. So now lets get into some of the items called out in Numbers 4 and 5

With the Facebook Comments, you can get a base idea behind what this entails at the following location:
http://developers.facebook.com/docs/reference/plugins/comments
This will give you the basic comment box to add to the site. The important part of this comment box is the “Unique ID”. This will add all the comments to the basic page. However, it must be Unique and not change. In my requirements above, I am listing the topic title as the Unique ID. I could easily generate a number for this and add a couple of letters to it to make it truly unique and allow the title to change. In this example, I want to emphasize certain elements, so that is why I chose to do it this way. You can easily do this differently and still have it work. But this leads to Number 5.

The Unique ID should not change for the page, topic, area, etc. If the ID changes, all previous comments vanish and you basically are starting over. So it is important that the IDs are unique and do not change. This way the element of participation is intact. It will always stay in your site.

Another note. This example is showing how to include Facebook API in a CakePHP application. You can follow these same techniques for just about any site, no matter what framework. Just apply the same inclusion techniques in the proper location for the different framework/php site.

Creating the base elements and DB table

So, let’s get this thing going. First you should have a CakePHP site going and ready, connecting to a DB somewhere. Bake the Model, Controller and Views for this one. The SQL for the Topics that I am using is below:

CREATE TABLE IF NOT EXISTS `topics` (
  `topic_id` int(11) NOT NULL auto_increment,
  `topic_slug` varchar(255) NOT NULL COMMENT 'Slug for use in the Facebook comments',
  `question` text NOT NULL COMMENT 'Question that is created for the different topics',
  `start_date` date NOT NULL COMMENT 'Start date for the question',
  `end_date` date default NULL COMMENT 'End date for the question',
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY  (`topic_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Topics for discussions and other items' AUTO_INCREMENT=1012 ;

I baked this using the basic CRUD for the views and Controller, no Admin hooks, and the Model validation is as follows:

class Topic extends AppModel {
    var $name = 'Topic';
    var $primaryKey = 'topic_id';
    var $displayField = 'topic_slug';
    var $validate = array(
        'topic_slug' => array(
            'minlength' => array(
                'rule' => array('minlength', 3),
                'message' => 'The Topic Identifier should be longer than 3 characters',
            ),
            'maxlength' => array(
                'rule' => array('maxlength', 50),
                'message' => 'The Topic Identifier should be no longer than 50 characters',
            ),
        ),
        'question' => array(
            'minlength' => array(
                'rule' => array('minlength', 3),
                'message' => 'You need to have a question at least 3 characters long',
            ),
        ),
        'start_date' => array(
            'date' => array(
                'rule' => array('date'),
                'message' => 'Please set a valid start date for this question',
            ),
        ),
    );
}

Controller work for the Index

I removed all the links to Add, Edit and Delete from the views and kept those hidden except for the Admin. Now comes the controller. This is the important part of this, as we need to include the Facebook items, and based on the last post, we put those in the Vendors, so we need to include this. I am also using Sanitize, Auth and few others (Security and Email), along with the helpers of HTML and FORM.

<?php
App::import('Sanitize');
App::import('Vendor', 'facebook');
class TopicsController extends AppController {

    var $name = 'Topics';
    var $helpers = array('Html', 'Form');
    var $components = array('Security', 'Auth', 'Email');

it is important to import the Facebook library. I will not go over the Auth in this example, or the beforeFilter or isAuthorized functions. The first action we need to deal with is the index() function. Originally, it looks like:

function index() {
        $this->Topic->recursive = 0;
        $this->set('topics', $this->paginate());
    }

For our purposes, we want to limit the amount of posts shown to only those that have a start date that is equal to or less than today’s date. I have three examples in my DB, and you can create as many as you want for the example. Just make sure at least one is in the future. Right now when we look at the Index page, it is showing all topics. So we need to set different items in the call for paginate. To only grab those that are equal to or less than to today’s date, we will alter the call to paginate as follows:

$today = date('Y-m-d');
$cond = array('Topic.start_date <=' => $today);
$this->set('topics', $this->paginate('Topic', $cond));

This will limit all the posts to display to everyone to only those with a current (or past) start date. All those with a start date in the future will not be available until that date. But we also need to set a variable for the Admin to see all those topics waiting for the future.

$future_topics = $this->Topic->find( 'all', array('conditions' => array('Topic.start_date >' => $today)) );
$this->set('futop', $future_topics);

We now have 2 variables to work with in the view. You can do it a whole slew of different ways, and that is fine, this is just the method I prefer to do it. Now we need to get the view going. This is going to a little different, I have a few things to show Admin stuff just to admins, and show the future topics just to admins.

The Add and Edit functions

That does it for the view, now we need to do the Add and Edit functions. Remember we baked this, and let all the default take place. But, I need to get a valid Unique ID, and do not want any spaces in it. So we will alter the Add function to do this. And for the Edit function, we want to prevent any editing of the slug area.

function add() {   
        if (!empty($this->data)) {
            $san = new Sanitize();
            
            $this->data['Topic']['topic_slug'] = $san->paranoid($this->data['Topic']['topic_slug'], array(' '));
                                    
            $this->Topic->create();
            // Slug the topic title before saving
            $this->data['Topic']['topic_slug'] = preg_replace('/ /', '_', $this->data['Topic']['topic_slug']);
            if ($this->Topic->save($this->data)) {
                $this->Session->setFlash(__('The new Topic has been saved and will be put in queue', true));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The Topic could not be saved. Please, try again.', true));
            }
        }
    }

I am sanitizing the data for the slug, and trusting myself for the topic details. In real life, we would apply a sanitizing effect to this field as well. I then erase all non alphanumeric characters, and replace spaces with an underscore “_”. Then save it up. I am basically leaving the “add.ctp” alone with what was baked. I removed the fields that I really do not care about.

So now we move to the edit. We want to prevent any changes to the topic_slug, so in the form, we will remove this from the form and make sure we remove the underscores when showing it. The controller is:

function edit($id = null) {
        if ( ((!$id) || (!$this->Topic->ifExists('Topic', $id))) && empty($this->data) ) {
            $this->Session->setFlash(__('The Topic you selected does not exist, please try again.', true));
            $this->redirect(array('action' => 'index'));
        }
        if (!empty($this->data)) {
            if ($this->Topic->save($this->data)) {
                $this->Session->setFlash(__('The topic has been saved', true));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The topic could not be saved. Please, try again.', true));
            }
        }
        if (empty($this->data)) {
            $this->data = $this->Topic->read(null, $id);
        }
    }

One quick thing to call out in this function. I added a new function to the parent app_model.php to make sure that the ID we are trying to edit exists and is valid. The basic baked function only checks for an ID, if one exists, it goes thru to the form, even if the ID does not exist in the DB. On the edit.ctp view, I removed the form element for the topic_slug and added this:

$topic_title = preg_replace('/_/', ' ', $this->data['Topic']['topic_slug']);
echo "<b>Topic Title</b>:   <h4>" . ucwords($topic_title) . "</h4>";

And now the edit form works, keeps the underscore in and saves the form. Again, in the real world, you would want to do a little more on the back end to ensure there is no changes being made. But now we get to the view area and where the Facebook API/Social Plugin comes in.

Creating the Detail View and adding in Facebook API/Social Plugin

So here is where we get to the meat of the post. All other areas were in preparing to get here. Now, all you need to do is create a few different topics to work with. We want to alter the view in such a way it will display only the items we need, and will only display the topic to an admin if it is a future topic. Otherwise return the user to the index for topics with a message saying that the topic does not exist or is not yet available. So the view function is simple:

function view($id = null) {
        if ( (!$id) || (!$this->Topic->ifExists('Topic', $id)) ) {
            $this->Session->setFlash(__('The requested topic does not exist. Please try again.', true));
            $this->redirect(array('action' => 'index'));
        }
        // Make sure this is a valid ID as well        
        $topic = $this->Topic->read(null, $id);
        $topic_title = $topic['Topic']['topic_slug'];
        // Set the title without the slug
        $topic_title = preg_replace('/_/', ' ', $topic['Topic']['topic_slug']);
        $topic['Topic']['topic_title'] = ucwords($topic_title);
        
        // make sure this topic is valid
        if ( ($topic['Topic']['start_date'] > date('Y-m-d')) && (!$this->is_admin) ){
            $this->Session->setFlash(__('The requested topic does not seem to be available at the present moment. Try again later.', true));
            $this->redirect(array('action' => 'index'));
        }
        $this->set('topic', $topic);
    }

Again, I used the app_model.php function to make sure the topic ID exists as well as being valid. If not, a message will be displayed to the end user. It sets the topic, and now we are ready for the view.

So what I did, was wipe all the data for the list, and added my own. I set the title to the H2 element. I set the question to a paragraph tag, and added the new FBML comment tag. The information for this tag can be found at:
http://developers.facebook.com/docs/reference/fbml/comments_%28XFBML%29

Pay close attention to the different parameters you can set and how you can format this to fit your style on your site. I have set the following elements and values:
xid = $topic[‘Topic’][‘topic_slug’]
width = 600
title = $topic[‘Topic’][‘topic_title’]
publish_feed = false

And so the actual markup added to the page is the following, including the FBML script and div tag:

<div id="fb-root"></div>
<script src="http://connect.facebook.net/en_US/all.js#appId=APP_ID&xfbml=1"></script>
<fb:comments xid="<?php echo $topic['Topic']['topic_slug']; ?>" width="600" title="<?php echo $topic['Topic']['topic_title']; ?>" publish_feed="false"></fb:comments>

And if you go to the page you will now see the comments section, but we are missing a very important item in the comments, and that is administration. The way to add this, we will need to add the correct element to the page. This consists of adding the correct JS API to the site.

According to the documentation, you have to be listed as a developer for the application to be able to administer comments. The way we have it set up right now, we can have comments, but there is no way we can administer those. The key to this is very simple and easily overlooked. In the FBML markup that we added to the view, you will notice a specific section:

http://connect.facebook.net/en_US/all.js#appId=APP_ID&xfbml=1

You must input your application ID to this markup. Once you do that, and if you are listed as a developer, then you are able to now administer comments on your site.

See, that was easy. There is a lot of text on this post, and most of it has to do with setting up CakePHP to do this. Once we got to the Facebook API/Social Plugin section, it was very simple. With the next post, we will expand on the comments section a little further and see what else is possible.

You can see this all in action at the following:
www.stephenhird.com

This Post Has 3 Comments

  1. stephen

    You are correct. What it is using is the Social Media Plugin coupled with the graph API. It was a poor choice of words on my part and I will update the post to reflect that correctly.

  2. Arif Saeed

    Yay! facebook everywhere 🙂

    well personally I feel to have comments in websites own database, but hey this is new tech 🙂

Leave a Reply