Yii 1.1: comment-module

Module that adds comments to your application. You can add comments on any AR Model you like.
29 followers

Comment module

makes every entity of your application commentable. Features:

  • Create, Update, Delete comments with ajax
  • Gravatar support
  • define multiple models that can be commented
  • Events raised on new, update, delete
  • more coming soon...

If there is something missing here, or you think one step should be described more detailed, please report it. Thanks!

Requirements

Resources

Download

There are two ways to get this extension working:

  1. Clone repo:

    • Go to your application baseDir (protected in default yii webapp).
    • git clone https://github.com/yiiext/comment-module.git extensions/comment-module
      • If your project is in a git repository you can alternatively add comment-module as a submodule like this:
      • git submodule add https://github.com/yiiext/comment-module.git protected/extensions/comment-module
    • go to new comment-modules base dir and run git submodule update --init to get the gravatar extension that's included.
  2. Download latest release and put all the files into extensions/comment-module under your application baseDir (protected in default yii webapp). To be able to use Gravatar support you have to copy YiiGravatar.php into extensions/comment-module/extensions/gravatar.

Quickstart

Add module to your application config (optional config values are commented):

<?php
    // ...
    'modules'=>array(
        // ...
        'comment'=>array(
            'class'=>'ext.comment-module.CommentModule',
            'commentableModels'=>array(
                // define commentable Models here (key is an alias that must be lower case, value is the model class name)
                'post'=>'Post'
            ),
            // set this to the class name of the model that represents your users
            'userModelClass'=>'User',
            // set this to the username attribute of User model class
            'userNameAttribute'=>'username',
            // set this to the email attribute of User model class
            'userEmailAttribute'=>'email',
            // you can set controller filters that will be added to the comment controller {@see CController::filters()}
//          'controllerFilters'=>array(),
            // you can set accessRules that will be added to the comment controller {@see CController::accessRules()}
//          'controllerAccessRules'=>array(),
            // you can extend comment class and use your extended one, set path alias here
//          'commentModelClass'=>'comment.models.Comment',
        ),
        // ...
    ),
    // ...

Create database tables: You can use the database migration provieded by this extension or create a table (example for mysql):

