blue-twilight/app/Http/Controllers/Gallery/PhotoCommentController.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;
}
}