├── .travis.yml ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Controllers │ ├── CommentController.php │ └── LikeController.php ├── Facade │ └── LaravelLikeComment.php ├── LikeCommentServiceProvider.php ├── Models │ ├── Comment.php │ ├── Like.php │ └── TotalLike.php ├── config │ └── laravelLikeComment.php ├── migrations │ ├── 2016_08_17_203844_create_laravellikecommet_comments_table.php │ ├── 2016_08_18_152344_create_laravellikecomment_total_likes_table.php │ └── 2016_08_18_152401_create_laravellikecomment_likes_table.php ├── public │ └── assets │ │ ├── css │ │ └── style.css │ │ └── js │ │ └── script.js ├── routes.php └── views │ ├── comment.blade.php │ └── like.blade.php └── tests ├── PackageTestCase └── Test.php /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | 6 | env: 7 | - LARAVEL_VERSION=5.5.* 8 | 9 | matrix: 10 | fast_finish: true 11 | 12 | before_script: 13 | - travis_retry composer self-update 14 | - travis_retry composer install --prefer-source --no-interaction 15 | - if [ "$LARAVEL_VERSION" != "" ]; then composer require --dev "laravel/laravel:${LARAVEL_VERSION}" --no-update; fi; 16 | - composer update 17 | 18 | script: 19 | - phpunit 20 | 21 | notifications: 22 | email: false -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Risul Islam risul321@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/risul/laravel-like-comment/v/stable)](https://packagist.org/packages/risul/laravel-like-comment) 2 | [![Total Downloads](https://poser.pugx.org/risul/laravel-like-comment/downloads)](https://packagist.org/packages/risul/laravel-like-comment) 3 | [![License](https://poser.pugx.org/risul/laravel-like-comment/license)](https://packagist.org/packages/risul/laravel-like-comment) 4 | 5 | Laravel like comment 6 | ===================== 7 | 8 | Laravel Like Comment is a lightweight and developer-friendly package that makes it easy to add "like" and "comment" features to any Laravel model. Whether you're building a blog, forum, or social platform, this package handles the heavy lifting so you can focus on your core application. 9 | 10 | It supports polymorphic relationships out of the box, allowing you to attach likes and comments to multiple models effortlessly. With clean API methods and customizable migrations, it's built to be both powerful and flexible. 11 | 12 | Perfect for projects that need quick social interaction features without reinventing the wheel. 13 | 14 | ## Features 15 | * Like 16 | * Dislike 17 | * Comment 18 | * Comment voting 19 | * User avatar in comment 20 | 21 | ## Demo 22 | [Try it](http://risul.herokuapp.com/laravel-like-comment) 23 | 24 | [Watch](https://www.youtube.com/watch?v=06kcpsnN-bo) 25 | 26 | ## Installation 27 | 28 | Run 29 | ```bash 30 | composer require risul/laravel-like-comment 31 | ``` 32 | 33 | ## Configuration 34 | Add 35 | ``` 36 | risul\LaravelLikeComment\LikeCommentServiceProvider::class 37 | ``` 38 | in your ```service providerr``` list. 39 | 40 | To copy views and migrations run 41 | ``` 42 | php artisan vendor:publish 43 | ``` 44 | 45 | Run 46 | ``` 47 | php artisan migrate 48 | ``` 49 | It will create like and comment table. 50 | 51 | Add this style links to your view head 52 | ```html 53 | 54 | 55 | 56 | 57 | 58 | ``` 59 | 60 | Add jquery and script 61 | ```html 62 | 63 | 64 | ``` 65 | 66 | 67 | In `config/laravelLikeComment.php` add user model path 68 | ```php 69 | 'userModel' => 'App\User' 70 | ``` 71 | 72 | Add following code in your user model 73 | ```php 74 | /** 75 | * Return the user attributes. 76 | 77 | * @return array 78 | */ 79 | public static function getAuthor($id) 80 | { 81 | $user = self::find($id); 82 | return [ 83 | 'id' => $user->id, 84 | 'name' => $user->name, 85 | 'email' => $user->email, 86 | 'url' => '', // Optional 87 | 'avatar' => 'gravatar', // Default avatar 88 | 'admin' => $user->role === 'admin', // bool 89 | ]; 90 | } 91 | ``` 92 | 93 | ## Usage 94 | Add this line at where you want to integrate Like 95 | ```php 96 | @include('laravelLikeComment::like', ['like_item_id' => 'image_31']) 97 | ``` 98 | `like_item_id:` This is the id of the item,page or model for which the like will be used 99 | 100 | Add this line where you want to integrate Comment 101 | ```php 102 | @include('laravelLikeComment::comment', ['comment_item_id' => 'video_12']) 103 | ``` 104 | ```comment_item_id:``` This is the id of the item, page, or model for which the comment will be used 105 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "risul/laravel-like-comment", 3 | "description": "Ajax based site wide like and comment system for laravel", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": ["laravel", "like", "comment", "ajax"], 7 | "authors": [ 8 | { 9 | "name": "risul", 10 | "email": "risul321@gmail.com" 11 | } 12 | ], 13 | "minimum-stability": "stable", 14 | "require": {}, 15 | "autoload": { 16 | "psr-4": { 17 | "risul\\LaravelLikeComment\\": "src/" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | 20 | 21 | ./app 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/Controllers/CommentController.php: -------------------------------------------------------------------------------- 1 | id; 31 | $parent = $request->parent; 32 | $commentBody = $request->comment; 33 | $itemId = $request->item_id; 34 | 35 | $user = self::getUser($userId); 36 | $userPic = $user['avatar']; 37 | if($userPic == 'gravatar'){ 38 | $hash = md5(strtolower(trim($user['email']))); 39 | $userPic = "http://www.gravatar.com/avatar/$hash?d=identicon"; 40 | } 41 | 42 | $comment = new Comment; 43 | $comment->user_id = $userId; 44 | $comment->parent_id = $parent; 45 | $comment->item_id = $itemId; 46 | $comment->comment = $commentBody; 47 | 48 | $comment->save(); 49 | 50 | $id = $comment->id; 51 | return response()->json(['flag' => 1, 'id' => $id, 'comment' => $commentBody, 'item_id' => $itemId, 'userName' => $user['name'], 'userPic' => $userPic]); 52 | // dd($comment); 53 | } 54 | 55 | /** 56 | * undocumented function 57 | * 58 | * @return void 59 | * @author 60 | **/ 61 | public static function viewLike($id){ 62 | echo view('laravelLikeComment::like') 63 | ->with('like_item_id', $id); 64 | } 65 | 66 | /** 67 | * undocumented function 68 | * 69 | * @return void 70 | * @author 71 | **/ 72 | public static function getComments($itemId){ 73 | $comments = Comment::where('item_id', $itemId)->orderBy('parent_id', 'asc')->get(); 74 | 75 | foreach ($comments as $comment){ 76 | $userId = $comment->user_id; 77 | $user = self::getUser($userId); 78 | $comment->name = $user['name']; 79 | $comment->email = $user['email']; 80 | $comment->url = $user['url']; 81 | 82 | if($user['avatar'] == 'gravatar'){ 83 | $hash = md5(strtolower(trim($user['email']))); 84 | $comment->avatar = "http://www.gravatar.com/avatar/$hash?d=identicon"; 85 | } else { 86 | $comment->avatar = $user['avatar']; 87 | } 88 | } 89 | 90 | return $comments; 91 | } 92 | 93 | /** 94 | * undocumented function 95 | * 96 | * @return void 97 | * @author 98 | **/ 99 | public static function getUser($userId){ 100 | $userModel = config('laravelLikeComment.userModel'); 101 | return $userModel::getAuthor($userId); 102 | } 103 | 104 | /** 105 | * Delete Comment 106 | * 107 | * @return json [] 108 | * @author risul3 109 | **/ 110 | public function delete($id) 111 | { 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Controllers/LikeController.php: -------------------------------------------------------------------------------- 1 | json(['flag' => 0]); 36 | } 37 | 38 | /* Prepare data */ 39 | $userId = Auth::user()->id; 40 | $itemId = $request->item_id; 41 | $vote = $request->vote; 42 | 43 | /* Get item's current like, dislike total */ 44 | $totalLike = TotalLike::where('item_id', $itemId)->first(); 45 | 46 | /* Get user's vote on this item */ 47 | $like = Like::where(['user_id' => $userId, 'item_id' => $itemId])->first(); 48 | 49 | /* Check if users vote on this item exists */ 50 | if ($like != null) { 51 | if ($like->vote == 1) { // if previous vote was like 52 | $totalLike->total_like--; // decrease item's total like by 1 53 | $totalLike->total_like = $totalLike->total_like == null ? 0 : $totalLike->total_like; // set 0 if total like is null 54 | if ($vote == 1) { // if current vote is like 55 | $vote = 0; // previous vote and current vote is same so discarde vote 56 | } 57 | } else if ($like->vote == -1) { // if previous vote was dislike 58 | $totalLike->total_dislike--; // decrease item's total like by 1 59 | $totalLike->total_dislike = $totalLike->total_dislike == null ? 0 : $totalLike->total_dislike; // set 0 if total dislike is null 60 | if ($vote == -1) { // if current vote is dislike 61 | $vote = 0; // previous vote and current vote is same so discarde vote 62 | } 63 | } 64 | } else { 65 | $like = new Like; // create new like object if previous vote not exists 66 | } 67 | 68 | /* Update vote data */ 69 | $like->user_id = $userId; 70 | $like->item_id = $itemId; 71 | $like->vote = $vote; 72 | 73 | if ($vote == 1) { 74 | $totalLike->total_like++; // increase total like if vote is like 75 | } else if ($vote == -1) { 76 | $totalLike->total_dislike++; // increase total dislike if vote is dislike 77 | } 78 | 79 | $like->save(); // save like 80 | $totalLike->save(); //save total like,dislike 81 | 82 | return response()->json(['flag' => 1, 'vote' => $vote, 'totalLike' => $totalLike->total_like, 'totalDislike' => $totalLike->total_dislike]); 83 | } 84 | 85 | /** 86 | * undocumented function 87 | * 88 | * @return void 89 | * @author 90 | **/ 91 | public static function getLikeViewData($itemId) 92 | { 93 | $totalCount = TotalLike::where('item_id', $itemId)->first(); 94 | if($totalCount == NULL) { 95 | $totalCount = new TotalLike; 96 | $totalCount->item_id = $itemId; 97 | $totalCount->total_like = 0; 98 | $totalCount->total_dislike = 0; 99 | 100 | $totalCount->save(); 101 | } 102 | 103 | $yourVote = 0; // 0 = Not voted, 1 = Liked, -1 = Disliked 104 | if (Auth::check()) { 105 | $checkYourVote = \risul\LaravelLikeComment\Models\Like::where([ 106 | 'user_id' => Auth::user()->id, 107 | 'item_id' => $itemId 108 | ])->first(); 109 | if ($checkYourVote != NULL) { 110 | $yourVote = $checkYourVote->vote; 111 | } 112 | } 113 | 114 | $likeDisabled = ""; 115 | if (!Auth::check()) { 116 | $likeDisabled = "disabled"; 117 | } 118 | 119 | $likeIconOutlined = $yourVote == 1 ? "" : "outline"; 120 | $dislikeIconOutlined = $yourVote == -1 ? "" : "outline"; 121 | 122 | return [ 123 | $itemId.'likeDisabled' => $likeDisabled, 124 | $itemId.'likeIconOutlined' => $likeIconOutlined, 125 | $itemId.'dislikeIconOutlined' => $dislikeIconOutlined, 126 | $itemId.'total_like' => $totalCount->total_like, 127 | $itemId.'total_dislike' => $totalCount->total_dislike, 128 | ]; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Facade/LaravelLikeComment.php: -------------------------------------------------------------------------------- 1 | publishes([ 17 | __DIR__.'/views' => resource_path('views/vendor/laravelLikeComment'), 18 | __DIR__.'/migrations' => database_path('migrations'), 19 | __DIR__.'/public/assets' => public_path('vendor/laravelLikeComment'), 20 | __DIR__.'/config/laravelLikeComment.php' => config_path('laravelLikeComment.php') 21 | ]); 22 | } 23 | 24 | /** 25 | * Register the application services. 26 | * 27 | * @return void 28 | */ 29 | public function register() 30 | { 31 | // Route 32 | include __DIR__.'/routes.php'; 33 | 34 | $this->app->singleton('LaravelLikeComment', function ($app) { return new LaravelLikeComment; }); 35 | 36 | // Config 37 | $this->mergeConfigFrom( __DIR__.'/config/laravelLikeComment.php', 'laravelLikeComment'); 38 | 39 | // View 40 | $this->loadViewsFrom(__DIR__.'/views', 'laravelLikeComment'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Models/Comment.php: -------------------------------------------------------------------------------- 1 | 'App\User' 4 | ]; 5 | -------------------------------------------------------------------------------- /src/migrations/2016_08_17_203844_create_laravellikecommet_comments_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->integer('user_id'); 18 | $table->integer('parent_id'); 19 | $table->string('item_id'); // ModelName_modelId 20 | $table->string('comment'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::drop('laravellikecomment_comments'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/migrations/2016_08_18_152344_create_laravellikecomment_total_likes_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('item_id'); // ModelName_modelId 18 | $table->integer('total_like'); 19 | $table->integer('total_dislike'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('laravellikecomment_like_totals'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/migrations/2016_08_18_152401_create_laravellikecomment_likes_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->integer('user_id'); 18 | $table->string('item_id'); // ModelName_modelId 19 | $table->smallInteger('vote'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('laravellikecomment_likes'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/public/assets/css/style.css: -------------------------------------------------------------------------------- 1 | .laravelLike-icon{ 2 | cursor: pointer; 3 | } 4 | .laravelLike-icon.up{ 5 | color: #27ae61; 6 | } 7 | .laravelLike-icon.down{ 8 | color: #c0392b; 9 | margin-left: 10px !important; 10 | } 11 | .comment{ 12 | display: none; 13 | } -------------------------------------------------------------------------------- /src/public/assets/js/script.js: -------------------------------------------------------------------------------- 1 | $('.laravelLike-icon').on('click', function(){ 2 | if($(this).hasClass('disabled')) 3 | return false; 4 | 5 | var item_id = $(this).data('item-id'); 6 | var vote = $(this).data('vote'); 7 | 8 | $.ajax({ 9 | method: "get", 10 | url: "/laravellikecomment/like/vote", 11 | data: {item_id: item_id, vote: vote}, 12 | dataType: "json" 13 | }) 14 | .done(function(msg){ 15 | if(msg.flag == 1){ 16 | if(msg.vote == 1){ 17 | $('#'+item_id+'-like').removeClass('outline'); 18 | $('#'+item_id+'-dislike').addClass('outline'); 19 | } 20 | else if(msg.vote == -1){ 21 | $('#'+item_id+'-dislike').removeClass('outline'); 22 | $('#'+item_id+'-like').addClass('outline'); 23 | } 24 | else if(msg.vote == 0){ 25 | $('#'+item_id+'-like').addClass('outline'); 26 | $('#'+item_id+'-dislike').addClass('outline'); 27 | } 28 | $('#'+item_id+'-total-like').text(msg.totalLike == null ? 0 : msg.totalLike); 29 | $('#'+item_id+'-total-dislike').text(msg.totalDislike == null ? 0 : msg.totalDislike); 30 | } 31 | }) 32 | .fail(function(msg){ 33 | alert(msg); 34 | }); 35 | }); 36 | 37 | 38 | $(document).on('click', '.reply-button', function(){ 39 | if($(this).hasClass("disabled")) 40 | return false; 41 | var toggle = $(this).data('toggle'); 42 | $("#"+toggle).fadeToggle('normal'); 43 | }); 44 | 45 | $(document).on('submit', '.laravelComment-form', function(){ 46 | var thisForm = $(this); 47 | var parent = $(this).data('parent'); 48 | var item_id = $(this).data('item'); 49 | var comment = $('textarea#'+parent+'-textarea').val(); 50 | 51 | $.ajax({ 52 | method: "get", 53 | url: "/laravellikecomment/comment/add", 54 | data: {parent: parent, comment: comment, item_id: item_id}, 55 | dataType: "json" 56 | }) 57 | .done(function(msg){ 58 | $(thisForm).toggle('normal'); 59 | var newComment = '
'+msg.userName+'
'+msg.comment+'
'; 60 | $('#'+item_id+'-comment-'+parent).prepend(newComment); 61 | $('textarea#'+parent+'-textarea').val(''); 62 | }) 63 | .fail(function(msg){ 64 | alert(msg); 65 | }); 66 | 67 | return false; 68 | }); 69 | 70 | 71 | $(document).on('click', '#showComment', function(){ 72 | var show = $(this).data("show-comment"); 73 | $('.show-'+$(this).data("item-id")+'-'+show).fadeIn('normal'); 74 | $(this).data("show-comment", show+1); 75 | $(this).text("Show more"); 76 | }); 77 | 78 | 79 | $(document).on('click', '#write-comment', function(){ 80 | $($(this).data("form")).show(); 81 | }); 82 | 83 | -------------------------------------------------------------------------------- /src/routes.php: -------------------------------------------------------------------------------- 1 | 'risul\LaravelLikeComment\Controllers', 'prefix'=>'laravellikecomment', 'middleware' => 'web'], function (){ 15 | Route::group(['middleware' => 'auth'], function (){ 16 | Route::get('/like/vote', 'LikeController@vote'); 17 | Route::get('/comment/add', 'CommentController@add'); 18 | }); 19 | }); -------------------------------------------------------------------------------- /src/views/comment.blade.php: -------------------------------------------------------------------------------- 1 | 7 |
8 |

Comments

9 |
10 | 11 | 20 | id] = 1; 25 | $GLOBALS['commentClass']++; 26 | ?> 27 |
28 | 29 | 30 | 31 |
32 | {{ $comment->name }} 33 | 36 |
37 | {{ $comment->comment }} 38 |
39 |
40 | Reply 41 |
42 | {{ \risul\LaravelLikeComment\Controllers\CommentController::viewLike('comment-'.$comment->id) }} 43 | 52 |
53 |
54 | parent_id == $comment->id && !isset($GLOBALS['commentVisit'][$child->id])){ 57 | dfs($comments, $child); 58 | } 59 | } 60 | echo "
"; 61 | echo "
"; 62 | } 63 | 64 | $comments = \risul\LaravelLikeComment\Controllers\CommentController::getComments($comment_item_id); 65 | foreach ($comments as $comment) { 66 | if(!isset($GLOBALS['commentVisit'][$comment->id])){ 67 | dfs($comments, $comment); 68 | } 69 | } 70 | ?> 71 |
72 | 73 |
74 | -------------------------------------------------------------------------------- /src/views/like.blade.php: -------------------------------------------------------------------------------- 1 | 4 |
5 | 9 | 10 | {{ $data[$like_item_id.'total_like'] }} 11 | 15 | 16 | {{ $data[$like_item_id.'total_dislike'] }} 17 |
18 | -------------------------------------------------------------------------------- /tests/PackageTestCase: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } --------------------------------------------------------------------------------