From 97ee60cfc9d77ddf95737e72d254541ed7242111 Mon Sep 17 00:00:00 2001 From: Andy Heathershaw Date: Wed, 19 Sep 2018 19:54:59 +0100 Subject: [PATCH] #4: Comments can now be approved and rejected from the front-end gallery --- .../Gallery/PhotoCommentController.php | 140 ++++++++++++++---- app/Photo.php | 16 +- app/PhotoComment.php | 28 ++-- app/Policies/AlbumPolicy.php | 11 ++ app/Policies/PhotoPolicy.php | 11 ++ ...19_190631_add_comment_rejected_columns.php | 34 +++++ database/seeds/PermissionsSeeder.php | 8 + resources/lang/en/gallery.php | 8 +- resources/lang/en/permissions.php | 1 + .../base/partials/album_permissions.blade.php | 1 + .../base/partials/photo_comments.blade.php | 10 +- .../partials/photo_single_comment.blade.php | 8 +- .../photo_single_comment_moderate.blade.php | 17 +++ routes/web.php | 3 + 14 files changed, 243 insertions(+), 53 deletions(-) create mode 100644 database/migrations/2018_09_19_190631_add_comment_rejected_columns.php create mode 100644 resources/views/themes/base/partials/photo_single_comment_moderate.blade.php diff --git a/app/Http/Controllers/Gallery/PhotoCommentController.php b/app/Http/Controllers/Gallery/PhotoCommentController.php index 7c44437..f81c890 100644 --- a/app/Http/Controllers/Gallery/PhotoCommentController.php +++ b/app/Http/Controllers/Gallery/PhotoCommentController.php @@ -7,37 +7,73 @@ use App\Facade\UserConfig; use App\Helpers\DbHelper; use App\Http\Controllers\Controller; use App\Http\Requests\StorePhotoCommentRequest; +use App\Photo; use App\PhotoComment; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; use Illuminate\Validation\ValidationException; class PhotoCommentController extends Controller { - public function reply(Request $request, $albumUrlAlias, $photoFilename, $commentID) + public function moderate(Request $request, $albumUrlAlias, $photoFilename, $commentID) { - $album = DbHelper::getAlbumByPath($albumUrlAlias); - if (is_null($album)) + $album = null; + + /** @var Photo $photo */ + $photo = null; + + /** @var PhotoComment $comment */ + $comment = null; + + if (!$this->loadAlbumPhotoComment($albumUrlAlias, $photoFilename, $commentID, $album, $photo, $comment)) { - App::abort(404); + return; + } + + if (Gate::denies('moderate-comments', $photo)) + { + App::abort(403); return null; } - $this->authorizeForUser($this->getUser(), 'view', $album); - - $photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename); - - if (!UserConfig::get('allow_photo_comments')) + if (!$comment->isModerated()) { - // Not allowed to post comments - App::abort(404); + if ($request->has('approve')) + { + $comment->approved_at = new \DateTime(); + $comment->approved_user_id = $this->getUser()->id; + $comment->save(); + + $request->getSession()->flash('success', trans('gallery.photo_comment_approved_successfully')); + } + else if ($request->has('reject')) + { + $comment->rejected_at = new \DateTime(); + $comment->rejected_user_id = $this->getUser()->id; + $comment->save(); + + $request->getSession()->flash('success', trans('gallery.photo_comment_rejected_successfully')); + } } - $comment = $photo->comments()->where('id', $commentID)->first(); - if (is_null($comment)) + return redirect($photo->url()); + } + + public function reply(Request $request, $albumUrlAlias, $photoFilename, $commentID) + { + $album = null; + + /** @var Photo $photo */ + $photo = null; + + /** @var PhotoComment $comment */ + $comment = null; + + if (!$this->loadAlbumPhotoComment($albumUrlAlias, $photoFilename, $commentID, $album, $photo, $comment)) { - App::abort(404); + return; } return Theme::render('partials.photo_comments_reply_form', [ @@ -48,21 +84,17 @@ class PhotoCommentController extends Controller public function store(Request $request, $albumUrlAlias, $photoFilename) { - $album = DbHelper::getAlbumByPath($albumUrlAlias); - if (is_null($album)) + $album = null; + + /** @var Photo $photo */ + $photo = null; + + /** @var PhotoComment $comment */ + $comment = null; + + if (!$this->loadAlbumPhotoComment($albumUrlAlias, $photoFilename, 0, $album, $photo, $comment)) { - App::abort(404); - return null; - } - - $this->authorizeForUser($this->getUser(), 'view', $album); - - $photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename); - - if (!UserConfig::get('allow_photo_comments')) - { - // Not allowed to post comments - redirect back to URL - return redirect($photo->url()); + return; } // Validate and link the parent comment, if provided @@ -96,15 +128,32 @@ class PhotoCommentController extends Controller $comment->parent_comment_id = $parentComment->id; } + // Set the created user ID if we're logged in $user = $this->getUser(); if (!is_null($user) && !$user->isAnonymous()) { $comment->created_user_id = $user->id; } + // Auto-approve the comment if we're allowed to moderate comments + $isAutoApproved = false; + if (Gate::allows('moderate-comments', $photo)) + { + $comment->approved_at = new \DateTime(); + $comment->approved_user_id = $user->id; + $isAutoApproved = true; + } + $comment->save(); - $request->getSession()->flash('success', trans('gallery.photo_comment_posted_successfully')); + if ($isAutoApproved) + { + $request->getSession()->flash('success', trans('gallery.photo_comment_posted_successfully')); + } + else + { + $request->getSession()->flash('success', trans('gallery.photo_comment_posted_successfully_pending_moderation')); + } if ($request->isXmlHttpRequest()) { @@ -132,4 +181,37 @@ class PhotoCommentController extends Controller } } } + + private function loadAlbumPhotoComment($albumUrlAlias, $photoFilename, $commentID, &$album, &$photo, &$comment) + { + $album = DbHelper::getAlbumByPath($albumUrlAlias); + if (is_null($album)) + { + App::abort(404); + return false; + } + + $this->authorizeForUser($this->getUser(), 'view', $album); + + $photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename); + + if (!UserConfig::get('allow_photo_comments')) + { + // Not allowed to post comments + App::abort(404); + return false; + } + + if (intval($commentID > 0)) + { + $comment = $photo->comments()->where('id', $commentID)->first(); + if (is_null($comment)) + { + App::abort(404); + return false; + } + } + + return true; + } } \ No newline at end of file diff --git a/app/Photo.php b/app/Photo.php index 8b4da40..a4fef8b 100644 --- a/app/Photo.php +++ b/app/Photo.php @@ -52,13 +52,6 @@ class Photo extends Model return $this->belongsTo(Album::class); } - public function approvedComments() - { - return $this->hasMany(PhotoComment::class) - ->whereNull('parent_comment_id') - ->whereNotNull('approved_at'); - } - public function comments() { return $this->hasMany(PhotoComment::class); @@ -89,6 +82,15 @@ class Photo extends Model return $this->belongsToMany(Label::class, 'photo_labels'); } + public function moderateCommentUrl($commentID = -1) + { + return route('moderatePhotoComment', [ + 'albumUrlAlias' => $this->album->url_path, + 'photoFilename' => $this->storage_file_name, + 'commentID' => $commentID + ]); + } + public function postCommentUrl() { return route('postPhotoComment', [ diff --git a/app/PhotoComment.php b/app/PhotoComment.php index f406e0e..da3cf9c 100644 --- a/app/PhotoComment.php +++ b/app/PhotoComment.php @@ -17,16 +17,6 @@ class PhotoComment extends Model 'comment' ]; - public function approvedBy() - { - return $this->belongsTo(User::class, 'approved_user_id'); - } - - public function approvedChildren() - { - return $this->children()->whereNotNull('approved_at'); - } - public function authorDisplayName() { return is_null($this->createdBy) ? $this->name : $this->createdBy->name; @@ -56,6 +46,24 @@ class PhotoComment extends Model return $depth; } + public function isApproved() + { + return ( + !is_null($this->approved_user_id) && + !is_null($this->approved_at) && + is_null($this->rejected_user_id) && + is_null($this->rejected_at) + ); + } + + public function isModerated() + { + return ( + (!is_null($this->approved_user_id) && !is_null($this->approved_at)) || + (!is_null($this->rejected_user_id) && !is_null($this->rejected_at)) + ); + } + public function parent() { return $this->belongsTo(PhotoComment::class, 'parent_comment_id'); diff --git a/app/Policies/AlbumPolicy.php b/app/Policies/AlbumPolicy.php index 2309121..0eb2a9d 100644 --- a/app/Policies/AlbumPolicy.php +++ b/app/Policies/AlbumPolicy.php @@ -93,6 +93,17 @@ class AlbumPolicy return $this->userHasPermission($user, $album, 'manipulate-photos'); } + public function moderateComments(User $user, Album $album) + { + if ($user->id == $album->user_id) + { + // The album's owner and can do everything + return true; + } + + return $this->userHasPermission($user, $album, 'moderate-comments'); + } + public function uploadPhotos(User $user, Album $album) { if ($user->id == $album->user_id) diff --git a/app/Policies/PhotoPolicy.php b/app/Policies/PhotoPolicy.php index 52c7119..e39ae42 100644 --- a/app/Policies/PhotoPolicy.php +++ b/app/Policies/PhotoPolicy.php @@ -61,4 +61,15 @@ class PhotoPolicy return $user->can('manipulate-photos', $photo->album); } + + public function moderateComments(User $user, Photo $photo) + { + if ($user->id == $photo->user_id) + { + // The photo's owner can do everything + return true; + } + + return $user->can('moderate-comments', $photo->album); + } } diff --git a/database/migrations/2018_09_19_190631_add_comment_rejected_columns.php b/database/migrations/2018_09_19_190631_add_comment_rejected_columns.php new file mode 100644 index 0000000..788effb --- /dev/null +++ b/database/migrations/2018_09_19_190631_add_comment_rejected_columns.php @@ -0,0 +1,34 @@ +unsignedInteger('rejected_user_id')->nullable(true); + $table->dateTime('rejected_at')->nullable(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('photo_comments', function (Blueprint $table) { + $table->dropColumn('rejected_user_id'); + $table->dropColumn('rejected_at'); + }); + } +} diff --git a/database/seeds/PermissionsSeeder.php b/database/seeds/PermissionsSeeder.php index 23a7df0..37309ad 100644 --- a/database/seeds/PermissionsSeeder.php +++ b/database/seeds/PermissionsSeeder.php @@ -139,5 +139,13 @@ class PermissionsSeeder extends Seeder 'is_default' => false, 'sort_order' => 60 ]); + + // album:moderate-comments = moderate comments posted on photos + DatabaseSeeder::createOrUpdate('permissions', [ + 'section' => 'album', + 'description' => 'moderate-comments', + 'is_default' => false, + 'sort_order' => 70 + ]); } } diff --git a/resources/lang/en/gallery.php b/resources/lang/en/gallery.php index 59705c6..4acfa4a 100644 --- a/resources/lang/en/gallery.php +++ b/resources/lang/en/gallery.php @@ -34,8 +34,14 @@ return [ 'other_albums_description' => 'You may also be interested in the following albums.', 'other_albums_description_empty' => 'The :album_name album does not contain any photos - however you may also be interested in the following albums.', 'other_albums_heading' => 'More Albums in :album_name', - 'photo_comment_posted_successfully' => 'Your comment was posted successfully and will appear after it has been moderated.', + 'photo_comment_pending_approval' => 'Pending Approval', + 'photo_comment_approved_successfully' => 'The comment was approved.', + 'photo_comment_posted_successfully' => 'Your comment was posted successfully.', + 'photo_comment_posted_successfully_pending_moderation' => 'Your comment was posted successfully and will appear after it has been moderated.', + 'photo_comment_rejected_successfully' => 'The comment was rejected.', + 'photo_comments_approve_action' => 'Approve', 'photo_comments_heading' => 'Comments', + 'photo_comments_reject_action' => 'Reject', 'photo_comments_reply_action' => 'Reply', 'photo_comments_reply_form_heading' => 'Leave a reply', 'photo_comments_reply_form_p1' => 'Complete the form below to start a new reply thread.', diff --git a/resources/lang/en/permissions.php b/resources/lang/en/permissions.php index 1844f8a..bcbe924 100644 --- a/resources/lang/en/permissions.php +++ b/resources/lang/en/permissions.php @@ -16,6 +16,7 @@ return [ 'edit' => 'Manage this album', 'list' => 'See this album in listings', 'manipulate-photos' => 'Manipulate photos in this album', + 'moderate-comments' => 'Moderate comments in this album', 'upload-photos' => 'Upload photos into this album', 'view' => 'Access this album' ] diff --git a/resources/views/themes/base/partials/album_permissions.blade.php b/resources/views/themes/base/partials/album_permissions.blade.php index 6684d9a..e1d8ffc 100644 --- a/resources/views/themes/base/partials/album_permissions.blade.php +++ b/resources/views/themes/base/partials/album_permissions.blade.php @@ -29,6 +29,7 @@ @include(Theme::viewName('partials.permission_checkbox'), ['permission' => Theme::getPermission($all_permissions, 'album', 'manipulate-photos')]) @include(Theme::viewName('partials.permission_checkbox'), ['permission' => Theme::getPermission($all_permissions, 'album', 'change-photo-metadata')]) @include(Theme::viewName('partials.permission_checkbox'), ['permission' => Theme::getPermission($all_permissions, 'album', 'delete-photos')]) + @include(Theme::viewName('partials.permission_checkbox'), ['permission' => Theme::getPermission($all_permissions, 'album', 'moderate-comments')]) @endif diff --git a/resources/views/themes/base/partials/photo_comments.blade.php b/resources/views/themes/base/partials/photo_comments.blade.php index e8da9f6..2828071 100644 --- a/resources/views/themes/base/partials/photo_comments.blade.php +++ b/resources/views/themes/base/partials/photo_comments.blade.php @@ -7,11 +7,13 @@
@include(Theme::viewName('partials.photo_comments_reply_form')) - @if ($photo->approvedComments()->count() > 0) - @foreach ($photo->approvedComments as $comment) + @foreach ($photo->comments()->whereNull('parent_comment_id')->get() as $comment) + @if ($comment->isApproved()) @include(Theme::viewName('partials.photo_single_comment')) - @endforeach - @endif + @elseif (!$comment->isModerated() && Gate::allows('moderate-comments', $photo)) + @include(Theme::viewName('partials.photo_single_comment_moderate')) + @endif + @endforeach diff --git a/resources/views/themes/base/partials/photo_single_comment.blade.php b/resources/views/themes/base/partials/photo_single_comment.blade.php index 7ea2a24..73c33bb 100644 --- a/resources/views/themes/base/partials/photo_single_comment.blade.php +++ b/resources/views/themes/base/partials/photo_single_comment.blade.php @@ -16,7 +16,11 @@ @if (!$is_reply) - @foreach ($comment->approvedChildren as $childComment) - @include(Theme::viewName('partials.photo_single_comment'), ['comment' => $childComment]) + @foreach ($comment->children as $childComment) + @if ($childComment->isApproved()) + @include(Theme::viewName('partials.photo_single_comment'), ['comment' => $childComment]) + @elseif (!$childComment->isModerated() && Gate::allows('moderate-comments', $photo)) + @include(Theme::viewName('partials.photo_single_comment_moderate'), ['comment' => $childComment]) + @endif @endforeach @endif \ No newline at end of file diff --git a/resources/views/themes/base/partials/photo_single_comment_moderate.blade.php b/resources/views/themes/base/partials/photo_single_comment_moderate.blade.php new file mode 100644 index 0000000..9c79140 --- /dev/null +++ b/resources/views/themes/base/partials/photo_single_comment_moderate.blade.php @@ -0,0 +1,17 @@ +
+
+ @lang('gallery.photo_comment_pending_approval') +
+
+ {{ $comment->authorDisplayName() }} +
{{ $comment->authorDisplayName() }}
+
{{ date(UserConfig::get('date_format'), strtotime($comment->created_at)) }}
+ {!! $comment->textAsHtml() !!} + +
+ {{ csrf_field() }} + + +
+
+
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index e18556c..ee73dd5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -109,6 +109,9 @@ Route::get('exif/{albumUrlAlias}/{photoFilename}', 'Gallery\PhotoController@show Route::post('p/{albumUrlAlias}/{photoFilename}/comments', 'Gallery\PhotoCommentController@store') ->name('postPhotoComment') ->where('albumUrlAlias', '.*'); +Route::post('p/{albumUrlAlias}/{photoFilename}/comments/moderate/{commentID}', 'Gallery\PhotoCommentController@moderate') + ->name('moderatePhotoComment') + ->where('albumUrlAlias', '.*'); Route::get('p/{albumUrlAlias}/{photoFilename}/comments/reply/{commentID}', 'Gallery\PhotoCommentController@reply') ->name('replyPhotoComment') ->where('albumUrlAlias', '.*');