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

View file

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

View file

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

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
/**
* 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 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,
]);
}

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

View file

@ -22,7 +22,7 @@ $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
@ -45,8 +45,13 @@ Adding new notification types
- [`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.

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