| 199 |
lars |
1 |
<?php namespace Clockwork\DataSource;
|
|
|
2 |
|
|
|
3 |
use Clockwork\Helpers\Serializer;
|
|
|
4 |
use Clockwork\Helpers\StackTrace;
|
|
|
5 |
use Clockwork\Request\Request;
|
|
|
6 |
|
|
|
7 |
use Illuminate\Contracts\Events\Dispatcher;
|
|
|
8 |
use Illuminate\Mail\Mailable;
|
|
|
9 |
use Illuminate\Mail\Events\MessageSending;
|
|
|
10 |
use Illuminate\Mail\Events\MessageSent;
|
|
|
11 |
use Illuminate\Notifications\Events\NotificationSending;
|
|
|
12 |
use Illuminate\Notifications\Events\NotificationSent;
|
|
|
13 |
|
|
|
14 |
// Data source for Laravel notifications and mail components, provides sent notifications and emails
|
|
|
15 |
class LaravelNotificationsDataSource extends DataSource
|
|
|
16 |
{
|
|
|
17 |
// Event dispatcher instance
|
|
|
18 |
protected $dispatcher;
|
|
|
19 |
|
|
|
20 |
// Sent notifications
|
|
|
21 |
protected $notifications = [];
|
|
|
22 |
|
|
|
23 |
// Last collected notification
|
|
|
24 |
protected $lastNotification;
|
|
|
25 |
|
|
|
26 |
// Create a new data source instance, takes an event dispatcher as argument
|
|
|
27 |
public function __construct(Dispatcher $dispatcher)
|
|
|
28 |
{
|
|
|
29 |
$this->dispatcher = $dispatcher;
|
|
|
30 |
}
|
|
|
31 |
|
|
|
32 |
// Add sent notifications to the request
|
|
|
33 |
public function resolve(Request $request)
|
|
|
34 |
{
|
|
|
35 |
$request->notifications = array_merge($request->notifications, $this->notifications);
|
|
|
36 |
|
|
|
37 |
return $request;
|
|
|
38 |
}
|
|
|
39 |
|
|
|
40 |
// Reset the data source to an empty state, clearing any collected data
|
|
|
41 |
public function reset()
|
|
|
42 |
{
|
|
|
43 |
$this->notifications = [];
|
|
|
44 |
}
|
|
|
45 |
|
|
|
46 |
// Listen to the email and notification events
|
|
|
47 |
public function listenToEvents()
|
|
|
48 |
{
|
|
|
49 |
$this->dispatcher->listen(MessageSending::class, function ($event) { $this->sendingMessage($event); });
|
|
|
50 |
$this->dispatcher->listen(MessageSent::class, function ($event) { $this->sentMessage($event); });
|
|
|
51 |
|
|
|
52 |
$this->dispatcher->listen(NotificationSending::class, function ($event) { $this->sendingNotification($event); });
|
|
|
53 |
$this->dispatcher->listen(NotificationSent::class, function ($event) { $this->sentNotification($event); });
|
|
|
54 |
}
|
|
|
55 |
|
|
|
56 |
// Collect a sent email
|
|
|
57 |
protected function sendingMessage($event)
|
|
|
58 |
{
|
|
|
59 |
$trace = StackTrace::get()->resolveViewName();
|
|
|
60 |
|
|
|
61 |
$mailable = ($frame = $trace->first(function ($frame) { return is_subclass_of($frame->object, Mailable::class); }))
|
|
|
62 |
? $frame->object : null;
|
|
|
63 |
|
|
|
64 |
$notification = (object) [
|
|
|
65 |
'subject' => $event->message->getSubject(),
|
|
|
66 |
'from' => $this->messageAddressToString($event->message->getFrom()),
|
|
|
67 |
'to' => $this->messageAddressToString($event->message->getTo()),
|
|
|
68 |
'content' => $this->messageBody($event->message),
|
|
|
69 |
'type' => 'mail',
|
|
|
70 |
'data' => [
|
|
|
71 |
'cc' => $this->messageAddressToString($event->message->getCc()),
|
|
|
72 |
'bcc' => $this->messageAddressToString($event->message->getBcc()),
|
|
|
73 |
'replyTo' => $this->messageAddressToString($event->message->getReplyTo()),
|
|
|
74 |
'mailable' => (new Serializer)->normalize($mailable)
|
|
|
75 |
],
|
|
|
76 |
'time' => microtime(true),
|
|
|
77 |
'trace' => (new Serializer)->trace($trace)
|
|
|
78 |
];
|
|
|
79 |
|
|
|
80 |
if ($this->updateLastNotification($notification)) return;
|
|
|
81 |
|
|
|
82 |
if ($this->passesFilters([ $notification ])) {
|
|
|
83 |
$this->notifications[] = $this->lastNotification = $notification;
|
|
|
84 |
} else {
|
|
|
85 |
$this->lastNotification = null;
|
|
|
86 |
}
|
|
|
87 |
}
|
|
|
88 |
|
|
|
89 |
// Update last notification with time taken to send it
|
|
|
90 |
protected function sentMessage($event)
|
|
|
91 |
{
|
|
|
92 |
if ($this->lastNotification) {
|
|
|
93 |
$this->lastNotification->duration = (microtime(true) - $this->lastNotification->time) * 1000;
|
|
|
94 |
}
|
|
|
95 |
}
|
|
|
96 |
|
|
|
97 |
// Collect a sent notification
|
|
|
98 |
protected function sendingNotification($event)
|
|
|
99 |
{
|
|
|
100 |
$trace = StackTrace::get()->resolveViewName();
|
|
|
101 |
|
|
|
102 |
$channelSpecific = $this->resolveChannelSpecific($event);
|
|
|
103 |
|
|
|
104 |
$notification = (object) [
|
|
|
105 |
'subject' => $channelSpecific['subject'],
|
|
|
106 |
'from' => $channelSpecific['from'],
|
|
|
107 |
'to' => $channelSpecific['to'],
|
|
|
108 |
'content' => $channelSpecific['content'],
|
|
|
109 |
'type' => $event->channel,
|
|
|
110 |
'data' => array_merge($channelSpecific['data'], [
|
|
|
111 |
'notification' => (new Serializer)->normalize($event->notification),
|
|
|
112 |
'notifiable' => (new Serializer)->normalize($event->notifiable)
|
|
|
113 |
]),
|
|
|
114 |
'time' => microtime(true),
|
|
|
115 |
'trace' => (new Serializer)->trace($trace)
|
|
|
116 |
];
|
|
|
117 |
|
|
|
118 |
if ($this->passesFilters([ $notification ])) {
|
|
|
119 |
$this->notifications[] = $this->lastNotification = $notification;
|
|
|
120 |
} else {
|
|
|
121 |
$this->lastNotification = null;
|
|
|
122 |
}
|
|
|
123 |
}
|
|
|
124 |
|
|
|
125 |
// Update last notification with time taken to send it and response
|
|
|
126 |
protected function sentNotification($event)
|
|
|
127 |
{
|
|
|
128 |
if ($this->lastNotification) {
|
|
|
129 |
$this->lastNotification->duration = (microtime(true) - $this->lastNotification->time) * 1000;
|
|
|
130 |
$this->lastNotification->data['response'] = $event->response;
|
|
|
131 |
}
|
|
|
132 |
}
|
|
|
133 |
|
|
|
134 |
// Update last sent email notification with additional data from the message sent event
|
|
|
135 |
protected function updateLastNotification($notification)
|
|
|
136 |
{
|
|
|
137 |
if (! $this->lastNotification) return false;
|
|
|
138 |
|
|
|
139 |
if ($this->lastNotification->to !== $notification->to) return false;
|
|
|
140 |
|
|
|
141 |
$this->lastNotification->subject = $notification->subject;
|
|
|
142 |
$this->lastNotification->from = $notification->from;
|
|
|
143 |
$this->lastNotification->to = $notification->to;
|
|
|
144 |
$this->lastNotification->content = $notification->content;
|
|
|
145 |
|
|
|
146 |
$this->lastNotification->data = array_merge($this->lastNotification->data, $notification->data);
|
|
|
147 |
|
|
|
148 |
return true;
|
|
|
149 |
}
|
|
|
150 |
|
|
|
151 |
// Resolve notification channel specific data
|
|
|
152 |
protected function resolveChannelSpecific($event)
|
|
|
153 |
{
|
|
|
154 |
if (method_exists($event->notification, 'toMail')) {
|
|
|
155 |
$channelSpecific = $this->resolveMailChannelSpecific($event, $event->notification->toMail($event->notifiable));
|
|
|
156 |
} elseif (method_exists($event->notification, 'toSlack')) {
|
|
|
157 |
$channelSpecific = $this->resolveSlackChannelSpecific($event, $event->notification->toSlack($event->notifiable));
|
|
|
158 |
} elseif (method_exists($event->notification, 'toNexmo')) {
|
|
|
159 |
$channelSpecific = $this->resolveNexmoChannelSpecific($event, $event->notification->toNexmo($event->notifiable));
|
|
|
160 |
} elseif (method_exists($event->notification, 'toBroadcast')) {
|
|
|
161 |
$channelSpecific = [ 'data' => [ 'data' => (new Serializer)->normalize($event->notification->toBroadcast($event->notifiable)) ] ];
|
|
|
162 |
} elseif (method_exists($event->notification, 'toArray')) {
|
|
|
163 |
$channelSpecific = [ 'data' => [ 'data' => (new Serializer)->normalize($event->notification->toArray($event->notifiable)) ] ];
|
|
|
164 |
} else {
|
|
|
165 |
$channelSpecific = [];
|
|
|
166 |
}
|
|
|
167 |
|
|
|
168 |
return array_merge(
|
|
|
169 |
[ 'subject' => null, 'from' => null, 'to' => null, 'content' => null, 'data' => [] ], $channelSpecific
|
|
|
170 |
);
|
|
|
171 |
}
|
|
|
172 |
|
|
|
173 |
// Resolve mail notification channel specific data
|
|
|
174 |
protected function resolveMailChannelSpecific($event, $message)
|
|
|
175 |
{
|
|
|
176 |
return [
|
|
|
177 |
'subject' => $message->subject ?: get_class($event->notification),
|
|
|
178 |
'from' => $this->notificationAddressToString($message->from),
|
|
|
179 |
'to' => $this->notificationAddressToString($event->notifiable->routeNotificationFor('mail', $event->notification)),
|
|
|
180 |
'data' => [
|
|
|
181 |
'cc' => $this->notificationAddressToString($message->cc),
|
|
|
182 |
'bcc' => $this->notificationAddressToString($message->bcc),
|
|
|
183 |
'replyTo' => $this->notificationAddressToString($message->replyTo)
|
|
|
184 |
]
|
|
|
185 |
];
|
|
|
186 |
}
|
|
|
187 |
|
|
|
188 |
// Resolve Slack notification channel specific data
|
|
|
189 |
protected function resolveSlackChannelSpecific($event, $message)
|
|
|
190 |
{
|
|
|
191 |
return [
|
|
|
192 |
'subject' => get_class($event->notification),
|
|
|
193 |
'from' => $message->username,
|
|
|
194 |
'to' => $message->channel,
|
|
|
195 |
'content' => $message->content
|
|
|
196 |
];
|
|
|
197 |
}
|
|
|
198 |
|
|
|
199 |
// Resolve Nexmo notification channel specific data
|
|
|
200 |
protected function resolveNexmoChannelSpecific($event, $message)
|
|
|
201 |
{
|
|
|
202 |
return [
|
|
|
203 |
'subject' => get_class($event->notification),
|
|
|
204 |
'from' => $message->from,
|
|
|
205 |
'to' => $event->notifiable->routeNotificationFor('nexmo', $event->notification),
|
|
|
206 |
'content' => $message->content
|
|
|
207 |
];
|
|
|
208 |
}
|
|
|
209 |
|
|
|
210 |
protected function messageAddressToString($address)
|
|
|
211 |
{
|
|
|
212 |
if (! $address) return;
|
|
|
213 |
|
|
|
214 |
return array_map(function ($address, $key) {
|
|
|
215 |
// Laravel 8 or earlier
|
|
|
216 |
if (! ($address instanceof \Symfony\Component\Mime\Address)) {
|
|
|
217 |
return $address ? "{$address} <{$key}>" : $key;
|
|
|
218 |
}
|
|
|
219 |
|
|
|
220 |
// Laravel 9 or later
|
|
|
221 |
return $address->toString();
|
|
|
222 |
}, $address, array_keys($address));
|
|
|
223 |
}
|
|
|
224 |
|
|
|
225 |
protected function messageBody($message)
|
|
|
226 |
{
|
|
|
227 |
// Laravel 8 or earlier
|
|
|
228 |
if (! ($message instanceof \Symfony\Component\Mime\Email)) {
|
|
|
229 |
return $message->getBody();
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
// Laravel 9 or later
|
|
|
233 |
return $message->getHtmlBody() ?: $message->getTextBody();
|
|
|
234 |
}
|
|
|
235 |
|
|
|
236 |
protected function notificationAddressToString($address)
|
|
|
237 |
{
|
|
|
238 |
if (! $address) return;
|
|
|
239 |
if (! is_array($address)) $address = [ $address ];
|
|
|
240 |
|
|
|
241 |
return array_map(function ($address) {
|
|
|
242 |
if (! is_array($address)) return $address;
|
|
|
243 |
|
|
|
244 |
$email = isset($address['address']) ? $address['address'] : $address[0];
|
|
|
245 |
$name = isset($address['name']) ? $address['name'] : $address[1];
|
|
|
246 |
|
|
|
247 |
return $name ? "{$name} <{$email}>" : $email;
|
|
|
248 |
}, $address);
|
|
|
249 |
}
|
|
|
250 |
}
|