#4: Comments can now be approved and rejected from the front-end gallery

This commit is contained in:
Andy Heathershaw 2018-09-19 19:54:59 +01:00
parent 1d10d50557
commit 97ee60cfc9
14 changed files with 243 additions and 53 deletions

View File

@ -7,37 +7,73 @@ use App\Facade\UserConfig;
use App\Helpers\DbHelper; use App\Helpers\DbHelper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\StorePhotoCommentRequest; use App\Http\Requests\StorePhotoCommentRequest;
use App\Photo;
use App\PhotoComment; use App\PhotoComment;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class PhotoCommentController extends Controller class PhotoCommentController extends Controller
{ {
public function reply(Request $request, $albumUrlAlias, $photoFilename, $commentID) public function moderate(Request $request, $albumUrlAlias, $photoFilename, $commentID)
{ {
$album = DbHelper::getAlbumByPath($albumUrlAlias); $album = null;
if (is_null($album))
/** @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; return null;
} }
$this->authorizeForUser($this->getUser(), 'view', $album); if (!$comment->isModerated())
$photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename);
if (!UserConfig::get('allow_photo_comments'))
{ {
// Not allowed to post comments if ($request->has('approve'))
App::abort(404); {
$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(); return redirect($photo->url());
if (is_null($comment)) }
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', [ return Theme::render('partials.photo_comments_reply_form', [
@ -48,21 +84,17 @@ class PhotoCommentController extends Controller
public function store(Request $request, $albumUrlAlias, $photoFilename) public function store(Request $request, $albumUrlAlias, $photoFilename)
{ {
$album = DbHelper::getAlbumByPath($albumUrlAlias); $album = null;
if (is_null($album))
/** @var Photo $photo */
$photo = null;
/** @var PhotoComment $comment */
$comment = null;
if (!$this->loadAlbumPhotoComment($albumUrlAlias, $photoFilename, 0, $album, $photo, $comment))
{ {
App::abort(404); return;
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());
} }
// Validate and link the parent comment, if provided // Validate and link the parent comment, if provided
@ -96,15 +128,32 @@ class PhotoCommentController extends Controller
$comment->parent_comment_id = $parentComment->id; $comment->parent_comment_id = $parentComment->id;
} }
// Set the created user ID if we're logged in
$user = $this->getUser(); $user = $this->getUser();
if (!is_null($user) && !$user->isAnonymous()) if (!is_null($user) && !$user->isAnonymous())
{ {
$comment->created_user_id = $user->id; $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(); $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()) 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;
}
} }

View File

@ -52,13 +52,6 @@ class Photo extends Model
return $this->belongsTo(Album::class); return $this->belongsTo(Album::class);
} }
public function approvedComments()
{
return $this->hasMany(PhotoComment::class)
->whereNull('parent_comment_id')
->whereNotNull('approved_at');
}
public function comments() public function comments()
{ {
return $this->hasMany(PhotoComment::class); return $this->hasMany(PhotoComment::class);
@ -89,6 +82,15 @@ class Photo extends Model
return $this->belongsToMany(Label::class, 'photo_labels'); 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() public function postCommentUrl()
{ {
return route('postPhotoComment', [ return route('postPhotoComment', [

View File

@ -17,16 +17,6 @@ class PhotoComment extends Model
'comment' 'comment'
]; ];
public function approvedBy()
{
return $this->belongsTo(User::class, 'approved_user_id');
}
public function approvedChildren()
{
return $this->children()->whereNotNull('approved_at');
}
public function authorDisplayName() public function authorDisplayName()
{ {
return is_null($this->createdBy) ? $this->name : $this->createdBy->name; return is_null($this->createdBy) ? $this->name : $this->createdBy->name;
@ -56,6 +46,24 @@ class PhotoComment extends Model
return $depth; 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() public function parent()
{ {
return $this->belongsTo(PhotoComment::class, 'parent_comment_id'); return $this->belongsTo(PhotoComment::class, 'parent_comment_id');

View File

@ -93,6 +93,17 @@ class AlbumPolicy
return $this->userHasPermission($user, $album, 'manipulate-photos'); 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) public function uploadPhotos(User $user, Album $album)
{ {
if ($user->id == $album->user_id) if ($user->id == $album->user_id)

View File

@ -61,4 +61,15 @@ class PhotoPolicy
return $user->can('manipulate-photos', $photo->album); 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);
}
} }

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddCommentRejectedColumns extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('photo_comments', function (Blueprint $table) {
$table->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');
});
}
}

View File

@ -139,5 +139,13 @@ class PermissionsSeeder extends Seeder
'is_default' => false, 'is_default' => false,
'sort_order' => 60 '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
]);
} }
} }

View File

