411 lines
13 KiB
PHP
411 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Gallery;
|
|
|
|
use App\Album;
|
|
use App\Facade\Theme;
|
|
use App\Facade\UserConfig;
|
|
use App\Helpers\DbHelper;
|
|
use App\Helpers\PermissionsHelper;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Notifications\ModeratePhotoComment;
|
|
use App\Notifications\PhotoCommentApproved;
|
|
use App\Notifications\PhotoCommentApprovedUser;
|
|
use App\Notifications\PhotoCommentRepliedTo;
|
|
use App\Photo;
|
|
use App\PhotoComment;
|
|
use App\User;
|
|
use App\UserActivity;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\App;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class PhotoCommentController extends Controller
|
|
{
|
|
public function moderate(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))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!User::currentOrAnonymous()->can('moderate-comments', $photo))
|
|
{
|
|
App::abort(403);
|
|
return null;
|
|
}
|
|
|
|
if (!$comment->isModerated())
|
|
{
|
|
if ($request->has('approve'))
|
|
{
|
|
$comment->approved_at = new \DateTime();
|
|
$comment->approved_user_id = $this->getUser()->id;
|
|
$comment->save();
|
|
|
|
$this->createUserActivityRecord($comment);
|
|
$this->notifyAlbumOwnerAndPoster($album, $photo, $comment);
|
|
$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'));
|
|
}
|
|
}
|
|
|
|
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))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!User::currentOrAnonymous()->can('post-comment', $photo))
|
|
{
|
|
App::abort(403);
|
|
return null;
|
|
}
|
|
|
|
return Theme::render('partials.photo_comments_reply_form', [
|
|
'photo' => $photo,
|
|
'reply_comment' => $comment
|
|
]);
|
|
}
|
|
|
|
public function store(Request $request, $albumUrlAlias, $photoFilename)
|
|
{
|
|
$album = null;
|
|
|
|
/** @var Photo $photo */
|
|
$photo = null;
|
|
|
|
/** @var PhotoComment $comment */
|
|
$comment = null;
|
|
|
|
if (!$this->loadAlbumPhotoComment($albumUrlAlias, $photoFilename, 0, $album, $photo, $comment))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!User::currentOrAnonymous()->can('post-comment', $photo))
|
|
{
|
|
App::abort(403);
|
|
return null;
|
|
}
|
|
|
|
// Validate and link the parent comment, if provided
|
|
// We do this here so if the validation fails, we still have the parent comment available in the catch block
|
|
$parentComment = null;
|
|
if ($request->has('parent_comment_id'))
|
|
{
|
|
$parentComment = $photo->comments()->where('id', intval($request->get('parent_comment_id')))->first();
|
|
|
|
if (is_null($parentComment))
|
|
{
|
|
return redirect($photo->url());
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
$this->validate($request, [
|
|
'name' => 'required|max:255',
|
|
'email' => 'sometimes|max:255|email',
|
|
'comment' => 'required'
|
|
]);
|
|
|
|
$commentText = $this->stripDisallowedHtmlTags($request->get('comment'));
|
|
|
|
$comment = new PhotoComment();
|
|
$comment->photo_id = $photo->id;
|
|
$comment->fill($request->only(['name', 'email']));
|
|
$comment->comment = $commentText;
|
|
|
|
if (!is_null($parentComment))
|
|
{
|
|
$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 (User::currentOrAnonymous()->can('moderate-comments', $photo))
|
|
{
|
|
$comment->approved_at = new \DateTime();
|
|
$comment->approved_user_id = $user->id;
|
|
$isAutoApproved = true;
|
|
}
|
|
|
|
// Auto-approve the comment if settings allow
|
|
if ($user->isAnonymous() && !UserConfig::get('moderate_anonymous_users'))
|
|
{
|
|
$comment->approved_at = new \DateTime();
|
|
$comment->approved_user_id = null; // we don't have a user ID to set!
|
|
$isAutoApproved = true;
|
|
}
|
|
else if (!$user->isAnonymous() && !UserConfig::get('moderate_known_users'))
|
|
{
|
|
$comment->approved_at = new \DateTime();
|
|
$comment->approved_user_id = $user->id;
|
|
$isAutoApproved = true;
|
|
}
|
|
|
|
$comment->save();
|
|
|
|
// Send notification e-mails to moderators or album owner
|
|
if (!$isAutoApproved)
|
|
{
|
|
$this->notifyAlbumModerators($album, $photo, $comment);
|
|
$request->getSession()->flash('success', trans('gallery.photo_comment_posted_successfully_pending_moderation'));
|
|
}
|
|
else
|
|
{
|
|
// Log an activity record for the user's feed
|
|
$this->createUserActivityRecord($comment);
|
|
$this->notifyAlbumOwnerAndPoster($album, $photo, $comment);
|
|
$request->getSession()->flash('success', trans('gallery.photo_comment_posted_successfully'));
|
|
}
|
|
|
|
if ($request->isXmlHttpRequest())
|
|
{
|
|
return response()->json(['redirect_url' => $photo->url()]);
|
|
}
|
|
else
|
|
{
|
|
return redirect($photo->url());
|
|
}
|
|
}
|
|
catch (ValidationException $e)
|
|
{
|
|
if (!is_null($parentComment))
|
|
{
|
|
return redirect()
|
|
->to($photo->replyToCommentFormUrl($parentComment->id))
|
|
->withErrors($e->errors())
|
|
->withInput($request->all());
|
|
}
|
|
else
|
|
{
|
|
return redirect()
|
|
->back()
|
|
->withErrors($e->errors())
|
|
->withInput($request->all());
|
|
}
|
|
}
|
|
}
|
|
|
|
private function createUserActivityRecord(PhotoComment $comment)
|
|
{
|
|
if (!is_null($comment->created_user_id))
|
|
{
|
|
$userActivity = new UserActivity();
|
|
$userActivity->user_id = $comment->created_user_id;
|
|
$userActivity->activity_at = $comment->created_at;
|
|
|
|
if (is_null($comment->parent_comment_id))
|
|
{
|
|
$userActivity->type = 'photo.commented';
|
|
}
|
|
else
|
|
{
|
|
$userActivity->type = 'photo.comment_replied';
|
|
}
|
|
|
|
$userActivity->photo_id = $comment->photo_id;
|
|
$userActivity->photo_comment_id = $comment->id;
|
|
$userActivity->save();
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Loads a given comment by its ID.
|
|
* @param $id
|
|
* @return PhotoComment
|
|
*/
|
|
private function loadCommentByID($id)
|
|
{
|
|
$comment = PhotoComment::where('id', intval($id))->first();
|
|
if (is_null($comment))
|
|
{
|
|
App::abort(404);
|
|
}
|
|
|
|
return $comment;
|
|
}
|
|
|
|
/**
|
|
* Sends an e-mail notification to an album's moderators that a comment is available to moderate.
|
|
* @param Album $album
|
|
* @param Photo $photo
|
|
* @param PhotoComment $comment
|
|
*/
|
|
private function notifyAlbumModerators(Album $album, Photo $photo, PhotoComment $comment)
|
|
{
|
|
// Get all users from the cache
|
|
$helper = new PermissionsHelper();
|
|
$moderators = $helper->usersWhoCan_Album($album, 'moderate-comments');
|
|
|
|
/** @var User $moderator */
|
|
foreach ($moderators as $moderator)
|
|
{
|
|
$moderator->notify(new ModeratePhotoComment($album, $photo, $comment));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends an e-mail notification to an album's owned that a comment has been posted/approved.
|
|
* @param Album $album
|
|
* @param Photo $photo
|
|
* @param PhotoComment $comment
|
|
*/
|
|
private function notifyAlbumOwnerAndPoster(Album $album, Photo $photo, PhotoComment $comment)
|
|
{
|
|
/** @var User $owner */
|
|
$owner = $album->user;
|
|
|
|
$owner->notify(new PhotoCommentApproved($album, $photo, $comment));
|
|
|
|
// Also send a notification to the comment poster
|
|
$poster = new User();
|
|
$poster->name = $comment->authorDisplayName();
|
|
$poster->email = $comment->authorEmail();
|
|
|
|
$poster->notify(new PhotoCommentApprovedUser($album, $photo, $comment));
|
|
|
|
// Send notification to the parent comment owner (if this is a reply)
|
|
if (!is_null($comment->parent_comment_id))
|
|
{
|
|
$parentComment = $this->loadCommentByID($comment->parent_comment_id);
|
|
|
|
if (is_null($parentComment))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$parentPoster = new User();
|
|
$parentPoster->name = $parentComment->authorDisplayName();
|
|
$parentPoster->email = $parentComment->authorEmail();
|
|
|
|
$parentPoster->notify(new PhotoCommentRepliedTo($album, $photo, $comment));
|
|
}
|
|
}
|
|
|
|
private function stripDisallowedHtmlTags($commentText)
|
|
{
|
|
$allowedHtmlTags = explode(',', UserConfig::get('photo_comments_allowed_html'));
|
|
$allowedHtmlTagsCleaned = [];
|
|
|
|
foreach ($allowedHtmlTags as $tag)
|
|
{
|
|
$allowedHtmlTagsCleaned[] = trim($tag);
|
|
}
|
|
|
|
// Match any starting HTML tags
|
|
$regexMatchString = '/<(?!\/)([a-z]+)(?:\s.*)*>/Us';
|
|
|
|
$htmlTagMatches = [];
|
|
preg_match_all($regexMatchString, $commentText, $htmlTagMatches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
|
|
|
for ($index = 0; $index < count($htmlTagMatches); $index++)
|
|
{
|
|
$htmlTagMatch = $htmlTagMatches[$index];
|
|
|
|
$htmlTag = $htmlTagMatch[1][0]; // e.g. "p" for <p>
|
|
if (in_array($htmlTag, $allowedHtmlTagsCleaned))
|
|
{
|
|
// This tag is allowed - carry on
|
|
continue;
|
|
}
|
|
|
|
/* This tag is not allowed - remove it from the string */
|
|
|
|
// Find the closing tag
|
|
$disallowedStringOffset = $htmlTagMatch[0][1];
|
|
$endingTagMatches = [];
|
|
preg_match(sprintf('/(<%1$s.*>)(.+)<\/%1$s>/Us', $htmlTag), $commentText, $endingTagMatches, 0, $disallowedStringOffset);
|
|
|
|
// Replace the matched string with the inner string
|
|
$commentText = substr_replace($commentText, $endingTagMatches[2], $disallowedStringOffset, strlen($endingTagMatches[0]));
|
|
|
|
// Adjust the offsets for strings after the one we're processing, so the offsets match up with the string correctly
|
|
for ($index2 = $index + 1; $index2 < count($htmlTagMatches); $index2++)
|
|
{
|
|
// If this string appears entirely BEFORE the next one starts, we need to subtract the entire length.
|
|
// Otherwise, we only need to substract the length of the start tag, as the next one starts within it.
|
|
$differenceAfterReplacement = strlen($endingTagMatches[1]);
|
|
|
|
if ($htmlTagMatch[0][1] + strlen($endingTagMatches[0]) < $htmlTagMatches[$index2][0][1])
|
|
{
|
|
$differenceAfterReplacement = strlen($endingTagMatches[0]) - strlen($endingTagMatches[2]);
|
|
}
|
|
|
|
$htmlTagMatches[$index2][0][1] -= $differenceAfterReplacement;
|
|
$htmlTagMatches[$index2][1][1] -= $differenceAfterReplacement;
|
|
}
|
|
}
|
|
|
|
return $commentText;
|
|
}
|
|
} |