Merge photo comments feature #114

Merged
aheathershaw merged 28 commits from feature/4-commenting-on-photos into master 2018-10-05 23:37:26 +01:00
14 changed files with 243 additions and 53 deletions
Showing only changes of commit 97ee60cfc9 - Show all commits

View File

@ -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;
}
}

View File

@ -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', [

View File

@ -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');

View File

@ -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)

View File

@ -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);
}
}

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,
'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_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',
'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.',

View File

@ -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'
]

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', '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')])
</div>
@endif
</div>

View File

@ -7,11 +7,13 @@
<hr/>
@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
</div>
</div>

View File

@ -16,7 +16,11 @@
</div>
@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

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')
->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', '.*');