diff --git a/app/Http/Controllers/NotificationsController.php b/app/Http/Controllers/NotificationsController.php index f8ee3a26..9d961828 100644 --- a/app/Http/Controllers/NotificationsController.php +++ b/app/Http/Controllers/NotificationsController.php @@ -20,10 +20,9 @@ namespace Poniverse\Ponyfm\Http\Controllers; +use DB; use Poniverse\Ponyfm\Models\Email; -use Poniverse\Ponyfm\Models\EmailClick; use Poniverse\Ponyfm\Models\EmailSubscription; -use View; class NotificationsController extends Controller { public function getEmailClick($emailKey) { @@ -31,7 +30,11 @@ class NotificationsController extends Controller { /** @var Email $email */ $email = Email::findOrFail($emailKey); - $email->emailClicks()->create(['ip_address' => \Request::ip()]); + DB::transaction(function() use ($email) { + $email->emailClicks()->create(['ip_address' => \Request::ip()]); + $email->notification->is_read = true; + $email->notification->save(); + }); return redirect($email->getActivity()->url); } diff --git a/app/Library/Notifications/Drivers/PonyfmDriver.php b/app/Library/Notifications/Drivers/PonyfmDriver.php index bf83c362..af816e1b 100644 --- a/app/Library/Notifications/Drivers/PonyfmDriver.php +++ b/app/Library/Notifications/Drivers/PonyfmDriver.php @@ -20,11 +20,16 @@ namespace Poniverse\Ponyfm\Library\Notifications\Drivers; -use ArrayAccess; use Carbon\Carbon; use Log; use Mail; use Poniverse\Ponyfm\Contracts\Favouritable; +use Poniverse\Ponyfm\Mail\BaseNotification; +use Poniverse\Ponyfm\Mail\ContentFavourited; +use Poniverse\Ponyfm\Mail\NewComment; +use Poniverse\Ponyfm\Mail\NewFollower; +use Poniverse\Ponyfm\Mail\NewPlaylist; +use Poniverse\Ponyfm\Mail\NewTrack; use Poniverse\Ponyfm\Models\Activity; use Poniverse\Ponyfm\Models\Comment; use Poniverse\Ponyfm\Models\Email; @@ -63,8 +68,9 @@ class PonyfmDriver extends AbstractDriver foreach ($recipients as $recipient) { $notification = $activity->notifications->where('user_id', $recipient->id)->first(); $email = $notification->email()->create([]); + Log::debug("Attempting to send an email about notification {$notification->id} to {$recipient->email}."); - Mail::to($recipient->email)->queue(new \Poniverse\Ponyfm\Mail\NewTrack($email)); + Mail::to($recipient->email)->queue(BaseNotification::factory($activity, $email)); } } @@ -82,7 +88,6 @@ class PonyfmDriver extends AbstractDriver ]); $recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args()); - $this->insertNotifications($activity, $recipientsQuery->get()); $this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_PUBLISHED_TRACK)->get()); } @@ -100,7 +105,9 @@ class PonyfmDriver extends AbstractDriver 'resource_id' => $playlist->id, ]); - $this->insertNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); + $recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args()); + $this->insertNotifications($activity, $recipientsQuery->get()); + $this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_PUBLISHED_PLAYLIST)->get()); } public function newFollower(User $userBeingFollowed, User $follower) @@ -113,7 +120,9 @@ class PonyfmDriver extends AbstractDriver 'resource_id' => $userBeingFollowed->id, ]); - $this->insertNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); + $recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args()); + $this->insertNotifications($activity, $recipientsQuery->get()); + $this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_NEW_FOLLOWER)->get()); } /** @@ -129,7 +138,9 @@ class PonyfmDriver extends AbstractDriver 'resource_id' => $comment->id, ]); - $this->insertNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); + $recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args()); + $this->insertNotifications($activity, $recipientsQuery->get()); + $this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_NEW_COMMENT)->get()); } /** @@ -145,6 +156,8 @@ class PonyfmDriver extends AbstractDriver 'resource_id' => $entityBeingFavourited->id, ]); - $this->insertNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args())); + $recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args()); + $this->insertNotifications($activity, $recipientsQuery->get()); + $this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_CONTENT_FAVOURITED)->get()); } } diff --git a/app/Mail/BaseNotification.php b/app/Mail/BaseNotification.php index 5ec1b4c3..c5a6cd16 100644 --- a/app/Mail/BaseNotification.php +++ b/app/Mail/BaseNotification.php @@ -23,6 +23,7 @@ namespace Poniverse\Ponyfm\Mail; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; +use Poniverse\Ponyfm\Models\Activity; use Poniverse\Ponyfm\Models\Email; abstract class BaseNotification extends Mailable { @@ -52,6 +53,36 @@ abstract class BaseNotification extends Mailable { $this->initiatingUser = $email->notification->activity->initiatingUser; } + /** + * Factory method that instantiates the appropriate {@link BaseNotification} + * subclass for the given activity type and {@link Email} record. + * + * @param Activity $activity + * @param Email $email + * @return BaseNotification + */ + static public function factory(Activity $activity, Email $email): BaseNotification { + switch ($activity->activity_type) { + case Activity::TYPE_NEWS: + break; + case Activity::TYPE_PUBLISHED_TRACK: + return new NewTrack($email); + case Activity::TYPE_PUBLISHED_ALBUM: + break; + case Activity::TYPE_PUBLISHED_PLAYLIST: + return new NewPlaylist($email); + case Activity::TYPE_NEW_FOLLOWER: + return new NewFollower($email); + case Activity::TYPE_NEW_COMMENT: + return new NewComment($email); + case Activity::TYPE_CONTENT_FAVOURITED: + return new ContentFavourited($email); + default: + break; + } + throw new \InvalidArgumentException("Email notifications for activity type {$activity->activity_type} are not implemented!"); + } + /** * Build the message. * @@ -80,8 +111,11 @@ abstract class BaseNotification extends Mailable { } /** - * Helper method to eliminate duplication between different types of notifications. - * Use it inside the build() method on this class's children. + * Helper method to eliminate duplication between different types of + * notifications. Use it inside the build() method on this class's children. + * + * Note that data common to all notification types is merged into the + * template variable array. * * @param string $templateName * @param string $subject @@ -91,8 +125,8 @@ abstract class BaseNotification extends Mailable { protected function renderEmail(string $templateName, string $subject, array $extraVariables) { return $this ->subject($subject) - ->view("emails.{$templateName}") - ->text("emails.{$templateName}_plaintext") + ->view("emails.notifications.{$templateName}") + ->text("emails.notifications.{$templateName}_plaintext") ->with(array_merge($extraVariables, [ 'notificationUrl' => $this->generateNotificationUrl(), 'unsubscribeUrl' => $this->generateUnsubscribeUrl() diff --git a/app/Mail/ContentFavourited.php b/app/Mail/ContentFavourited.php new file mode 100644 index 00000000..59e35e2c --- /dev/null +++ b/app/Mail/ContentFavourited.php @@ -0,0 +1,40 @@ +. + */ + +namespace Poniverse\Ponyfm\Mail; + +class ContentFavourited extends BaseNotification +{ + /** + * @inheritdoc + */ + public function build() + { + $creatorName = $this->initiatingUser->display_name; + + return $this->renderEmail( + 'content-favourited', + $this->activityRecord->text, [ + 'creatorName' => $creatorName, + 'resourceType' => $this->activityRecord->getResourceType(), + 'resourceTitle' => $this->activityRecord->resource->resource->title, + ]); + } +} diff --git a/app/Mail/NewComment.php b/app/Mail/NewComment.php new file mode 100644 index 00000000..8580e7ed --- /dev/null +++ b/app/Mail/NewComment.php @@ -0,0 +1,53 @@ +. + */ + +namespace Poniverse\Ponyfm\Mail; + +use Poniverse\Ponyfm\Models\User; + +class NewComment extends BaseNotification +{ + /** + * @inheritdoc + */ + public function build() + { + $creatorName = $this->initiatingUser->display_name; + + // Profile comments get a different template and subject line from + // other types of comments. + if ($this->activityRecord->getResourceType() === User::class) { + return $this->renderEmail( + 'new-comment-profile', + $this->activityRecord->text, [ + 'creatorName' => $creatorName, + ]); + } else { + return $this->renderEmail( + 'new-comment-content', + $this->activityRecord->text, [ + 'creatorName' => $creatorName, + 'resourceType' => $this->activityRecord->getResourceType(), + 'resourceTitle' => $this->activityRecord->resource->resource->title, + ]); + } + + } +} diff --git a/app/Mail/NewFollower.php b/app/Mail/NewFollower.php new file mode 100644 index 00000000..c96660e3 --- /dev/null +++ b/app/Mail/NewFollower.php @@ -0,0 +1,39 @@ +. + */ + +namespace Poniverse\Ponyfm\Mail; + +class NewFollower extends BaseNotification +{ + /** + * @inheritdoc + */ + public function build() + { + $creatorName = $this->initiatingUser->display_name; + + return $this->renderEmail( + 'new-follower', + "{$creatorName} is now following you on Pony.fm!", + [ + 'creatorName' => $creatorName, + ]); + } +} diff --git a/app/Mail/NewPlaylist.php b/app/Mail/NewPlaylist.php new file mode 100644 index 00000000..c865a494 --- /dev/null +++ b/app/Mail/NewPlaylist.php @@ -0,0 +1,41 @@ +. + */ + +namespace Poniverse\Ponyfm\Mail; + +class NewPlaylist extends BaseNotification +{ + /** + * @inheritdoc + */ + public function build() + { + $creatorName = $this->initiatingUser->display_name; + $playlistTitle = $this->activityRecord->resource->title; + + return $this->renderEmail( + 'new-playlist', + "{$creatorName} created a playlist, \"{$playlistTitle}\"!", + [ + 'creatorName' => $creatorName, + 'playlistTitle' => $playlistTitle, + ]); + } +} diff --git a/app/Mail/NewTrack.php b/app/Mail/NewTrack.php index 6f271fd2..46bf2b2a 100644 --- a/app/Mail/NewTrack.php +++ b/app/Mail/NewTrack.php @@ -1,24 +1,40 @@ . + */ + namespace Poniverse\Ponyfm\Mail; class NewTrack extends BaseNotification { /** - * Build the message. - * - * @return $this + * @inheritdoc */ public function build() { - $artistName = $this->initiatingUser->display_name; + $creatorName = $this->initiatingUser->display_name; $trackTitle = $this->activityRecord->resource->title; return $this->renderEmail( 'new-track', - "{$artistName} published \"{$trackTitle}\"", + "{$creatorName} published \"{$trackTitle}\"!", [ - 'artist' => $artistName, + 'creatorName' => $creatorName, 'trackTitle' => $trackTitle, ]); } diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 62207ab7..8899d587 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -211,6 +211,31 @@ class Activity extends Model } /** + * Returns a string representing the type of resource this activity is about + * for use in human-facing notification text. + * + * @return string + * @throws \Exception + */ + public function getResourceType():string + { + switch($this->activity_type) { + case static::TYPE_NEW_COMMENT: + if ($this->resource_type === User::class) { + return $this->resource->getResourceType(); + } else { + return $this->resource->resource->getResourceType(); + } + case static::TYPE_CONTENT_FAVOURITED: + return $this->resource->getResourceType(); + } + throw new \Exception("Unknown activity type {$this->activity_type} - cannot determine resource type."); + } + + /** + * The string this method generates is used for email subject lines as well + * as on-site notifications. + * * @return string human-readable Markdown string describing this notification * @throws \Exception */ @@ -237,11 +262,11 @@ class Activity extends Model // Must be a content comment. } else { - return "{$this->initiatingUser->display_name} left a comment on your {$this->resource->resource->getResourceType()}, {$this->resource->resource->title}!"; + return "{$this->initiatingUser->display_name} left a comment on your {$this->getResourceType()}, {$this->resource->resource->title}!"; } case static::TYPE_CONTENT_FAVOURITED: - return "{$this->initiatingUser->display_name} favourited your {$this->resource->getResourceType()}, {$this->resource->title}!"; + return "{$this->initiatingUser->display_name} favourited your {$this->getResourceType()}, {$this->resource->title}!"; default: throw new \Exception('This activity\'s activity type is unknown!'); diff --git a/documentation/notifications.md b/documentation/notifications.md index 7542a688..9c988634 100644 --- a/documentation/notifications.md +++ b/documentation/notifications.md @@ -17,12 +17,12 @@ The `Notification` facade is used to send notifications as follows: ```php use Notification; -// Something happens, like a newtrack getting published. +// Something happens, like a new track getting published. $track = new Track(); ... // The "something" is done happening! Time to send a notification. -Notification::publishedTrack($track); +Notification::publishedNewTrack($track); ``` This facade has a method for every notification type, drawn from the @@ -44,9 +44,14 @@ Adding new notification types - [`NotificationManager`](../app/Library/Notifications/NotificationManager.php) - [`RecipientFinder`](../app/Library/Notifications/RecipientFinder.php) - [`PonyfmDriver`](../app/Library/Notifications/PonyfmDriver.php) - -3. Ensure you create HTML and plaintext templates for the email version of the - notification. + +3. Create a migration to add the new notification type to the `activity_types` + table. Add a constant for it to the [`Activity`](../app/Models/Activity.php) + class. + +3. Ensure you create HTML and plaintext templates, as well as a subclass of + [`BaseNotification`](../app/Mail/BaseNotification.php) for the email version + of the notification. 4. Call the new method on the `Notification` facade from wherever the new notification gets triggered. diff --git a/resources/views/emails/new-track.blade.php b/resources/views/emails/new-track.blade.php deleted file mode 100644 index 806007b6..00000000 --- a/resources/views/emails/new-track.blade.php +++ /dev/null @@ -1,5 +0,0 @@ -

{{ $artist }} published a new track on Pony.fm! Listen to it now:

- -

{{ $trackTitle }}

- -

Unsubscribe from email notifications for new tracks

diff --git a/resources/views/emails/new-track_plaintext.blade.php b/resources/views/emails/new-track_plaintext.blade.php deleted file mode 100644 index dce5e58b..00000000 --- a/resources/views/emails/new-track_plaintext.blade.php +++ /dev/null @@ -1,10 +0,0 @@ -{{ $artist }} published a new track on Pony.fm! - -Title: {{ $trackTitle }} - -Listen to it: -{{ $notificationUrl }} - ---- -Unsubscribe from email notifications for new tracks: -{{ $unsubscribeUrl }} diff --git a/resources/views/emails/notifications/_layout.blade.php b/resources/views/emails/notifications/_layout.blade.php new file mode 100644 index 00000000..132a1131 --- /dev/null +++ b/resources/views/emails/notifications/_layout.blade.php @@ -0,0 +1,13 @@ +@yield('content') + +
+ +

Unsubscribe from this kind of email

+ +
+Poniverse
+248-1641 Lonsdale Avenue
+North Vancouver
+BC V7M 2J5
+Canada +
diff --git a/resources/views/emails/notifications/_layout_plaintext.blade.php b/resources/views/emails/notifications/_layout_plaintext.blade.php new file mode 100644 index 00000000..c6c998f4 --- /dev/null +++ b/resources/views/emails/notifications/_layout_plaintext.blade.php @@ -0,0 +1,10 @@ +@yield('content') + +--- +Unsubscribe from this kind of email: +{{ $unsubscribeUrl }} + +Poniverse +248-1641 Lonsdale Avenue +North Vancouver +BC V7M 2J5 diff --git a/resources/views/emails/notifications/content-favourited.blade.php b/resources/views/emails/notifications/content-favourited.blade.php new file mode 100644 index 00000000..565700e2 --- /dev/null +++ b/resources/views/emails/notifications/content-favourited.blade.php @@ -0,0 +1,9 @@ +@extends('emails.notifications._layout') + +@section('content') +

+ {{ $creatorName }} favourited your {{ $resourceType }}, + {{ $resourceTitle }} + Yay! +

+@endsection diff --git a/resources/views/emails/notifications/content-favourited_plaintext.blade.php b/resources/views/emails/notifications/content-favourited_plaintext.blade.php new file mode 100644 index 00000000..d14dc51d --- /dev/null +++ b/resources/views/emails/notifications/content-favourited_plaintext.blade.php @@ -0,0 +1,8 @@ +@extends('emails.notifications._layout_plaintext') + +@section('content') +{{ $creatorName }} favourited your {{ $resourceType }}, "{{ $resourceTitle }}". Yay! + +Here's a link to the {{ $resourceType }}: +{{ $notificationUrl }} +@endsection diff --git a/resources/views/emails/notifications/new-comment-content.blade.php b/resources/views/emails/notifications/new-comment-content.blade.php new file mode 100644 index 00000000..796d8321 --- /dev/null +++ b/resources/views/emails/notifications/new-comment-content.blade.php @@ -0,0 +1,9 @@ +@extends('emails.notifications._layout') + +@section('content') +

+ {{ $creatorName }} left a comment on your {{ $resourceType }}, + {{ $resourceTitle }}! + Visit it to read the comment and reply. +

+@endsection diff --git a/resources/views/emails/notifications/new-comment-content_plaintext.blade.php b/resources/views/emails/notifications/new-comment-content_plaintext.blade.php new file mode 100644 index 00000000..3ece5499 --- /dev/null +++ b/resources/views/emails/notifications/new-comment-content_plaintext.blade.php @@ -0,0 +1,8 @@ +@extends('emails.notifications._layout_plaintext') + +@section('content') +{{ $creatorName }} left a comment on your {{ $resourceType }}, "{{ $resourceTitle }}"! + +Visit the following link to read the comment and reply: +{{ $notificationUrl }} +@endsection diff --git a/resources/views/emails/notifications/new-comment-profile.blade.php b/resources/views/emails/notifications/new-comment-profile.blade.php new file mode 100644 index 00000000..4dcf3609 --- /dev/null +++ b/resources/views/emails/notifications/new-comment-profile.blade.php @@ -0,0 +1,9 @@ +@extends('emails.notifications._layout') + +@section('content') +

+ {{ $creatorName }} left a comment on your Pony.fm profile! + Visit your profile to + read it and reply. +

+@endsection diff --git a/resources/views/emails/notifications/new-comment-profile_plaintext.blade.php b/resources/views/emails/notifications/new-comment-profile_plaintext.blade.php new file mode 100644 index 00000000..8bc4c58a --- /dev/null +++ b/resources/views/emails/notifications/new-comment-profile_plaintext.blade.php @@ -0,0 +1,8 @@ +@extends('emails.notifications._layout_plaintext') + +@section('content') +{{ $creatorName }} left a comment on your Pony.fm profile! + +Visit your profile with the following link to read it and reply: +{{ $notificationUrl }} +@endsection diff --git a/resources/views/emails/notifications/new-follower.blade.php b/resources/views/emails/notifications/new-follower.blade.php new file mode 100644 index 00000000..582fd04e --- /dev/null +++ b/resources/views/emails/notifications/new-follower.blade.php @@ -0,0 +1,9 @@ +@extends('emails.notifications._layout') + +@section('content') +

+ Congrats! + {{ $creatorName }} + is now following you on Pony.fm! +

+@endsection diff --git a/resources/views/emails/notifications/new-follower_plaintext.blade.php b/resources/views/emails/notifications/new-follower_plaintext.blade.php new file mode 100644 index 00000000..88d02d59 --- /dev/null +++ b/resources/views/emails/notifications/new-follower_plaintext.blade.php @@ -0,0 +1,8 @@ +@extends('emails.notifications._layout_plaintext') + +@section('content') +Congrats! {{ $creatorName }} is now following you on Pony.fm! + +Here's a link to their profile: +{{ $notificationUrl }} +@endsection diff --git a/resources/views/emails/notifications/new-playlist.blade.php b/resources/views/emails/notifications/new-playlist.blade.php new file mode 100644 index 00000000..79d857f9 --- /dev/null +++ b/resources/views/emails/notifications/new-playlist.blade.php @@ -0,0 +1,8 @@ +@extends('emails.notifications._layout') + +@section('content') +

{{ $creatorName }} created a new playlist on Pony.fm! Check it out:

+ +

{{ $playlistTitle }}

+ +@endsection diff --git a/resources/views/emails/notifications/new-playlist_plaintext.blade.php b/resources/views/emails/notifications/new-playlist_plaintext.blade.php new file mode 100644 index 00000000..cab8d9d0 --- /dev/null +++ b/resources/views/emails/notifications/new-playlist_plaintext.blade.php @@ -0,0 +1,10 @@ +@extends('emails.notifications._layout_plaintext') + +@section('content') +{{ $creatorName }} created a new playlist on Pony.fm! + +Title: {{ $playlistTitle }} + +Listen to it: +{{ $notificationUrl }} +@endsection diff --git a/resources/views/emails/notifications/new-track.blade.php b/resources/views/emails/notifications/new-track.blade.php new file mode 100644 index 00000000..f5a7d660 --- /dev/null +++ b/resources/views/emails/notifications/new-track.blade.php @@ -0,0 +1,8 @@ +@extends('emails.notifications._layout') + +@section('content') +

{{ $creatorName }} published a new track on Pony.fm! Listen to it now:

+ +

{{ $trackTitle }}

+ +@endsection diff --git a/resources/views/emails/notifications/new-track_plaintext.blade.php b/resources/views/emails/notifications/new-track_plaintext.blade.php new file mode 100644 index 00000000..89f01e4e --- /dev/null +++ b/resources/views/emails/notifications/new-track_plaintext.blade.php @@ -0,0 +1,10 @@ +@extends('emails.notifications._layout_plaintext') + +@section('content') +{{ $creatorName }} published a new track on Pony.fm! + +Title: {{ $trackTitle }} + +Listen to it: +{{ $notificationUrl }} +@endsection