├── .gitignore
├── .github
└── FUNDING.yml
├── src
├── Events
│ ├── CommentCreated.php
│ ├── CommentDeleted.php
│ └── CommentUpdated.php
├── routes.php
├── CommentController.php
├── CommentControllerInterface.php
├── Commenter.php
├── Commentable.php
├── CommentPolicy.php
├── WebCommentController.php
├── Comment.php
├── ServiceProvider.php
└── CommentService.php
├── resources
├── lang
│ ├── ar
│ │ └── comments.php
│ ├── en
│ │ └── comments.php
│ └── ru
│ │ └── comments.php
└── views
│ ├── _form.blade.php
│ ├── components
│ └── comments.blade.php
│ └── _comment.blade.php
├── LICENSE.md
├── composer.json
├── migrations
└── 2018_06_30_113500_create_comments_table.php
├── config
└── comments.php
├── UPGRADE.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.lock
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | patreon: laravelista
4 |
--------------------------------------------------------------------------------
/src/Events/CommentCreated.php:
--------------------------------------------------------------------------------
1 | comment = $comment;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Events/CommentDeleted.php:
--------------------------------------------------------------------------------
1 | comment = $comment;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Events/CommentUpdated.php:
--------------------------------------------------------------------------------
1 | comment = $comment;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/routes.php:
--------------------------------------------------------------------------------
1 | name('comments.store');
7 | Route::delete('comments/{comment}', Config::get('comments.controller') . '@destroy')->name('comments.destroy');
8 | Route::put('comments/{comment}', Config::get('comments.controller') . '@update')->name('comments.update');
9 | Route::post('comments/{comment}', Config::get('comments.controller') . '@reply')->name('comments.reply');
--------------------------------------------------------------------------------
/src/CommentController.php:
--------------------------------------------------------------------------------
1 | middleware('web');
14 |
15 | if (Config::get('comments.guest_commenting') == true) {
16 | $this->middleware('auth')->except('store');
17 | $this->middleware(ProtectAgainstSpam::class)->only('store');
18 | } else {
19 | $this->middleware('auth');
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/CommentControllerInterface.php:
--------------------------------------------------------------------------------
1 | morphMany(Config::get('comments.model'), 'commenter');
19 | }
20 |
21 | /**
22 | * Returns only approved comments that this user has made.
23 | */
24 | public function approvedComments()
25 | {
26 | return $this->morphMany(Config::get('comments.model'), 'commenter')->where('approved', true);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/resources/lang/ar/comments.php:
--------------------------------------------------------------------------------
1 | 'لا توجد تعليقات حتي الآن.',
5 | 'authentication_required' => 'تسجيل الدخول مطلوب',
6 | 'you_must_login_to_post_a_comment' => 'يجب عليك تسجيل الدخول لإضافة تعليق.',
7 | 'log_in' => 'تسجيل الدخول',
8 | 'reply' => 'رد',
9 | 'edit' => 'تعديل',
10 | 'delete' => 'حذف',
11 | 'edit_comment' => 'تعديل التعليق',
12 | 'update_your_message_here' => 'حدّث تعليقك:',
13 | 'update' => 'تحديث',
14 | 'cancel' => 'إلغاء',
15 | 'reply_to_comment' => 'الرد علي التعليق',
16 | 'markdown_cheatsheet' => 'Markdown أوامر.',
17 | 'submit' => 'إرسال',
18 | 'your_message_is_required' => 'محتوي التعليق مطلوب.',
19 | 'enter_your_message_here' => 'أدخل تعليقك:',
20 | 'enter_your_email_here' => 'بريدك الإلكتروني:',
21 | 'enter_your_name_here' => 'اسمك:',
22 | ];
--------------------------------------------------------------------------------
/resources/lang/en/comments.php:
--------------------------------------------------------------------------------
1 | 'There are no comments yet.',
5 | 'authentication_required' => 'Authentication required',
6 | 'you_must_login_to_post_a_comment' => 'You must log in to post a comment.',
7 | 'log_in' => 'Log in',
8 | 'reply' => 'Reply',
9 | 'edit' => 'Edit',
10 | 'delete' => 'Delete',
11 | 'edit_comment' => 'Edit comment',
12 | 'update_your_message_here' => 'Update your message here:',
13 | 'update' => 'Update',
14 | 'cancel' => 'Cancel',
15 | 'reply_to_comment' => 'Reply to comment',
16 | 'markdown_cheatsheet' => 'Markdown cheatsheet.',
17 | 'submit' => 'Submit',
18 | 'your_message_is_required' => 'Your message is required.',
19 | 'enter_your_message_here' => 'Enter your message here:',
20 | 'enter_your_email_here' => 'Enter your email here:',
21 | 'enter_your_name_here' => 'Enter your name here:',
22 | ];
--------------------------------------------------------------------------------
/resources/lang/ru/comments.php:
--------------------------------------------------------------------------------
1 | 'Комментариев пока нет.',
5 | 'authentication_required' => 'Требуется авторизация',
6 | 'you_must_login_to_post_a_comment' => 'Войдите, чтобы оставить комментарий.',
7 | 'log_in' => 'Войти',
8 | 'reply' => 'Ответить',
9 | 'edit' => 'Редактировать',
10 | 'delete' => 'Удалить',
11 | 'edit_comment' => 'Редактирование комментария',
12 | 'update_your_message_here' => 'Отредактируйте свой комментарий здесь:',
13 | 'update' => 'Отредактировать',
14 | 'cancel' => 'Отменить',
15 | 'reply_to_comment' => 'Ответ на комментарий',
16 | 'markdown_cheatsheet' => 'Markdown разметка',
17 | 'submit' => 'Отправить',
18 | 'your_message_is_required' => 'Поле обязательно для заполнения.',
19 | 'enter_your_message_here' => 'Введите свой комментарий здесь:',
20 | 'enter_your_email_here' => 'Введите свою почту здесь:',
21 | 'enter_your_name_here' => 'Введите свое имя здесь:',
22 | ];
23 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2020 Mario Bašić
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravelista/comments",
3 | "description": "Comments for Laravel.",
4 | "keywords": [
5 | "laravel",
6 | "comments"
7 | ],
8 | "license": "MIT",
9 | "authors": [
10 | {
11 | "name": "Mario Bašić",
12 | "email": "mario@laravelista.hr",
13 | "homepage": "https://laravelista.hr"
14 | }
15 | ],
16 | "require": {
17 | "php": "^8.0",
18 | "erusev/parsedown": "^1.7",
19 | "illuminate/database": "^9.0",
20 | "illuminate/http": "^9.0",
21 | "illuminate/pagination": "^9.0",
22 | "illuminate/routing": "^9.0",
23 | "illuminate/queue": "^9.0",
24 | "illuminate/support": "^9.0",
25 | "spatie/laravel-honeypot": "^4.1"
26 | },
27 | "autoload": {
28 | "psr-4": {
29 | "Laravelista\\Comments\\": "src/"
30 | }
31 | },
32 | "extra": {
33 | "laravel": {
34 | "providers": [
35 | "Laravelista\\Comments\\ServiceProvider"
36 | ]
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Commentable.php:
--------------------------------------------------------------------------------
1 | comments as $comment) {
22 | $comment->delete();
23 | }
24 | });
25 | }
26 |
27 | /**
28 | * Returns all comments for this model.
29 | */
30 | public function comments()
31 | {
32 | return $this->morphMany(Config::get('comments.model'), 'commentable');
33 | }
34 |
35 | /**
36 | * Returns only approved comments for this model.
37 | */
38 | public function approvedComments()
39 | {
40 | return $this->morphMany(Config::get('comments.model'), 'commentable')->where('approved', true);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/CommentPolicy.php:
--------------------------------------------------------------------------------
1 | getKey() == $comment->commenter_id;
30 | }
31 |
32 | /**
33 | * Can user update the comment
34 | *
35 | * @param $user
36 | * @param Comment $comment
37 | * @return bool
38 | */
39 | public function update($user, Comment $comment) : bool
40 | {
41 | return $user->getKey() == $comment->commenter_id;
42 | }
43 |
44 | /**
45 | * Can user reply to the comment
46 | *
47 | * @param $user
48 | * @param Comment $comment
49 | * @return bool
50 | */
51 | public function reply($user, Comment $comment) : bool
52 | {
53 | return $user->getKey() != $comment->commenter_id;
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/migrations/2018_06_30_113500_create_comments_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 |
19 | $table->string('commenter_id')->nullable();
20 | $table->string('commenter_type')->nullable();
21 | $table->index(["commenter_id", "commenter_type"]);
22 |
23 | $table->string('guest_name')->nullable();
24 | $table->string('guest_email')->nullable();
25 |
26 | $table->string("commentable_type");
27 | $table->string("commentable_id");
28 | $table->index(["commentable_type", "commentable_id"]);
29 |
30 | $table->text('comment');
31 |
32 | $table->boolean('approved')->default(true);
33 |
34 | $table->unsignedBigInteger('child_id')->nullable();
35 | $table->foreign('child_id')->references('id')->on('comments')->onDelete('cascade');
36 |
37 | $table->softDeletes();
38 | $table->timestamps();
39 | });
40 | }
41 |
42 | /**
43 | * Reverse the migrations.
44 | *
45 | * @return void
46 | */
47 | public function down()
48 | {
49 | Schema::dropIfExists('comments');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/WebCommentController.php:
--------------------------------------------------------------------------------
1 | commentService = $commentService;
19 | }
20 |
21 | /**
22 | * Creates a new comment for given model.
23 | */
24 | public function store(Request $request)
25 | {
26 | $comment = $this->commentService->store($request);
27 |
28 | return Redirect::to(URL::previous() . '#comment-' . $comment->getKey());
29 | }
30 |
31 | /**
32 | * Updates the message of the comment.
33 | */
34 | public function update(Request $request, Comment $comment)
35 | {
36 | $comment = $this->commentService->update($request, $comment);
37 |
38 | return Redirect::to(URL::previous() . '#comment-' . $comment->getKey());
39 | }
40 |
41 | /**
42 | * Deletes a comment.
43 | */
44 | public function destroy(Comment $comment)
45 | {
46 | $this->commentService->destroy($comment);
47 |
48 | return Redirect::back();
49 | }
50 |
51 | /**
52 | * Creates a reply "comment" to a comment.
53 | */
54 | public function reply(Request $request, Comment $comment)
55 | {
56 | $reply = $this->commentService->reply($request, $comment);
57 |
58 | return Redirect::to(URL::previous() . '#comment-' . $reply->getKey());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Comment.php:
--------------------------------------------------------------------------------
1 | 'boolean'
41 | ];
42 |
43 | /**
44 | * The event map for the model.
45 | *
46 | * @var array
47 | */
48 | protected $dispatchesEvents = [
49 | 'created' => CommentCreated::class,
50 | 'updated' => CommentUpdated::class,
51 | 'deleted' => CommentDeleted::class,
52 | ];
53 |
54 | /**
55 | * The user who posted the comment.
56 | */
57 | public function commenter()
58 | {
59 | return $this->morphTo();
60 | }
61 |
62 | /**
63 | * The model that was commented upon.
64 | */
65 | public function commentable()
66 | {
67 | return $this->morphTo();
68 | }
69 |
70 | /**
71 | * Returns all comments that this comment is the parent of.
72 | */
73 | public function children()
74 | {
75 | return $this->hasMany(Config::get('comments.model'), 'child_id');
76 | }
77 |
78 | /**
79 | * Returns the comment to which this comment belongs to.
80 | */
81 | public function parent()
82 | {
83 | return $this->belongsTo(Config::get('comments.model'), 'child_id');
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/resources/views/_form.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | @if($errors->has('commentable_type'))
4 |
5 | {{ $errors->first('commentable_type') }}
6 |
7 | @endif
8 | @if($errors->has('commentable_id'))
9 |
10 | {{ $errors->first('commentable_id') }}
11 |
12 | @endif
13 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/resources/views/components/comments.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | if (isset($approved) and $approved == true) {
3 | $comments = $model->approvedComments;
4 | } else {
5 | $comments = $model->comments;
6 | }
7 | @endphp
8 |
9 | @if($comments->count() < 1)
10 | @lang('comments::comments.there_are_no_comments')
11 | @endif
12 |
13 |
14 | @php
15 | $comments = $comments->sortBy('created_at');
16 |
17 | if (isset($perPage)) {
18 | $page = request()->query('page', 1) - 1;
19 |
20 | $parentComments = $comments->where('child_id', '');
21 |
22 | $slicedParentComments = $parentComments->slice($page * $perPage, $perPage);
23 |
24 | $m = Config::get('comments.model'); // This has to be done like this, otherwise it will complain.
25 | $modelKeyName = (new $m)->getKeyName(); // This defaults to 'id' if not changed.
26 |
27 | $slicedParentCommentsIds = $slicedParentComments->pluck($modelKeyName)->toArray();
28 |
29 | // Remove parent Comments from comments.
30 | $comments = $comments->where('child_id', '!=', '');
31 |
32 | $grouped_comments = new \Illuminate\Pagination\LengthAwarePaginator(
33 | $slicedParentComments->merge($comments)->groupBy('child_id'),
34 | $parentComments->count(),
35 | $perPage
36 | );
37 |
38 | $grouped_comments->withPath(request()->url());
39 | } else {
40 | $grouped_comments = $comments->groupBy('child_id');
41 | }
42 | @endphp
43 | @foreach($grouped_comments as $comment_id => $comments)
44 | {{-- Process parent nodes --}}
45 | @if($comment_id == '')
46 | @foreach($comments as $comment)
47 | @include('comments::_comment', [
48 | 'comment' => $comment,
49 | 'grouped_comments' => $grouped_comments,
50 | 'maxIndentationLevel' => $maxIndentationLevel ?? 3
51 | ])
52 | @endforeach
53 | @endif
54 | @endforeach
55 |
56 |
57 | @isset ($perPage)
58 | {{ $grouped_comments->links() }}
59 | @endisset
60 |
61 | @auth
62 | @include('comments::_form')
63 | @elseif(Config::get('comments.guest_commenting') == true)
64 | @include('comments::_form', [
65 | 'guest_commenting' => true
66 | ])
67 | @else
68 |
69 |
70 |
@lang('comments::comments.authentication_required')
71 |
@lang('comments::comments.you_must_login_to_post_a_comment')
72 |
@lang('comments::comments.log_in')
73 |
74 |
75 | @endauth
76 |
--------------------------------------------------------------------------------
/config/comments.php:
--------------------------------------------------------------------------------
1 | \Laravelista\Comments\Comment::class,
11 |
12 | /**
13 | * You can customize the behaviour of these permissions by
14 | * creating your own and pointing to it here.
15 | */
16 | 'permissions' => [
17 | 'create-comment' => 'Laravelista\Comments\CommentPolicy@create',
18 | 'delete-comment' => 'Laravelista\Comments\CommentPolicy@delete',
19 | 'edit-comment' => 'Laravelista\Comments\CommentPolicy@update',
20 | 'reply-to-comment' => 'Laravelista\Comments\CommentPolicy@reply',
21 | ],
22 |
23 | /**
24 | * The Comment Controller.
25 | * Change this to your own implementation of the CommentController.
26 | * You can use the \Laravelista\Comments\CommentControllerInterface
27 | * or extend the \Laravelista\Comments\CommentController.
28 | */
29 | 'controller' => '\Laravelista\Comments\WebCommentController',
30 |
31 | /**
32 | * Disable/enable the package routes.
33 | * If you want to completely take over the way this package handles
34 | * routes and controller logic, set this to false and provide your
35 | * own routes and controller for comments.
36 | */
37 | 'routes' => true,
38 |
39 | /**
40 | * By default comments posted are marked as approved. If you want
41 | * to change this, set this option to true. Then, all comments
42 | * will need to be approved by setting the `approved` column to
43 | * `true` for each comment.
44 | *
45 | * To see only approved comments use this code in your view:
46 | *
47 | * @comments([
48 | * 'model' => $book,
49 | * 'approved' => true
50 | * ])
51 | *
52 | */
53 | 'approval_required' => false,
54 |
55 | /**
56 | * Set this option to `true` to enable guest commenting.
57 | *
58 | * Visitors will be asked to provide their name and email
59 | * address in order to post a comment.
60 | */
61 | 'guest_commenting' => false,
62 |
63 | /**
64 | * Set this option to `true` to enable soft deleting of comments.
65 | *
66 | * Comments will be soft deleted using laravels "softDeletes" trait.
67 | */
68 | 'soft_deletes' => false,
69 |
70 | /**
71 | * Enable/disable the package provider to load migrations.
72 | * This option might be useful if you use multiple database connections.
73 | */
74 | 'load_migrations' => true,
75 |
76 | /**
77 | * Enable/disable calling Paginator::useBootstrap() in the boot method
78 | * to prevent breaking non bootstrap based Site.
79 | */
80 | 'paginator_use_bootstrap' => true,
81 |
82 | ];
83 |
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadRoutesFrom(__DIR__ . '/routes.php');
24 | }
25 | }
26 |
27 | /**
28 | * If load_migrations config is true (by default it is),
29 | * then load the package migrations, otherwise don't load
30 | * the migrations.
31 | */
32 | protected function loadMigrations()
33 | {
34 | if (Config::get('comments.load_migrations') === true) {
35 | $this->loadMigrationsFrom(__DIR__ . '/../migrations');
36 | }
37 | }
38 |
39 | /**
40 | * If for some reason you want to override the component.
41 | */
42 | protected function includeBladeComponent()
43 | {
44 | Blade::include('comments::components.comments', 'comments');
45 | }
46 |
47 | /**
48 | * Define permission defined in the config.
49 | */
50 | protected function definePermissions()
51 | {
52 | foreach(Config::get('comments.permissions', []) as $permission => $policy) {
53 | Gate::define($permission, $policy);
54 | }
55 | }
56 |
57 | public function boot()
58 | {
59 | $this->loadRoutes();
60 |
61 | $this->loadMigrations();
62 |
63 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'comments');
64 |
65 | $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', 'comments');
66 |
67 | $this->includeBladeComponent();
68 |
69 | $this->definePermissions();
70 |
71 | $this->publishes([
72 | __DIR__.'/../migrations/' => App::databasePath('migrations')
73 | ], 'migrations');
74 |
75 | $this->publishes([
76 | __DIR__ . '/../resources/views' => App::resourcePath('views/vendor/comments'),
77 | ], 'views');
78 |
79 | $this->publishes([
80 | __DIR__ . '/../config/comments.php' => App::configPath('comments.php'),
81 | ], 'config');
82 |
83 | $this->publishes([
84 | __DIR__ . '/../resources/lang' => App::resourcePath('lang/vendor/comments'),
85 | ], 'translations');
86 |
87 | Route::model('comment', Config::get('comments.model'));
88 |
89 | if (Config::get('comments.paginator_use_bootstrap', true)) {
90 | Paginator::useBootstrap();
91 | }
92 | }
93 |
94 | public function register()
95 | {
96 | $this->mergeConfigFrom(
97 | __DIR__ . '/../config/comments.php',
98 | 'comments'
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/CommentService.php:
--------------------------------------------------------------------------------
1 | 'required|string|max:255',
28 | 'guest_email' => 'required|string|email|max:255',
29 | ];
30 | }
31 |
32 | // Merge guest rules, if any, with normal validation rules.
33 | Validator::make($request->all(), array_merge($guest_rules ?? [], [
34 | 'commentable_type' => 'required|string',
35 | 'commentable_id' => 'required|string|min:1',
36 | 'message' => 'required|string'
37 | ]))->validate();
38 |
39 | $model = $request->commentable_type::findOrFail($request->commentable_id);
40 |
41 | $commentClass = Config::get('comments.model');
42 | $comment = new $commentClass;
43 |
44 | if (!Auth::check()) {
45 | $comment->guest_name = $request->guest_name;
46 | $comment->guest_email = $request->guest_email;
47 | } else {
48 | $comment->commenter()->associate(Auth::user());
49 | }
50 |
51 | $comment->commentable()->associate($model);
52 | $comment->comment = $request->message;
53 | $comment->approved = !Config::get('comments.approval_required');
54 | $comment->save();
55 |
56 | return $comment;
57 | }
58 |
59 | /**
60 | * Handles updating the message of the comment.
61 | * @return mixed the configured comment-model
62 | */
63 | public function update(Request $request, Comment $comment)
64 | {
65 | Gate::authorize('edit-comment', $comment);
66 |
67 | Validator::make($request->all(), [
68 | 'message' => 'required|string'
69 | ])->validate();
70 |
71 | $comment->update([
72 | 'comment' => $request->message
73 | ]);
74 |
75 | return $comment;
76 | }
77 |
78 | /**
79 | * Handles deleting a comment.
80 | * @return mixed the configured comment-model
81 | */
82 | public function destroy(Comment $comment): void
83 | {
84 | Gate::authorize('delete-comment', $comment);
85 |
86 | if (Config::get('comments.soft_deletes') == true) {
87 | $comment->delete();
88 | } else {
89 | $comment->forceDelete();
90 | }
91 | }
92 |
93 | /**
94 | * Handles creating a reply "comment" to a comment.
95 | * @return mixed the configured comment-model
96 | */
97 | public function reply(Request $request, Comment $comment)
98 | {
99 | Gate::authorize('reply-to-comment', $comment);
100 |
101 | Validator::make($request->all(), [
102 | 'message' => 'required|string'
103 | ])->validate();
104 |
105 | $commentClass = Config::get('comments.model');
106 | $reply = new $commentClass;
107 | $reply->commenter()->associate(Auth::user());
108 | $reply->commentable()->associate($comment->commentable);
109 | $reply->parent()->associate($comment);
110 | $reply->comment = $request->message;
111 | $reply->approved = !Config::get('comments.approval_required');
112 | $reply->save();
113 |
114 | return $reply;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/UPGRADE.md:
--------------------------------------------------------------------------------
1 | # Upgrading from older versions
2 |
3 | Be sure to update your version of the config file to match the latest version of the package.
4 |
5 | ## TODO
6 |
7 | - [ ] document new config option `paginator_use_bootstrap`.
8 |
9 |
10 | ## Support for soft deletes (`3.4.0`)
11 |
12 | If you are updating an already existing database table `comments` and want support for soft deletes **(new installations get this by default)**, then create a new migration with `php artisan make:migration add_soft_delete_column_to_comments_table` and paste this code inside:
13 |
14 | ```
15 | softDeletes();
32 | });
33 | }
34 | }
35 | ```
36 |
37 | Finally, run `php artisan migrate`.
38 |
39 |
40 | ## Support for guest commenting
41 |
42 | If you are updating an already existing database table `comments` and want support for guest commenting **(new installations get this by default)**, then create a new migration with `php artisan make:migration add_guest_commenting_columns_to_comments_table` and paste this code inside:
43 |
44 | ```
45 | string('commenter_id')->nullable()->change();
62 | $table->string('commenter_type')->nullable()->change();
63 |
64 | $table->string('guest_name')->nullable();
65 | $table->string('guest_email')->nullable();
66 | });
67 | }
68 | }
69 | ```
70 |
71 | Finally, run `php artisan migrate`.
72 |
73 |
74 | ## Support for approving comments
75 |
76 | If you are updating an already existing database table `comments` and want support for approving comments **(new installations get this by default)**, then create a new migration with `php artisan make:migration add_approved_column_to_comments_table` and paste this code inside:
77 |
78 | ```
79 | boolean('approved')->default(true)->nullable();
96 | });
97 | }
98 | }
99 | ```
100 |
101 | Finally, run `php artisan migrate`.
102 |
103 |
104 | ## Support for multiple user models
105 |
106 | If you are updating an already existing database table `comments` and want support for multiple user models **(new installations get this by default)**, then create a new migration with `php artisan make:migration add_commenter_type_column_to_comments_table` and paste this code inside:
107 |
108 | ```
109 | string('commenter_id')->change();
127 | $table->string('commenter_type')->nullable();
128 | });
129 |
130 | DB::table('comments')->update([
131 | 'commenter_type' => '\App\User'
132 | ]);
133 | }
134 | }
135 | ```
136 |
137 | Then, add `doctrine/dbal` dependency with:
138 |
139 | ```
140 | composer require doctrine/dbal
141 | ```
142 |
143 | Finally, run `php artisan migrate`.
144 |
145 |
146 | ## Support for non-integer IDs
147 |
148 | If you are updating an already existing database table `comments` and want support for non-integer IDs **(new installations get this by default)**, then create a new migration with `php artisan make:migration allow_commentable_id_to_be_string` and paste this code inside:
149 |
150 | ```
151 | string('commentable_id')->change();
168 | });
169 | }
170 | }
171 | ```
172 |
173 | Then, add `doctrine/dbal` dependency with:
174 |
175 | ```
176 | composer require doctrine/dbal
177 | ```
178 |
179 | Finally, run `php artisan migrate`.
180 |
181 |
--------------------------------------------------------------------------------
/resources/views/_comment.blade.php:
--------------------------------------------------------------------------------
1 | @inject('markdown', 'Parsedown')
2 | @php
3 | // TODO: There should be a better place for this.
4 | $markdown->setSafeMode(true);
5 | @endphp
6 |
7 |
111 |
112 | {{-- Recursion for children --}}
113 | @if($grouped_comments->has($comment->getKey()) && $indentationLevel > $maxIndentationLevel)
114 | {{-- TODO: Don't repeat code. Extract to a new file and include it. --}}
115 | @foreach($grouped_comments[$comment->getKey()] as $child)
116 | @include('comments::_comment', [
117 | 'comment' => $child,
118 | 'grouped_comments' => $grouped_comments
119 | ])
120 | @endforeach
121 | @endif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Comments
2 |
3 | Comments is a Laravel package. With it you can easily implement native comments for your application.
4 |
5 | [](https://www.patreon.com/laravelista)
6 |
7 |
8 | ## Overview
9 |
10 | This package can be used to comment on any model you have in your application.
11 |
12 | All comments are stored in a single table with a polymorphic relation for content and a polymorphic relation for the user who posted the comment.
13 |
14 |
15 | ### Features
16 |
17 | - View comments
18 | - Create comments
19 | - Delete comments
20 | - Edit comments
21 | - Reply to comments
22 | - Authorization rules
23 | - Support localization
24 | - Dispatch events
25 | - Route, Controller, Comment, Migration & View customizations
26 | - Support for non-integer IDs
27 | - Support for multiple User models
28 | - Solved N+1 query problem
29 | - Comment approval (opt-in)
30 | - Guest commenting (opt-in)
31 | - Pagination (opt-in)
32 | - Soft deletes (opt-in)
33 | - Works with custom ID columns
34 | - Optionally load package migrations [NEW]
35 | - Configure maximum indentation level [NEW]
36 |
37 |
38 | ### Screenshots
39 |
40 | Here are a few screenshots.
41 |
42 | No comments & guest:
43 |
44 | 
45 |
46 | No comments & logged in:
47 |
48 | 
49 |
50 | One comment:
51 |
52 | 
53 |
54 | One comment edit form:
55 |
56 | 
57 |
58 | Two comments from different users:
59 |
60 | 
61 |
62 |
63 | ### Tutorials & articles
64 |
65 | I plan to expand this chapter with more tutorials and articles. If you write something about this package let me know, so that I can update this chapter.
66 |
67 | **Screencasts:**
68 |
69 | - [Adding comments to your Laravel application](https://www.youtube.com/watch?v=YhA0CSX1HIg) by Andre Madarang.
70 |
71 |
72 | ## Installation
73 |
74 | From the command line:
75 |
76 | ```bash
77 | composer require laravelista/comments
78 | ```
79 |
80 |
81 | ### Run migrations
82 |
83 | We need to create the table for comments.
84 |
85 | ```bash
86 | php artisan migrate
87 | ```
88 |
89 |
90 | ### Add Commenter trait to your User model
91 |
92 | Add the `Commenter` trait to your User model so that you can retrieve the comments for a user:
93 |
94 | ```php
95 | use Laravelista\Comments\Commenter;
96 |
97 | class User extends Authenticatable
98 | {
99 | use Notifiable, Commenter;
100 | }
101 | ```
102 |
103 |
104 | ### Add Commentable trait to models
105 |
106 | Add the `Commentable` trait to the model for which you want to enable comments for:
107 |
108 | ```php
109 | use Laravelista\Comments\Commentable;
110 |
111 | class Product extends Model
112 | {
113 | use Commentable;
114 | }
115 | ```
116 |
117 |
118 | ### Publish Config & configure (optional)
119 |
120 | Publish the config file (optional):
121 |
122 | ```bash
123 | php artisan vendor:publish --provider="Laravelista\Comments\ServiceProvider" --tag=config
124 | ```
125 |
126 |
127 | ### Publish views (customization)
128 |
129 | The default UI is made for Bootstrap 4, but you can change it however you want.
130 |
131 | ```bash
132 | php artisan vendor:publish --provider="Laravelista\Comments\ServiceProvider" --tag=views
133 | ```
134 |
135 |
136 | ### Publish Migrations (customization)
137 |
138 | You can publish migration to allow you to have more control over your table
139 |
140 | ```bash
141 | php artisan vendor:publish --provider="Laravelista\Comments\ServiceProvider" --tag=migrations
142 | ```
143 |
144 |
145 | ### Publish translations (customization)
146 |
147 | The package currently only supports English, but I am open to PRs for other languages.
148 |
149 | ```bash
150 | php artisan vendor:publish --provider="Laravelista\Comments\ServiceProvider" --tag=translations
151 | ```
152 |
153 |
154 | ## Usage
155 |
156 | In the view where you want to display comments, place this code and modify it:
157 |
158 | ```
159 | @comments(['model' => $book])
160 | ```
161 |
162 | In the example above we are setting the `commentable_type` to the class of the book. We are also passing the `commentable_id` the `id` of the book so that we know to which book the comments relate to. Behind the scenes, the package detects the currently logged in user if any.
163 |
164 | If you open the page containing the view where you have placed the above code, you should see a working comments form.
165 |
166 |
167 | ### View only approved comments
168 |
169 | To view only approved comments, use this code:
170 |
171 | ```
172 | @comments([
173 | 'model' => $book,
174 | 'approved' => true
175 | ])
176 | ```
177 |
178 |
179 | ### Paginate comments
180 |
181 | Pagination paginates by top level comments only, meaning that if you specify the number of comments per page to be 1, and that one comment has 100 replies, it will display that one comment and all of its replies.
182 |
183 | It was not possible to do it any other way, because if I paginate by all comments (parent and child) you will end up with blank pages since the comments components loops parent comments first and then uses recursion for replies.
184 |
185 | To use pagination, use this code:
186 |
187 | ```
188 | @comments([
189 | 'model' => $user,
190 | 'perPage' => 2
191 | ])
192 | ```
193 |
194 | Replace `2` with any number you want.
195 |
196 | ### Configure maximum indentation level
197 |
198 | By default the replies go up to level three. After that they are "mashed" at that level.
199 |
200 | ```
201 | - 0
202 | - 1
203 | - 2
204 | - 3
205 | ```
206 |
207 | You can configure the maximum indentation level like so:
208 |
209 | ```
210 | @comments([
211 | 'model' => $user,
212 | 'maxIndentationLevel' => 1
213 | ])
214 | ```
215 |
216 |
217 | ## Events
218 |
219 | This package fires events to let you know when things happen.
220 |
221 | - `Laravelista\Comments\Events\CommentCreated`
222 | - `Laravelista\Comments\Events\CommentUpdated`
223 | - `Laravelista\Comments\Events\CommentDeleted`
224 |
225 |
226 | ## REST API
227 |
228 | To change the controller or the routes, see the config.
229 |
230 | ```
231 | Route::post('comments', '\Laravelista\Comments\CommentController@store')->name('comments.store');
232 | Route::delete('comments/{comment}', '\Laravelista\Comments\CommentController@destroy')->name('comments.destroy');
233 | Route::put('comments/{comment}', '\Laravelista\Comments\CommentController@update')->name('comments.update');
234 | Route::post('comments/{comment}', '\Laravelista\Comments\CommentController@reply')->name('comments.reply');
235 | ```
236 |
237 |
238 | ### POST `/comments`
239 |
240 | Request data:
241 |
242 | ```
243 | 'commentable_type' => 'required|string',
244 | 'commentable_id' => 'required|string|min:1',
245 | 'message' => 'required|string'
246 | ```
247 |
248 |
249 | ### PUT `/comments/{comment}`
250 |
251 | - {comment} - Comment ID.
252 |
253 | Request data:
254 |
255 | ```
256 | 'message' => 'required|string'
257 | ```
258 |
259 |
260 | ### POST `/comments/{comment}`
261 |
262 | - {comment} - Comment ID.
263 |
264 | Request data:
265 |
266 | ```
267 | 'message' => 'required|string'
268 | ```
269 |
270 |
271 | ## Upgrading from older versions (troubleshoot)
272 |
273 | Before creating an issue, read [this](./UPGRADE.md).
274 |
275 |
276 | ## Sponsors & Backers
277 |
278 | I would like to extend my thanks to the following sponsors & backers for funding my open-source journey. If you are interested in becoming a sponsor or backer, please visit the [Backers page](https://mariobasic.com/backers).
279 |
280 |
281 | ## Contributing
282 |
283 | Thank you for considering contributing to Comments! The contribution guide can be found [Here](https://mariobasic.com/contributing).
284 |
285 |
286 | ## Code of Conduct
287 |
288 | In order to ensure that the open-source community is welcoming to all, please review and abide by the [Code of Conduct](https://mariobasic.com/code-of-conduct).
289 |
290 |
291 | ## License
292 |
293 | Comments is open-source software licensed under the [MIT license](https://opensource.org/licenses/MIT).
294 |
--------------------------------------------------------------------------------
{{ $comment->commenter->name ?? $comment->guest_name }} - {{ $comment->created_at->diffForHumans() }}
11 |{{-- Margin bottom --}} 89 | 90 | 97 | 98 | {{-- Recursion for children --}} 99 | @if($grouped_comments->has($comment->getKey()) && $indentationLevel <= $maxIndentationLevel) 100 | {{-- TODO: Don't repeat code. Extract to a new file and include it. --}} 101 | @foreach($grouped_comments[$comment->getKey()] as $child) 102 | @include('comments::_comment', [ 103 | 'comment' => $child, 104 | 'grouped_comments' => $grouped_comments 105 | ]) 106 | @endforeach 107 | @endif 108 | 109 |