251 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php namespace Clockwork\DataSource;
 | |
| 
 | |
| use Clockwork\Helpers\Serializer;
 | |
| use Clockwork\Helpers\StackTrace;
 | |
| use Clockwork\Request\Request;
 | |
| 
 | |
| use Illuminate\Contracts\Events\Dispatcher;
 | |
| use Illuminate\Mail\Mailable;
 | |
| use Illuminate\Mail\Events\MessageSending;
 | |
| use Illuminate\Mail\Events\MessageSent;
 | |
| use Illuminate\Notifications\Events\NotificationSending;
 | |
| use Illuminate\Notifications\Events\NotificationSent;
 | |
| 
 | |
| // Data source for Laravel notifications and mail components, provides sent notifications and emails
 | |
| class LaravelNotificationsDataSource extends DataSource
 | |
| {
 | |
| 	// Event dispatcher instance
 | |
| 	protected $dispatcher;
 | |
| 
 | |
| 	// Sent notifications
 | |
| 	protected $notifications = [];
 | |
| 
 | |
| 	// Last collected notification
 | |
| 	protected $lastNotification;
 | |
| 
 | |
| 	// Create a new data source instance, takes an event dispatcher as argument
 | |
| 	public function __construct(Dispatcher $dispatcher)
 | |
| 	{
 | |
| 		$this->dispatcher = $dispatcher;
 | |
| 	}
 | |
| 
 | |
| 	// Add sent notifications to the request
 | |
| 	public function resolve(Request $request)
 | |
| 	{
 | |
| 		$request->notifications = array_merge($request->notifications, $this->notifications);
 | |
| 
 | |
| 		return $request;
 | |
| 	}
 | |
| 
 | |
| 	// Reset the data source to an empty state, clearing any collected data
 | |
| 	public function reset()
 | |
| 	{
 | |
| 		$this->notifications = [];
 | |
| 	}
 | |
| 
 | |
| 	// Listen to the email and notification events
 | |
| 	public function listenToEvents()
 | |
| 	{
 | |
| 		$this->dispatcher->listen(MessageSending::class, function ($event) { $this->sendingMessage($event); });
 | |
| 		$this->dispatcher->listen(MessageSent::class, function ($event) { $this->sentMessage($event); });
 | |
| 
 | |
| 		$this->dispatcher->listen(NotificationSending::class, function ($event) { $this->sendingNotification($event); });
 | |
| 		$this->dispatcher->listen(NotificationSent::class, function ($event) { $this->sentNotification($event); });
 | |
| 	}
 | |
| 
 | |
| 	// Collect a sent email
 | |
| 	protected function sendingMessage($event)
 | |
| 	{
 | |
| 		$trace = StackTrace::get()->resolveViewName();
 | |
| 
 | |
| 		$mailable = ($frame = $trace->first(function ($frame) { return is_subclass_of($frame->object, Mailable::class); }))
 | |
| 			? $frame->object : null;
 | |
| 
 | |
| 		$notification = (object) [
 | |
| 			'subject' => $event->message->getSubject(),
 | |
| 			'from'    => $this->messageAddressToString($event->message->getFrom()),
 | |
| 			'to'      => $this->messageAddressToString($event->message->getTo()),
 | |
| 			'content' => $this->messageBody($event->message),
 | |
| 			'type'    => 'mail',
 | |
| 			'data'    => [
 | |
| 				'cc'       => $this->messageAddressToString($event->message->getCc()),
 | |
| 				'bcc'      => $this->messageAddressToString($event->message->getBcc()),
 | |
| 				'replyTo'  => $this->messageAddressToString($event->message->getReplyTo()),
 | |
| 				'mailable' => (new Serializer)->normalize($mailable)
 | |
| 			],
 | |
| 			'time'    => microtime(true),
 | |
| 			'trace'   => (new Serializer)->trace($trace)
 | |
| 		];
 | |
| 
 | |
| 		if ($this->updateLastNotification($notification)) return;
 | |
| 
 | |
| 		if ($this->passesFilters([ $notification ])) {
 | |
| 			$this->notifications[] = $this->lastNotification = $notification;
 | |
| 		} else {
 | |
| 			$this->lastNotification = null;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Update last notification with time taken to send it
 | |
| 	protected function sentMessage($event)
 | |
| 	{
 | |
| 		if ($this->lastNotification) {
 | |
| 			$this->lastNotification->duration = (microtime(true) - $this->lastNotification->time) * 1000;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Collect a sent notification
 | |
| 	protected function sendingNotification($event)
 | |
| 	{
 | |
| 		$trace = StackTrace::get()->resolveViewName();
 | |
| 
 | |
| 		$channelSpecific = $this->resolveChannelSpecific($event);
 | |
| 
 | |
| 		$notification = (object) [
 | |
| 			'subject' => $channelSpecific['subject'],
 | |
| 			'from'    => $channelSpecific['from'],
 | |
| 			'to'      => $channelSpecific['to'],
 | |
| 			'content' => $channelSpecific['content'],
 | |
| 			'type'    => $event->channel,
 | |
| 			'data'    => array_merge($channelSpecific['data'], [
 | |
| 				'notification' => (new Serializer)->normalize($event->notification),
 | |
| 				'notifiable'   => (new Serializer)->normalize($event->notifiable)
 | |
| 			]),
 | |
| 			'time'    => microtime(true),
 | |
| 			'trace'   => (new Serializer)->trace($trace)
 | |
| 		];
 | |
| 
 | |
| 		if ($this->passesFilters([ $notification ])) {
 | |
| 			$this->notifications[] = $this->lastNotification = $notification;
 | |
| 		} else {
 | |
| 			$this->lastNotification = null;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Update last notification with time taken to send it and response
 | |
| 	protected function sentNotification($event)
 | |
| 	{
 | |
| 		if ($this->lastNotification) {
 | |
| 			$this->lastNotification->duration = (microtime(true) - $this->lastNotification->time) * 1000;
 | |
| 			$this->lastNotification->data['response'] = $event->response;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Update last sent email notification with additional data from the message sent event
 | |
| 	protected function updateLastNotification($notification)
 | |
| 	{
 | |
| 		if (! $this->lastNotification) return false;
 | |
| 
 | |
| 		if ($this->lastNotification->to !== $notification->to) return false;
 | |
| 
 | |
| 		$this->lastNotification->subject = $notification->subject;
 | |
| 		$this->lastNotification->from    = $notification->from;
 | |
| 		$this->lastNotification->to      = $notification->to;
 | |
| 		$this->lastNotification->content = $notification->content;
 | |
| 
 | |
| 		$this->lastNotification->data = array_merge($this->lastNotification->data, $notification->data);
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	// Resolve notification channel specific data
 | |
| 	protected function resolveChannelSpecific($event)
 | |
| 	{
 | |
| 		if (method_exists($event->notification, 'toMail')) {
 | |
| 			$channelSpecific = $this->resolveMailChannelSpecific($event, $event->notification->toMail($event->notifiable));
 | |
| 		} elseif (method_exists($event->notification, 'toSlack')) {
 | |
| 			$channelSpecific = $this->resolveSlackChannelSpecific($event, $event->notification->toSlack($event->notifiable));
 | |
| 		} elseif (method_exists($event->notification, 'toNexmo')) {
 | |
| 			$channelSpecific = $this->resolveNexmoChannelSpecific($event, $event->notification->toNexmo($event->notifiable));
 | |
| 		} elseif (method_exists($event->notification, 'toBroadcast')) {
 | |
| 			$channelSpecific = [ 'data' => [ 'data' => (new Serializer)->normalize($event->notification->toBroadcast($event->notifiable)) ] ];
 | |
| 		} elseif (method_exists($event->notification, 'toArray')) {
 | |
| 			$channelSpecific = [ 'data' => [ 'data' => (new Serializer)->normalize($event->notification->toArray($event->notifiable)) ] ];
 | |
| 		} else {
 | |
| 			$channelSpecific = [];
 | |
| 		}
 | |
| 
 | |
| 		return array_merge(
 | |
| 			[ 'subject' => null, 'from' => null, 'to' => null, 'content' => null, 'data' => [] ], $channelSpecific
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	// Resolve mail notification channel specific data
 | |
| 	protected function resolveMailChannelSpecific($event, $message)
 | |
| 	{
 | |
| 		return [
 | |
| 			'subject' => $message->subject ?: get_class($event->notification),
 | |
| 			'from'    => $this->notificationAddressToString($message->from),
 | |
| 			'to'      => $this->notificationAddressToString($event->notifiable->routeNotificationFor('mail', $event->notification)),
 | |
| 			'data'    => [
 | |
| 				'cc'      => $this->notificationAddressToString($message->cc),
 | |
| 				'bcc'     => $this->notificationAddressToString($message->bcc),
 | |
| 				'replyTo' => $this->notificationAddressToString($message->replyTo)
 | |
| 			]
 | |
| 		];
 | |
| 	}
 | |
| 
 | |
| 	// Resolve Slack notification channel specific data
 | |
| 	protected function resolveSlackChannelSpecific($event, $message)
 | |
| 	{
 | |
| 		return [
 | |
| 			'subject' => get_class($event->notification),
 | |
| 			'from'    => $message->username,
 | |
| 			'to'      => $message->channel,
 | |
| 			'content' => $message->content
 | |
| 		];
 | |
| 	}
 | |
| 
 | |
| 	// Resolve Nexmo notification channel specific data
 | |
| 	protected function resolveNexmoChannelSpecific($event, $message)
 | |
| 	{
 | |
| 		return [
 | |
| 			'subject' => get_class($event->notification),
 | |
| 			'from'    => $message->from,
 | |
| 			'to'      => $event->notifiable->routeNotificationFor('nexmo', $event->notification),
 | |
| 			'content' => $message->content
 | |
| 		];
 | |
| 	}
 | |
| 
 | |
| 	protected function messageAddressToString($address)
 | |
| 	{
 | |
| 		if (! $address) return;
 | |
| 
 | |
| 		return array_map(function ($address, $key) {
 | |
| 			// Laravel 8 or earlier
 | |
| 			if (! ($address instanceof \Symfony\Component\Mime\Address)) {
 | |
| 				return $address ? "{$address} <{$key}>" : $key;
 | |
| 			}
 | |
| 
 | |
| 			// Laravel 9 or later
 | |
| 			return $address->toString();
 | |
| 		}, $address, array_keys($address));
 | |
| 	}
 | |
| 
 | |
| 	protected function messageBody($message)
 | |
| 	{
 | |
| 		// Laravel 8 or earlier
 | |
| 		if (! ($message instanceof \Symfony\Component\Mime\Email)) {
 | |
| 			return $message->getBody();
 | |
| 		}
 | |
| 
 | |
| 		// Laravel 9 or later
 | |
| 		return $message->getHtmlBody() ?: $message->getTextBody();
 | |
| 	}
 | |
| 
 | |
| 	protected function notificationAddressToString($address)
 | |
| 	{
 | |
| 		if (! $address) return;
 | |
| 		if (! is_array($address)) $address = [ $address ];
 | |
| 
 | |
| 		return array_map(function ($address) {
 | |
| 			if (! is_array($address)) return $address;
 | |
| 
 | |
| 			$email = isset($address['address']) ? $address['address'] : $address[0];
 | |
| 			$name = isset($address['name']) ? $address['name'] : $address[1];
 | |
| 
 | |
| 			return $name ? "{$name} <{$email}>" : $email;
 | |
| 		}, $address);
 | |
| 	}
 | |
| }
 | 
