#25: Added email templates for all currently implemented notification types.

This commit is contained in:
Peter Deltchev 2016-12-23 05:08:41 -08:00
parent 5822408655
commit 78b22cdfd0
26 changed files with 423 additions and 42 deletions

View file

@ -20,10 +20,9 @@
namespace Poniverse\Ponyfm\Http\Controllers; namespace Poniverse\Ponyfm\Http\Controllers;
use DB;
use Poniverse\Ponyfm\Models\Email; use Poniverse\Ponyfm\Models\Email;
use Poniverse\Ponyfm\Models\EmailClick;
use Poniverse\Ponyfm\Models\EmailSubscription; use Poniverse\Ponyfm\Models\EmailSubscription;
use View;
class NotificationsController extends Controller { class NotificationsController extends Controller {
public function getEmailClick($emailKey) { public function getEmailClick($emailKey) {
@ -31,7 +30,11 @@ class NotificationsController extends Controller {
/** @var Email $email */ /** @var Email $email */
$email = Email::findOrFail($emailKey); $email = Email::findOrFail($emailKey);
DB::transaction(function() use ($email) {
$email->emailClicks()->create(['ip_address' => \Request::ip()]); $email->emailClicks()->create(['ip_address' => \Request::ip()]);
$email->notification->is_read = true;
$email->notification->save();
});
return redirect($email->getActivity()->url); return redirect($email->getActivity()->url);
} }

View file

@ -20,11 +20,16 @@
namespace Poniverse\Ponyfm\Library\Notifications\Drivers; namespace Poniverse\Ponyfm\Library\Notifications\Drivers;
use ArrayAccess;
use Carbon\Carbon; use Carbon\Carbon;
use Log; use Log;
use Mail; use Mail;
use Poniverse\Ponyfm\Contracts\Favouritable; 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\Activity;
use Poniverse\Ponyfm\Models\Comment; use Poniverse\Ponyfm\Models\Comment;
use Poniverse\Ponyfm\Models\Email; use Poniverse\Ponyfm\Models\Email;
@ -63,8 +68,9 @@ class PonyfmDriver extends AbstractDriver
foreach ($recipients as $recipient) { foreach ($recipients as $recipient) {
$notification = $activity->notifications->where('user_id', $recipient->id)->first(); $notification = $activity->notifications->where('user_id', $recipient->id)->first();
$email = $notification->email()->create([]); $email = $notification->email()->create([]);
Log::debug("Attempting to send an email about notification {$notification->id} to {$recipient->email}."); 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()); $recipientsQuery = $this->getRecipients(__FUNCTION__, func_get_args());
$this->insertNotifications($activity, $recipientsQuery->get()); $this->insertNotifications($activity, $recipientsQuery->get());
$this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_PUBLISHED_TRACK)->get()); $this->sendEmails($activity, $recipientsQuery->withEmailSubscriptionFor(Activity::TYPE_PUBLISHED_TRACK)->get());
} }
@ -100,7 +105,9 @@ class PonyfmDriver extends AbstractDriver
'resource_id' => $playlist->id, '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) public function newFollower(User $userBeingFollowed, User $follower)
@ -113,7 +120,9 @@ class PonyfmDriver extends AbstractDriver
'resource_id' => $userBeingFollowed->id, '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, '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, '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());
} }
} }

View file

