CakePHP Ajax Comments
Recently, in a post regaurding site improvements, there was a comment request from Lecterror for the ajax comments I used in my blog system.
lecterror Said:
Hi there, I'd love to hear more about the AJAX-based comment system, looks really good.
Let me start by saying that using AJAX is not a beginners subject to tackle. This is not something to start with if you have just started writing your first CakePHP app yesterday.
With that said, on to the tutorial.
First let's start with our Comments Controller.
class CommentsController extends AppController {
var $components = array('RequestHandler');
var $helpers = array('Ajax');
var $name = 'Comments';
function add() {
if($this->RequestHandler->isAjax())
{
$this->Comment->recursive =-1;
$commentInfos = $this->Comment->findAllByIp($_SERVER['REMOTE_ADDR']);
$spam = FALSE;
foreach($commentInfos as $commentInfo)
{
if ( time() - strtotime($commentInfo['Comment']['created']) < 180)
{
$spam = TRUE;
}
}
if ($spam === FALSE)
{
if (!empty($this->data)) {
$this->data['Comment']['ip'] = $_SERVER['REMOTE_ADDR'];
$this->Comment->create();
if ($this->Comment->save($this->data)) {
$this->Comment->Post->recursive = -1;
$postData = $this->Comment->Post->findById($this->data['Comment']['post_id'], array('comment_count', 'id'));
$postData['Post']['comment_count'] =$postData['Post']['comment_count'] + 1;
$this->Comment->Post->save($postData);
$this->Comment->recursive =-1;
$comments = $this->Comment->findAllByPostId($this->data['Comment']['post_id']);
$this->set(compact('comments'));
$this->viewPath = 'elements'.DS.'posts';
$this->render('comments');
}
}
}
else
{
$this->Comment->recursive =-1;
$comments = $this->Comment->findAll(array('Comment.post_id' => $this->data['Comment']['post_id']));
$this->set(compact('comments'));
$this->viewPath = 'elements'.DS.'posts';
$this->render('spam');
}
}
else
{
$this->Session->setFlash(__('Invalid Action. Please view a post to add a comment.', true));
$this->redirect(array('controller' => 'pages', 'action'=>'display', 'home'));
}
}
}
?>
In the add() function, we setup the action to require it being called via Ajax. If not then we display a nice message, and redirect to the home page.
The next section is my solution for spam protection. It simply detects the IP Address of the poster, and determines whether it has been 3 minutes since last post or not. The spam response is handled by app/views/elements/posts/spam.ctp and the !spam response app/views/elements/posts/comments.ctp
Next we look at the /app/views/posts/view.ctp file. This is where the Ajax call is made from.
<?php
echo '<h3><a name="comments">Comments</a></h3>';
if (! empty( $post['Comment'] ))
{
echo '';
foreach ( $post['Comment'] as $comment )
{
echo '<div class="comments">';
echo '<span class="date">On ' . $time->nice( $comment['created'] ) . '</span>' . " <h1>" . $comment['name'] . ' Said:</h1>';
echo $comment['content'];
echo '</div>';
}
}
?>
</div>
<div class="comments form">
<?php echo $ajax->form('/comments/add', 'post', array('url' => '/comments/add', 'update' => 'PostsComments', 'indicator' => 'commentSaved'));?>
<fieldset>
<legend><?php __('Add Comment');?></legend>
<div id="commentSaved" style="display: none; float: right;">
<?php echo $html->image('ajax-loader.gif'); ?>
</div>
<?php
echo $form->hidden('Comment.post_id', array('value' => $post['Post']['id']));
echo $form->input('Comment.name');
echo $form->input('Comment.email');
echo $form->input('Comment.web', array('value' => 'http://'));
echo $form->input('Comment.content');
//
?>
</fieldset>
<?php echo $form->end('Submit');?>
</div>
The top div PostsComments is what is refreshed from the ajax call. This div displays the files /app/views/elements/posts/comments.ctp and /app/views/elements/posts/spam.ctp as shown below.
if (! empty( $comments ))
{
echo '<h3>Comments</h3>';
foreach ( $comments as $comment )
{
echo '<div class="comments">';
echo '<span class="date">On ' . $time->nice( $comment['Comment']['created'] ) . '</span>' . " <h1>" . $comment['Comment']['name'] . ' Said:</h1>';
echo $comment['Comment']['content'];
echo '</div>';
}
}
echo '<h2 class="saved">Your Comment Has Been Saved</h2>';
?>
And then the spam.ctp file
if (! empty( $comments ))
{
echo '<h3>Comments</h3>';
foreach ( $comments as $comment )
{
echo '<div class="comments">';
echo '<span class="date">On ' . $time->nice( $comment['Comment']['created'] ) . '</span>' . " <h1>" . $comment['Comment']['name'] . ' Said:</h1>';
echo $comment['Comment']['content'];
echo '</div>';
}
}
?>
<h2 class="spam">You Must Wait 3 Minutes Between Each Comment</h2>
That's it. Any questions, just ask


lecterror Said:
This is great, thanks! One thing though, have you ever had any problems with AJAX requests not being done properly? When I was working on my "paginate article comment with ajax" feature, I was debugging with Firebug, and strange thing would happen on random. Sometimes Prototype wouldn't make the call properly, and isAjax() would return false. This is why I had to add the ajax named param (as you can see here: http://dsi.vozibrale.com/articles/view/neutrino-is-not-dead#comments), so what I do now is check for ajax calls like this: if ($this->RequestHandler->isAjax() || isset($this->passedArgs['ajax'])) It seems like the bakery has a similar issue with the rating system.. :-/ Ooops, sorry for the long rant, thanks again for sharing the code, this is my next to-do feature! Cheers!