@ -34,8 +34,14 @@ return [
'other_albums_description' => 'You may also be interested in the following albums.', 'other_albums_description' => 'You may also be interested in the following albums.',
'other_albums_description_empty' => 'The <b>:album_name</b> album does not contain any photos - however you may also be interested in the following albums.', 'other_albums_description_empty' => 'The <b>:album_name</b> album does not contain any photos - however you may also be interested in the following albums.',
'other_albums_heading' => 'More Albums in :album_name', '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_heading' => 'Comments',
'photo_comments_reject_action' => 'Reject',
'photo_comments_reply_action' => 'Reply', 'photo_comments_reply_action' => 'Reply',
'photo_comments_reply_form_heading' => 'Leave a reply', 'photo_comments_reply_form_heading' => 'Leave a reply',
'photo_comments_reply_form_p1' => 'Complete the form below to start a new reply thread.', 'photo_comments_reply_form_p1' => 'Complete the form below to start a new reply thread.',

View File

@ -16,6 +16,7 @@ return [
'edit' => 'Manage this album', 'edit' => 'Manage this album',
'list' => 'See this album in listings', 'list' => 'See this album in listings',
'manipulate-photos' => 'Manipulate photos in this album', 'manipulate-photos' => 'Manipulate photos in this album',
'moderate-comments' => 'Moderate comments in this album',
'upload-photos' => 'Upload photos into this album', 'upload-photos' => 'Upload photos into this album',
'view' => 'Access this album' 'view' => 'Access this album'
] ]

View File

@ -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', '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', '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', 'delete-photos')])
@include(Theme::viewName('partials.permission_checkbox'), ['permission' => Theme::getPermission($all_permissions, 'album', 'moderate-comments')])
</div> </div>
@endif @endif
</div> </div>

View File

@ -7,11 +7,13 @@
<hr/> <hr/>
@include(Theme::viewName('partials.photo_comments_reply_form')) @include(Theme::viewName('partials.photo_comments_reply_form'))
@if ($photo->approvedComments()->count() > 0) @foreach ($photo->comments()->whereNull('parent_comment_id')->get() as $comment)
@foreach ($photo->approvedComments as $comment) @if ($comment->isApproved())
@include(Theme::viewName('partials.photo_single_comment')) @include(Theme::viewName('partials.photo_single_comment'))
@endforeach @elseif (!$comment->isModerated() && Gate::allows('moderate-comments', $photo))
@endif @include(Theme::viewName('partials.photo_single_comment_moderate'))
@endif
@endforeach
</div> </div>
</div> </div>

View File

@ -16,7 +16,11 @@
</div> </div>
@if (!$is_reply) @if (!$is_reply)
@foreach ($comment->approvedChildren as $childComment) @foreach ($comment->children as $childComment)
@include(Theme::viewName('partials.photo_single_comment'), ['comment' => $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 @endforeach
@endif @endif

View File

@ -0,0 +1,17 @@
<div class="card photo-comment mt-2 border-warning" data-comment-id="{{ $comment->id }}" style="margin-left: {{ $comment->depth() * 20 }}px;">
<div class="card-header bg-warning border-warning text-white">
<b>@lang('gallery.photo_comment_pending_approval')</b>
</div>
<div class="card-body">
<img class="img-thumbnail rounded float-left mr-3 mb-2" src="{{ Theme::gravatarUrl($comment->email) }}" alt="{{ $comment->authorDisplayName() }}" title="{{ $comment->authorDisplayName() }}">
<h5 class="card-title mt-1"><b>{{ $comment->authorDisplayName() }}</b></h5>
<h6 class="card-subtitle mb-4 text-muted">{{ date(UserConfig::get('date_format'), strtotime($comment->created_at)) }}</h6>
{!! $comment->textAsHtml() !!}
<form action="{{ $photo->moderateCommentUrl($comment->id) }}" method="post">
{{ csrf_field() }}
<button type="submit" name="approve" class="btn btn-success card-link">@lang('gallery.photo_comments_approve_action')</button>
<button type="submit" name="reject" class="btn btn-outline-danger card-link">@lang('gallery.photo_comments_reject_action')</button>
</form>
</div>
</div>

View File

@ -109,6 +109,9 @@ Route::get('exif/{albumUrlAlias}/{photoFilename}', 'Gallery\PhotoController@show
Route::post('p/{albumUrlAlias}/{photoFilename}/comments', 'Gallery\PhotoCommentController@store') Route::post('p/{albumUrlAlias}/{photoFilename}/comments', 'Gallery\PhotoCommentController@store')
->name('postPhotoComment') ->name('postPhotoComment')
->where('albumUrlAlias', '.*'); ->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') Route::get('p/{albumUrlAlias}/{photoFilename}/comments/reply/{commentID}', 'Gallery\PhotoCommentController@reply')
->name('replyPhotoComment') ->name('replyPhotoComment')
->where('albumUrlAlias', '.*'); ->where('albumUrlAlias', '.*');