CREATE TABLE IF NOT EXISTS `comments` (
      `id`         int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
      `message`    text COLLATE utf8_unicode_ci,
      `userId`     int(11) UNSIGNED DEFAULT NULL,
      `createDate` datetime DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `fk_comments_userId` (`userId`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

You might also want to add a foreign key for userId column that references you user tables pk.

Create a database table for every commentable Model relation:

CREATE TABLE IF NOT EXISTS `posts_comments_nm` (
      `postId`    int(11) UNSIGNED NOT NULL,
      `commentId` int(11) UNSIGNED NOT NULL,
      PRIMARY KEY (`postId`,`commentId`),
      KEY `fk_posts_comments_comments` (`commentId`),
      KEY `fk_posts_comments_posts` (`postId`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

You might want to add foreign keys here too.

Add commentable behavior to all Models you want to be commented.

<?php
    // ...
    public function behaviors() {
        return array(
            'commentable' => array(
                'class' => 'ext.comment-module.behaviors.CommentableBehavior',
                // name of the table created in last step
                'mapTable' => 'posts_comments_nm',
                // name of column to related model id in mapTable
                'mapRelatedColumn' => 'postId'
            ),
       );
    }

Finally add comments to your view template of the commentable model:

<h1>comments</h1>
 
<?php $this->renderPartial('comment.views.comment.commentList', array(
    'model'=>$model
)); ?>

Extending Comment-Module

Comment module raises events to which you can attach event handlers to handle them. See The Definitive Guide to Yii on how to do this.

You can also attach behaviors to CommentModule by setting 'behaviors'=>array(/* ... */) in the module config described above. See CModule::behaviors on how to add behaviors to a module.

onNewComment

This event is raised when a new comment has been saved. The following attributes are available on the $event given as the first parameter to the event handler:

  • $event->comment is the ActiveRecord instance of the currently added comment.
  • $event->commentedModel is the model the comment was added to.

Possible use cases:

  • Send an E-Mail-Notification

onUpdateComment

This event is raised when a user edited a comment. The following attributes are available on the $event given as the first parameter to the event handler:

  • $event->comment is the ActiveRecord instance of the updated comment.

onDeleteComment

This event is raised when a user deleted a comment. The following attributes are available on the $event given as the first parameter to the event handler:

  • $event->comment is the ActiveRecord instance of the deleted comment.

Changelog

0.6.0 (2011-12-22)

  • added getCommentCount() to CommentableBehavior (cebe)
  • added delete button and action (cebe)
  • added update button and action (cebe)
  • fixed problem with sqlite timestamp (cebe)

0.5.0 (2011-12-19)

  • initial public release (cebe)

Total 20 comments

#15884 report it
jonathan serrano at 2013/12/26 07:10am
lacks pagination

wow great it works! unfortunately there's a missing part, pagination, it's inappropriate ti load thousands of comments in a load out, the proper way of doing this is loading 25 comments at a time, then at the bottom there should be this "load more comments" or "next page" button

#15649 report it
Sukunj Mendpara Rudra Soft at 2013/12/04 04:54am
Help..

Thanks for wonderfull extension...

But i have one problem...

When i refresh page previous comments are not displaying..

#14563 report it
chamara at 2013/08/23 01:03am
Thanks lot...

save me..

but i have a problem when page refresh all commented disappear only showing the comment box, i want to show all post that add plzz help me any one?

#11993 report it
sanguina at 2013/02/19 01:46pm
Suggestion for Improvement

thanks for this awesome extension. a small suggestion:

Currently logged in users are being able to post empty comments as well. Some sort of error message would have been better

thanks again, using this in my first yii app and its working great ! :)

#11800 report it
skworden at 2013/02/04 04:26pm
This is how i added user image

Please read all of the comments. you also have to add this to your Comment.php model

public function getUserImg() { return $this->user->filename; }

Also, please read this post i made too on how to upload files to your db for users User widget this is where the filename comes from.

In the section below where it has yii gravitar widget in _view.php I replace it with this.

<?php 
        $userObject = $data->userImg; //Renders the comment users filename path Note: public function getUserImg() {return $this->user->filename;} was added to Comment.php model to make it accessable here. filename would be whatever your image column is in your user table
        $imgbaseurl = Yii::app()->baseUrl.'/'; //gets your base url
        $defaultimg = $imgbaseurl.'images/users/User/default.png'; //Path to your default image location and image name
        $defaultguestimg = $imgbaseurl.'images/users/User/default.png'; //Path to your default guest image location and image name
        $imagelink = $imgbaseurl.''.$userObject; //actual user image
 
        if (Yii::app()->user->isGuest) //if user is guest
        {
            echo "<img src=$defaultguestimg>";//render defualt guest image
        }
        elseif (empty($userObject)) //if image column in your database is empty 
        {
            echo "<img src=$defaultimg>"; //render the default image
        }
        else //if user is not guest and and the db field isn't empty  
        {
            echo "<img src=$imagelink>"; //render the user image
        }
        ?>
#11530 report it
skworden at 2013/01/18 12:02pm
Changed Styles

EDIT: There is a problem when adding/deleting comments... for example when it shows 1 of 15 comments it doesn't update until you refresh the page. Also, if you are using pagination the comment goes below the pager instead of under the last comment until you refresh the page. Anyone know fixes to these?

I changed the styles (see screen shot at the bottom) I think it looks a lot more professional. This also add an edit link to the comments if the user is admin and it is not the admin's post.

Please Note: the submit button style will change site wide you can change it to be just for the comment if you like. Also, I moved my styles to my main css file. however, if you don't do so you may have to clear the site cache to see changes (you can manually delete them by going to your webroot/assets/ (assets folder is at the same level as protected folder) and deleting everything in there.)

CSS

/*comment extension*/
 
div.ext-comment {
background: #f7f7f7;
width: 100%;
margin: 15px auto;
height: auto;
border: 1px solid #e9e9e9;
overflow: hidden;
}
div.commentspacer {
padding:
10px;
}
div.ext-comment p {
margin: 0px 0px 0px 100px;
min-height: 77px;
height: auto;
background: #fff;
padding: 8px;   
}
div.ext-comment img {
float: left;
width: 80px;
height: 80px;
}
span.ext-comment-name {
font-weight: bold;
}
 
div.comment-head {
font-weight: bold;
text-transform: capitalize;
padding: 5px;
}
span.ext-comment-options {
float: right;
color: #aaa;
}
 
div.comment-img {
float: left;
width: 80px;
height: 80px;
margin: auto;
padding: 5px;
border: 1px solid #e0e0e0;
background: #fff;
}
.comment-head {
border-bottom: 1px solid #e9e9e9;
font-weight: bold;
text-transform: capitalize;
padding: 8px;
color: #6a6a6a;
}
div.textfieldholder {
margin: 0px 0px 0px 100px;
min-height: 70px;
height: auto;
display: block;
padding: 0px;
}
 
.commenttextfield {
min-height: 77px;
height: auto;
background: #fff;
padding: 8px;
width: 98%;
margin: 0px;
}
.commenttextfield:focus {
box-shadow: 0 0 5px rgba(81, 203, 238, 1);
border: 1px solid rgba(81, 203, 238, 1);
}
.commentbutton {
float: right;
margin-top: 0px;
margin-right: 13px;
padding: 0px 10px 10px; 10px;
}
#ext-comment .button, input[type="submit"] { 
color: #ffffff;
background-color: hsl(200, 0%, 15%) !important;
background-repeat: repeat-x  !important;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#b5b5b5", endColorstr="#262626") !important;;
background-image: -khtml-gradient(linear, left top, left bottom, from(#b5b5b5), to(#262626)) !important;
background-image: -moz-linear-gradient(top, #b5b5b5, #262626) !important;
background-image: -ms-linear-gradient(top, #b5b5b5, #262626) !important;
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b5b5b5), color-stop(100%, #262626)) !important;
background-image: -webkit-linear-gradient(top, #b5b5b5, #262626) !important;
background-image: -o-linear-gradient(top, #b5b5b5, #262626) !important;
background-image: linear-gradient(#b5b5b5, #262626) !important;
border-color: #262626 #262626 hsl(200, 0%, 1%) !important;
color: #fff !important; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.92) !important;
-webkit-font-smoothing: antialiased !important;
min-height: 0px !important;
min-width: 0px !important;
-moz-border-radius: 0px !important;
-webkit-border-radius: 0px!important;
-khtml-border-radius: 0px!important;
border-radius: 0px!important;
margin: 0px!important;
padding: 3px!important;
}
#ext-comment .button, input[type="submit"]:hover { 
opacity:0.7;
}
 
#comments a:link {
font: #fff;
color: #ffffff !important;
}
#comments a:visited {
text-decoration: none;
}
#comments a:hover {
text-decoration: none;
}

views/_form

<?php if (Yii::app()->user->isGuest) {
?><div class="ext-comment-not-loggedin">
    Sorry, you have to login to leave a comment.
</div><?php } else { ?>
<div id="ext-comment-form-<?php echo $comment->isNewRecord ? 'new' : 'edit-'.$comment->id; ?>" class="form">
 
<?php $form = $this->beginWidget('CActiveForm', array(
    'id'=>'ext-comment-form',
    'action'=>array('/comment/comment/create'),
    'enableAjaxValidation'=>false
)); ?>
 
    <?php echo $form->errorSummary($comment); ?>
<div class="ext-comment">
    <div class="comment-head">
        <span class="ext-comment-head">
        Create New Comment
        </span>
    </div>
    <div class="commentspacer">
        <div class="comment-img">
            <?php $this->widget('comment.extensions.gravatar.yii-gravatar.YiiGravatar', array(
                             'email'=>$data->userEmail,
                             'size'=>80,
                             'defaultImage'=>'monsterid',
                             'secure'=>false,
                             'rating'=>'r',
                             'emailHashed'=>false,
                             'htmlOptions'=>array(
                             'alt'=>CHtml::encode($data->userName),
                             'title'=>CHtml::encode($data->userName)
                             )
                             )); ?>
        </div>
        <?php echo $form->error($comment,'message'); ?>
 
        <div class="textfieldholder">
        <?php echo $form->textArea($comment,'message',array(
            'class'=>'commenttextfield', //height/width is set by this css class above
            'placeholder'=>'1,000 Charter Max...',//text to display when new message field is empty
            'maxlength'=>1000, //sets number of charters aloud to be input for comment.
        )); ?>
        </div>
 
    </div>
    <div class="commentbutton">
        <?php if ($comment->isNewRecord) {
 
            echo $form->hiddenField($comment, 'type');
            echo $form->hiddenField($comment, 'key');
 
            /* echo CHtml::hiddenField('returnUrl', $this->createUrl(''));}
            echo CHtml::submitButton('Save'); */
            echo CHtml::ajaxSubmitButton('Post Comment',
                array('/comment/comment/create'),
                array(
                    'replace'=>'#ext-comment-form-new',
                    'error'=>"function(){
                        $('#Comment_message').css('border-color', 'red');
                        $('#Comment_message').css('background-color', '#fcc');
                    }"
                ),
                array('id'=>'ext-comment-submit' . (isset($ajaxId) ? $ajaxId : ''))
            );
        } else {
            echo CHtml::ajaxSubmitButton('Update Comment',
                array('/comment/comment/update', 'id'=>$comment->id),
                array(
                    'replace'=>'#ext-comment-form-edit-'.$comment->id,
                    'error'=>"function(){
                        $('#Comment_message').css('border-color', 'red');
                        $('#Comment_message').css('background-color', '#fcc');
                    }"
                ),
                array('id'=>'ext-comment-submit' . (isset($ajaxId) ? $ajaxId : ''))
            );
        }
        ?>
    </div>
</div>
 
<?php $this->endWidget() ?>
 
</div><!-- form -->
<?php } ?>

views/_view

<?php
    Yii::app()->clientScript->registerCss('ext-comment', "");
?>
<div class="ext-comment" id="ext-comment-<?php echo $data->id; ?>">
    <div class="comment-head">
        <span class="ext-comment-head">
            <span class="ext-comment-name"><?php echo CHtml::encode($data->userName); ?></span>
            wrote on
            <span class="ext-comment-date">
                <?php echo Yii::app()->format->formatDateTime(
                    is_numeric($data->createDate) ? $data->createDate : strtotime($data->createDate)
                ); ?>
            </span>
        </span>
        <span class="ext-comment-options">
            <?php if (!Yii::app()->user->isGuest && (Yii::app()->user->id == $data->userId)) {
                echo CHtml::ajaxLink('delete', array('/comment/comment/delete', 'id'=>$data->id), array(
                    'success'=>'function(){ $("#ext-comment-'.$data->id.'").remove(); }',
                    'type'=>'POST',
                ), array(
                    'id'=>'delete-comment-'.$data->id,
                    'confirm'=>'Are you sure you want to delete this item?',
                ));
                echo " | ";
                echo CHtml::ajaxLink('edit', array('/comment/comment/update', 'id'=>$data->id), array(
                    'replace'=>'#ext-comment-'.$data->id,
                    'type'=>'GET',
                ), array(
                    'id'=>'ext-comment-edit-'.$data->id,
                ));
            }
            /* adds edit link to post if is not admin's post so they can still edit it */
                              elseif (Yii::app()->getModule('user')->isAdmin()) {
                echo CHtml::ajaxLink('edit', array('/comment/comment/update', 'id'=>$data->id), array(
                    'replace'=>'#ext-comment-'.$data->id,
                    'type'=>'GET',
                ), array(
                    'id'=>'ext-comment-edit-'.$data->id,
                ));
            }?>
        </span>
    </div>
    <div class="commentspacer">
        <div class="comment-img">
            <?php $this->widget('comment.extensions.gravatar.yii-gravatar.YiiGravatar', array(
                          'email'=>$data->userEmail,
                          'size'=>80,
                          'defaultImage'=>'monsterid',
                          'secure'=>false,
                          'rating'=>'r',
                          'emailHashed'=>false,
                          'htmlOptions'=>array(
                          'alt'=>CHtml::encode($data->userName),
                          'title'=>CHtml::encode($data->userName)
                          )
                          )); ?>
        </div>
 
 
        <p><?php echo nl2br(CHtml::encode($data->message)); ?></p>
 
    </div>
</div>

views/commentList

<?php
 
/** @var CArrayDataProvider $comments */
$comments = $model->getCommentDataProvider();
$comments->pagination->pageSize = 20; //sets number of comments to display per page
 
$this->widget('zii.widgets.CListView', array(
    'dataProvider'=>$comments,
    'itemView'=>'comment.views.comment._view',
        'template'=>'{items}{pager}<br/><br/>', //breaks are here because my css wasn't cooperating and it needed to be spaced because of overlap
));
 
$this->renderPartial('comment.views.comment._form', array(
    'comment'=>$model->commentInstance
));

New Style

screen shot

Default Style

a

#11474 report it
skworden at 2013/01/15 04:40pm
RE: No Scope Error

This should get rid of your No Scope Error. All it is saying is that you don't have $comment defined. So you will have to define it...

In your main.php under the section below add your commentable models names there.

i.e. I have a models that I added this too named Products and one called Companies.

// define commentable Models here (key is an alias that must be lower case, value is the model class name)
       'post'=>'Products', 'Companies'
),

Also, in the database schema I just left out the unsigned part because my user table doesn't use it and you won't be able to create a FK relationship if they aren't the same.

You should also add FKs when adding your comment tables. If you don't the comments won't delete out of the table. It will only delete out of the comment table.

Here is an example of what I used.

SET FOREIGN_KEY_CHECKS=0;//turns off foreign key checks so you don't throw an errno 150 error...
DROP TABLE IF EXISTS `comments`;
 
CREATE TABLE IF NOT EXISTS `comments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `message` text COLLATE utf8_unicode_ci,
  `userId` int(11) DEFAULT NULL,
  `createDate` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `comments` (`userId`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
DROP TABLE IF EXISTS `company_comments`;
 
CREATE TABLE IF NOT EXISTS `company_comments` (
  `postId` int(11) NOT NULL,
  `commentId` int(11) NOT NULL,
  PRIMARY KEY (`postId`,`commentId`),
  KEY `fk_posts_comments_comments` (`commentId`),
  KEY `fk_posts_comments_posts` (`postId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
ALTER TABLE `comments`
  ADD CONSTRAINT `comments` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON UPDATE CASCADE;
 
ALTER TABLE `company_comments`
  ADD CONSTRAINT `company_comments` FOREIGN KEY (`commentId`) REFERENCES `comments` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
 
SET FOREIGN_KEY_CHECKS=1;//turns foreign key checks back on
#9899 report it
thura747 at 2012/09/20 01:24am
No scope defined in CommentModule for commentable Model Request

No scope defined in CommentModule for commentable Model Request

I followed your instructions. but I found the error. I changed 'userModelClass'=>'User', to 'userModelClass'=>'Users', and userId at comments tbl is int(11) unsigned null, also same as the id of Users tbl. How could I solve this problem?

#9357 report it
w00tw00t111 at 2012/08/06 05:20pm
Re: you need a relation table from comments to any model using it

@CeBe,

I did create that table

CREATE TABLE IF NOT EXISTS `posts_comments_nm` (
      `postId`    int(11) UNSIGNED NOT NULL,
      `commentId` int(11) UNSIGNED NOT NULL,
      PRIMARY KEY (`postId`,`commentId`),
      KEY `fk_posts_comments_comments` (`commentId`),
      KEY `fk_posts_comments_posts` (`postId`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

If that's what you were referring to, but still not sure how the module knows the "context" or owner of the post_id.

Ended up editing the above table and adding parent_model and made the appropriate changes to the module model.

Didn't know if you had built the ability into the module to begin with.

#9340 report it
jiaming at 2012/08/05 01:49am
is there a way to create pagination?

is there a way to create pagination?...or the comment list is getting too long...just like the comments in this page...=.=

#9335 report it
CeBe at 2012/08/04 05:19am
you need a relation table from comments to any model using it

@w00tw00t111 do you have a relation table created for every model that is commentable? All you have to do is described here:

https://github.com/yiiext/comment-module#quickstart

#9330 report it
w00tw00t111 at 2012/08/03 11:34am
Table Prefix Solution

Also,

for others who are using table prefixes, I found the following was necessary in Comment.php:

/**
     * @return string the associated database table name
     */
    public function tableName()
    {
        return '{{comments}}';
    }
#9329 report it
w00tw00t111 at 2012/08/03 11:32am
Multiple Model Classes Same ID ?

Might be slightly confused about this Cebe, but installed it and it's working great.

However, how is the itemview supposed to know which model the comment is related to.

For instance, let's say you have a question with an id of 1 and an answer with a id of 1.

If both models are commentable (question && answer) and each one has a comment added, then when the view searches for id of 1, won't it pull both comments for each individual model?

This is the case I am having currently. In modules that I've designed for my app that are reusable by multiple models I have had to add a column "parent_model" to store the model class that this particular item is for. So in the question/answer example it would be stored as follows: comment_id: 1, parent_model: Question, parent_id: 1, message: test, createDate: NOW() and comment_id: 2, parent_model: Answer, parent_id: 1, message: test2, createDate: NOW()

Hopefully, that makes sense. Am I doing something wrong? Is this functionality already built in?

Thanks CeBe!

#8327 report it
jiaming at 2012/05/25 09:31pm
@Cebe

Thanks..That works!

Amazing extension...save me tons of time

Cheers Jiaming

#8323 report it
CeBe at 2012/05/25 03:49pm
get_class

@jzhong5: how about get_class?

if (get_class($event->commentedModel) == 'Question') {
    // ...
}
#8322 report it
jiaming at 2012/05/25 10:46am
How can I get the model's name?

Hi, I am using this module to send email notification and also using it in other models.

However, I just send email in one Model, so I don't want to messed things up...

I know $event->commentedModel will give me the specific values in the model...

But How can i get the model's name?

For example ,I use this ext in Questions Model, how can I get "Questions" instead of a specific id or name belongs to 'Questions'..?

Because if i can get this, I can only send email notification in the specific model....

Best, jiaming

#8227 report it
jiaming at 2012/05/20 12:03am
Thanks!!!!

THANKS!! Works like a charm now...

THANKS FOR YOUR HELP!!!

#8226 report it
CeBe at 2012/05/19 06:33pm
handling events

To attach eventhandlers to modules events, you can configure a callback inside your main config, or if you have PHP 5.3 or higher you can even add an anonymous function there:

<?php
    // ...
    'modules'=>array(
        // ...
        'comment'=>array(
            'class'=>'ext.comment-module.CommentModule',
            // ...
            'onNewComment' => array('MyHandlerClass', 'staticMethodName'),
            'onUpdatedComment' => function($event) {
                mail('admin@example.com', 'new comment!', $event->comment->message);
            },
 
 
        ),
        // ...
    ),
    // ...
#8225 report it
jiaming at 2012/05/19 02:44pm
How to trigger events in this extensions...

Any hints about how to trigger events in this extensions...?

I want to send email to the admin when specific model was commented....

I tried to add some trigger codes in the Comment Controller but not works...

Any ideas??

Thanks so much!

#7774 report it
CeBe at 2012/04/17 04:20pm
$model->refresh helps

@p0rsche: you have to refresh the model after saving with CTimeStampBehavior like it is discussed here.

Leave a comment

Please to leave your comment.

Create extension