@ -23,6 +23,7 @@ namespace Poniverse\Ponyfm\Mail;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Poniverse\Ponyfm\Models\Activity;
use Poniverse\Ponyfm\Models\Email; use Poniverse\Ponyfm\Models\Email;
abstract class BaseNotification extends Mailable { abstract class BaseNotification extends Mailable {
@ -52,6 +53,36 @@ abstract class BaseNotification extends Mailable {
$this->initiatingUser = $email->notification->activity->initiatingUser; $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. * Build the message.
* *
@ -80,8 +111,11 @@ abstract class BaseNotification extends Mailable {
} }
/** /**
* Helper method to eliminate duplication between different types of notifications. * Helper method to eliminate duplication between different types of
* Use it inside the build() method on this class's children. * 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 $templateName
* @param string $subject * @param string $subject
@ -91,8 +125,8 @@ abstract class BaseNotification extends Mailable {
protected function renderEmail(string $templateName, string $subject, array $extraVariables) { protected function renderEmail(string $templateName, string $subject, array $extraVariables) {
return $this return $this
->subject($subject) ->subject($subject)
->view("emails.{$templateName}") ->view("emails.notifications.{$templateName}")
->text("emails.{$templateName}_plaintext") ->text("emails.notifications.{$templateName}_plaintext")
->with(array_merge($extraVariables, [ ->with(array_merge($extraVariables, [
'notificationUrl' => $this->generateNotificationUrl(), 'notificationUrl' => $this->generateNotificationUrl(),
'unsubscribeUrl' => $this->generateUnsubscribeUrl() 'unsubscribeUrl' => $this->generateUnsubscribeUrl()

View file

@ -0,0 +1,40 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2016 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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,
]);
}
}

53
app/Mail/NewComment.php Normal file
View file

@ -0,0 +1,53 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2016 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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,
]);
}
}
}

39
app/Mail/NewFollower.php Normal file
View file

@ -0,0 +1,39 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2016 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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,
]);
}
}

41
app/Mail/NewPlaylist.php Normal file
View file

@ -0,0 +1,41 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2016 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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,
]);
}
}

View file

@ -1,24 +1,40 @@
<?php <?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2016 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Poniverse\Ponyfm\Mail; namespace Poniverse\Ponyfm\Mail;
class NewTrack extends BaseNotification class NewTrack extends BaseNotification
{ {
/** /**
* Build the message. * @inheritdoc
*
* @return $this
*/ */
public function build() public function build()
{ {
$artistName = $this->initiatingUser->display_name; $creatorName = $this->initiatingUser->display_name;
$trackTitle = $this->activityRecord->resource->title; $trackTitle = $this->activityRecord->resource->title;
return $this->renderEmail( return $this->renderEmail(
'new-track', 'new-track',
"{$artistName} published \"{$trackTitle}\"", "{$creatorName} published \"{$trackTitle}\"!",
[ [
'artist' => $artistName, 'creatorName' => $creatorName,
'trackTitle' => $trackTitle, 'trackTitle' => $trackTitle,
]); ]);
} }

View file

@ -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 * @return string human-readable Markdown string describing this notification
* @throws \Exception * @throws \Exception
*/ */
@ -237,11 +262,11 @@ class Activity extends Model
// Must be a content comment. // Must be a content comment.
} else { } 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: 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: default:
throw new \Exception('This activity\'s activity type is unknown!'); throw new \Exception('This activity\'s activity type is unknown!');

View file

@ -22,7 +22,7 @@ $track = new Track();
... ...
// The "something" is done happening! Time to send a notification. // 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 This facade has a method for every notification type, drawn from the
@ -45,8 +45,13 @@ Adding new notification types
- [`RecipientFinder`](../app/Library/Notifications/RecipientFinder.php) - [`RecipientFinder`](../app/Library/Notifications/RecipientFinder.php)
- [`PonyfmDriver`](../app/Library/Notifications/PonyfmDriver.php) - [`PonyfmDriver`](../app/Library/Notifications/PonyfmDriver.php)
3. Ensure you create HTML and plaintext templates for the email version of the 3. Create a migration to add the new notification type to the `activity_types`
notification. 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 4. Call the new method on the `Notification` facade from wherever the
new notification gets triggered. new notification gets triggered.

View file

@ -1,5 +0,0 @@
<p>{{ $artist }} published a new track on Pony.fm! Listen to it now:</p>
<p><a href="{{ $notificationUrl }}" target="_blank">{{ $trackTitle }}</a></p>
<p><a href="{{ $unsubscribeUrl }}" target="_blank">Unsubscribe from email notifications for new tracks</a></p>

View file

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

View file

@ -0,0 +1,13 @@
@yield('content')
<hr>
<p><a href="{{ $unsubscribeUrl }}" target="_blank">Unsubscribe from this kind of email</a></p>
<address style="font-size:85%;">
Poniverse<br>
248-1641 Lonsdale Avenue<br>
North Vancouver<br>
BC V7M 2J5<br>
Canada
</address>

View file

@ -0,0 +1,10 @@
@yield('content')
---
Unsubscribe from this kind of email:
{{ $unsubscribeUrl }}
Poniverse
248-1641 Lonsdale Avenue
North Vancouver
BC V7M 2J5

View file

@ -0,0 +1,9 @@
@extends('emails.notifications._layout')
@section('content')
<p>
{{ $creatorName }} favourited your {{ $resourceType }},
<em><a href="{{ $notificationUrl }}" target="_blank">{{ $resourceTitle }}</a></em>
Yay!
</p>
@endsection

View file

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

View file

@ -0,0 +1,9 @@
@extends('emails.notifications._layout')
@section('content')
<p>
{{ $creatorName }} left a comment on your {{ $resourceType }},
<a href="{{ $notificationUrl }}" target="_blank"><em>{{ $resourceTitle }}</em></a>!
<a href="{{ $notificationUrl }}" target="_blank">Visit it</a> to read the comment and reply.
</p>
@endsection

View file

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

View file

@ -0,0 +1,9 @@
@extends('emails.notifications._layout')
@section('content')
<p>
{{ $creatorName }} left a comment on your Pony.fm profile!
<a href="{{ $notificationUrl }}" target="_blank">Visit your profile</a> to
read it and reply.
</p>
@endsection

View file

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

View file

@ -0,0 +1,9 @@
@extends('emails.notifications._layout')
@section('content')
<p>
Congrats!
<a href="{{ $notificationUrl }}" target="_blank">{{ $creatorName }}</a>
is now following you on Pony.fm!
</p>
@endsection

View file

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

View file

@ -0,0 +1,8 @@
@extends('emails.notifications._layout')
@section('content')
<p>{{ $creatorName }} created a new playlist on Pony.fm! Check it out:</p>
<p><a href="{{ $notificationUrl }}" target="_blank">{{ $playlistTitle }}</a></p>
@endsection

View file

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

View file

@ -0,0 +1,8 @@
@extends('emails.notifications._layout')
@section('content')
<p>{{ $creatorName }} published a new track on Pony.fm! Listen to it now:</p>
<p><a href="{{ $notificationUrl }}" target="_blank">{{ $trackTitle }}</a></p>
@endsection

View file

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