Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -0,0 +1,172 @@
<?php
namespace Illuminate\Mail;
use Closure;
use Illuminate\Container\Container;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
use Illuminate\Support\Traits\Macroable;
class Attachment
{
use Macroable;
/**
* The attached file's filename.
*
* @var string|null
*/
public $as;
/**
* The attached file's mime type.
*
* @var string|null
*/
public $mime;
/**
* A callback that attaches the attachment to the mail message.
*
* @var \Closure
*/
protected $resolver;
/**
* Create a mail attachment.
*
* @param \Closure $resolver
* @return void
*/
private function __construct(Closure $resolver)
{
$this->resolver = $resolver;
}
/**
* Create a mail attachment from a path.
*
* @param string $path
* @return static
*/
public static function fromPath($path)
{
return new static(fn ($attachment, $pathStrategy) => $pathStrategy($path, $attachment));
}
/**
* Create a mail attachment from in-memory data.
*
* @param \Closure $data
* @param string $name
* @return static
*/
public static function fromData(Closure $data, $name)
{
return (new static(
fn ($attachment, $pathStrategy, $dataStrategy) => $dataStrategy($data, $attachment)
))->as($name);
}
/**
* Create a mail attachment from a file in the default storage disk.
*
* @param string $path
* @return static
*/
public static function fromStorage($path)
{
return static::fromStorageDisk(null, $path);
}
/**
* Create a mail attachment from a file in the specified storage disk.
*
* @param string|null $disk
* @param string $path
* @return static
*/
public static function fromStorageDisk($disk, $path)
{
return new static(function ($attachment, $pathStrategy, $dataStrategy) use ($disk, $path) {
$storage = Container::getInstance()->make(
FilesystemFactory::class
)->disk($disk);
$attachment
->as($attachment->as ?? basename($path))
->withMime($attachment->mime ?? $storage->mimeType($path));
return $dataStrategy(fn () => $storage->get($path), $attachment);
});
}
/**
* Set the attached file's filename.
*
* @param string $name
* @return $this
*/
public function as($name)
{
$this->as = $name;
return $this;
}
/**
* Set the attached file's mime type.
*
* @param string $mime
* @return $this
*/
public function withMime($mime)
{
$this->mime = $mime;
return $this;
}
/**
* Attach the attachment with the given strategies.
*
* @param \Closure $pathStrategy
* @param \Closure $dataStrategy
* @return mixed
*/
public function attachWith(Closure $pathStrategy, Closure $dataStrategy)
{
return ($this->resolver)($this, $pathStrategy, $dataStrategy);
}
/**
* Attach the attachment to a built-in mail type.
*
* @param \Illuminate\Mail\Mailable|\Illuminate\Mail\Message|\Illuminate\Notifications\Messages\MailMessage $mail
* @return mixed
*/
public function attachTo($mail)
{
return $this->attachWith(
fn ($path) => $mail->attach($path, ['as' => $this->as, 'mime' => $this->mime]),
fn ($data) => $mail->attachData($data(), $this->as, ['mime' => $this->mime])
);
}
/**
* Determine if the given attachment is equivalent to this attachment.
*
* @param \Illuminate\Mail\Attachment $attachment
* @return bool
*/
public function isEquivalent(Attachment $attachment)
{
return $this->attachWith(
fn ($path) => [$path, ['as' => $this->as, 'mime' => $this->mime]],
fn ($data) => [$data(), ['as' => $this->as, 'mime' => $this->mime]],
) === $attachment->attachWith(
fn ($path) => [$path, ['as' => $attachment->as, 'mime' => $attachment->mime]],
fn ($data) => [$data(), ['as' => $attachment->as, 'mime' => $attachment->mime]],
);
}
}

View File

@@ -2,23 +2,34 @@
namespace Illuminate\Mail\Events;
use Symfony\Component\Mime\Email;
class MessageSending
{
/**
* The Swift message instance.
* The Symfony Email instance.
*
* @var \Swift_Message
* @var \Symfony\Component\Mime\Email
*/
public $message;
/**
* The message data.
*
* @var array
*/
public $data;
/**
* Create a new event instance.
*
* @param \Swift_Message $message
* @param \Symfony\Component\Mime\Email $message
* @param array $data
* @return void
*/
public function __construct($message)
public function __construct(Email $message, array $data = [])
{
$this->data = $data;
$this->message = $message;
}
}

View File

@@ -2,23 +2,92 @@
namespace Illuminate\Mail\Events;
use Exception;
use Illuminate\Mail\SentMessage;
/**
* @property \Symfony\Component\Mime\Email $message
*/
class MessageSent
{
/**
* The Swift message instance.
* The message that was sent.
*
* @var \Swift_Message
* @var \Illuminate\Mail\SentMessage
*/
public $message;
public $sent;
/**
* The message data.
*
* @var array
*/
public $data;
/**
* Create a new event instance.
*
* @param \Swift_Message $message
* @param \Illuminate\Mail\SentMessage $message
* @param array $data
* @return void
*/
public function __construct($message)
public function __construct(SentMessage $message, array $data = [])
{
$this->message = $message;
$this->sent = $message;
$this->data = $data;
}
/**
* Get the serializable representation of the object.
*
* @return array
*/
public function __serialize()
{
$hasAttachments = collect($this->message->getAttachments())->isNotEmpty();
return $hasAttachments ? [
'sent' => base64_encode(serialize($this->sent)),
'data' => base64_encode(serialize($this->data)),
'hasAttachments' => true,
] : [
'sent' => $this->sent,
'data' => $this->data,
'hasAttachments' => false,
];
}
/**
* Marshal the object from its serialized data.
*
* @param array $data
* @return void
*/
public function __unserialize(array $data)
{
if (isset($data['hasAttachments']) && $data['hasAttachments'] === true) {
$this->sent = unserialize(base64_decode($data['sent']));
$this->data = unserialize(base64_decode($data['data']));
} else {
$this->sent = $data['sent'];
$this->data = $data['data'];
}
}
/**
* Dynamically get the original message.
*
* @param string $key
* @return mixed
*
* @throws \Exception
*/
public function __get($key)
{
if ($key === 'message') {
return $this->sent->getOriginalMessage();
}
throw new Exception('Unable to access undefined property on '.__CLASS__.': '.$key);
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,507 @@
<?php
namespace Illuminate\Mail;
use Aws\Ses\SesClient;
use Closure;
use Illuminate\Contracts\Mail\Factory as FactoryContract;
use Illuminate\Log\LogManager;
use Illuminate\Mail\Transport\ArrayTransport;
use Illuminate\Mail\Transport\LogTransport;
use Illuminate\Mail\Transport\SesTransport;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
use Symfony\Component\Mailer\Transport\Dsn;
use Symfony\Component\Mailer\Transport\FailoverTransport;
use Symfony\Component\Mailer\Transport\SendmailTransport;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
/**
* @mixin \Illuminate\Mail\Mailer
*/
class MailManager implements FactoryContract
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The array of resolved mailers.
*
* @var array
*/
protected $mailers = [];
/**
* The registered custom driver creators.
*
* @var array
*/
protected $customCreators = [];
/**
* Create a new Mail manager instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Get a mailer instance by name.
*
* @param string|null $name
* @return \Illuminate\Contracts\Mail\Mailer
*/
public function mailer($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->mailers[$name] = $this->get($name);
}
/**
* Get a mailer driver instance.
*
* @param string|null $driver
* @return \Illuminate\Mail\Mailer
*/
public function driver($driver = null)
{
return $this->mailer($driver);
}
/**
* Attempt to get the mailer from the local cache.
*
* @param string $name
* @return \Illuminate\Mail\Mailer
*/
protected function get($name)
{
return $this->mailers[$name] ?? $this->resolve($name);
}
/**
* Resolve the given mailer.
*
* @param string $name
* @return \Illuminate\Mail\Mailer
*
* @throws \InvalidArgumentException
*/
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Mailer [{$name}] is not defined.");
}
// Once we have created the mailer instance we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new Mailer(
$name,
$this->app['view'],
$this->createSymfonyTransport($config),
$this->app['events']
);
if ($this->app->bound('queue')) {
$mailer->setQueue($this->app['queue']);
}
// Next we will set all of the global addresses on this mailer, which allows
// for easy unification of all "from" addresses as well as easy debugging
// of sent messages since these will be sent to a single email address.
foreach (['from', 'reply_to', 'to', 'return_path'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
}
/**
* Create a new transport instance.
*
* @param array $config
* @return \Symfony\Component\Mailer\Transport\TransportInterface
*
* @throws \InvalidArgumentException
*/
public function createSymfonyTransport(array $config)
{
// Here we will check if the "transport" key exists and if it doesn't we will
// assume an application is still using the legacy mail configuration file
// format and use the "mail.driver" configuration option instead for BC.
$transport = $config['transport'] ?? $this->app['config']['mail.driver'];
if (isset($this->customCreators[$transport])) {
return call_user_func($this->customCreators[$transport], $config);
}
if (trim($transport ?? '') === '' || ! method_exists($this, $method = 'create'.ucfirst($transport).'Transport')) {
throw new InvalidArgumentException("Unsupported mail transport [{$transport}].");
}
return $this->{$method}($config);
}
/**
* Create an instance of the Symfony SMTP Transport driver.
*
* @param array $config
* @return \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport
*/
protected function createSmtpTransport(array $config)
{
$factory = new EsmtpTransportFactory;
$transport = $factory->create(new Dsn(
! empty($config['encryption']) && $config['encryption'] === 'tls' ? (($config['port'] == 465) ? 'smtps' : 'smtp') : '',
$config['host'],
$config['username'] ?? null,
$config['password'] ?? null,
$config['port'] ?? null,
$config
));
return $this->configureSmtpTransport($transport, $config);
}
/**
* Configure the additional SMTP driver options.
*
* @param \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport $transport
* @param array $config
* @return \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport
*/
protected function configureSmtpTransport(EsmtpTransport $transport, array $config)
{
$stream = $transport->getStream();
if ($stream instanceof SocketStream) {
if (isset($config['source_ip'])) {
$stream->setSourceIp($config['source_ip']);
}
if (isset($config['timeout'])) {
$stream->setTimeout($config['timeout']);
}
}
return $transport;
}
/**
* Create an instance of the Symfony Sendmail Transport driver.
*
* @param array $config
* @return \Symfony\Component\Mailer\Transport\SendmailTransport
*/
protected function createSendmailTransport(array $config)
{
return new SendmailTransport(
$config['path'] ?? $this->app['config']->get('mail.sendmail')
);
}
/**
* Create an instance of the Symfony Amazon SES Transport driver.
*
* @param array $config
* @return \Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport
*/
protected function createSesTransport(array $config)
{
$config = array_merge(
$this->app['config']->get('services.ses', []),
['version' => 'latest', 'service' => 'email'],
$config
);
$config = Arr::except($config, ['transport']);
return new SesTransport(
new SesClient($this->addSesCredentials($config)),
$config['options'] ?? []
);
}
/**
* Add the SES credentials to the configuration array.
*
* @param array $config
* @return array
*/
protected function addSesCredentials(array $config)
{
if (! empty($config['key']) && ! empty($config['secret'])) {
$config['credentials'] = Arr::only($config, ['key', 'secret', 'token']);
}
return Arr::except($config, ['token']);
}
/**
* Create an instance of the Symfony Mail Transport driver.
*
* @return \Symfony\Component\Mailer\Transport\SendmailTransport
*/
protected function createMailTransport()
{
return new SendmailTransport;
}
/**
* Create an instance of the Symfony Mailgun Transport driver.
*
* @param array $config
* @return \Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunApiTransport
*/
protected function createMailgunTransport(array $config)
{
$factory = new MailgunTransportFactory();
if (! isset($config['secret'])) {
$config = $this->app['config']->get('services.mailgun', []);
}
return $factory->create(new Dsn(
'mailgun+'.($config['scheme'] ?? 'https'),
$config['endpoint'] ?? 'default',
$config['secret'],
$config['domain']
));
}
/**
* Create an instance of the Symfony Postmark Transport driver.
*
* @param array $config
* @return \Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkApiTransport
*/
protected function createPostmarkTransport(array $config)
{
$factory = new PostmarkTransportFactory();
$options = isset($config['message_stream_id'])
? ['message_stream' => $config['message_stream_id']]
: [];
return $factory->create(new Dsn(
'postmark+api',
'default',
$config['token'] ?? $this->app['config']->get('services.postmark.token'),
null,
null,
$options
));
}
/**
* Create an instance of the Symfony Failover Transport driver.
*
* @param array $config
* @return \Symfony\Component\Mailer\Transport\FailoverTransport
*/
protected function createFailoverTransport(array $config)
{
$transports = [];
foreach ($config['mailers'] as $name) {
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Mailer [{$name}] is not defined.");
}
// Now, we will check if the "driver" key exists and if it does we will set
// the transport configuration parameter in order to offer compatibility
// with any Laravel <= 6.x application style mail configuration files.
$transports[] = $this->app['config']['mail.driver']
? $this->createSymfonyTransport(array_merge($config, ['transport' => $name]))
: $this->createSymfonyTransport($config);
}
return new FailoverTransport($transports);
}
/**
* Create an instance of the Log Transport driver.
*
* @param array $config
* @return \Illuminate\Mail\Transport\LogTransport
*/
protected function createLogTransport(array $config)
{
$logger = $this->app->make(LoggerInterface::class);
if ($logger instanceof LogManager) {
$logger = $logger->channel(
$config['channel'] ?? $this->app['config']->get('mail.log_channel')
);
}
return new LogTransport($logger);
}
/**
* Create an instance of the Array Transport Driver.
*
* @return \Illuminate\Mail\Transport\ArrayTransport
*/
protected function createArrayTransport()
{
return new ArrayTransport;
}
/**
* Set a global address on the mailer by type.
*
* @param \Illuminate\Mail\Mailer $mailer
* @param array $config
* @param string $type
* @return void
*/
protected function setGlobalAddress($mailer, array $config, string $type)
{
$address = Arr::get($config, $type, $this->app['config']['mail.'.$type]);
if (is_array($address) && isset($address['address'])) {
$mailer->{'always'.Str::studly($type)}($address['address'], $address['name']);
}
}
/**
* Get the mail connection configuration.
*
* @param string $name
* @return array
*/
protected function getConfig(string $name)
{
// Here we will check if the "driver" key exists and if it does we will use
// the entire mail configuration file as the "driver" config in order to
// provide "BC" for any Laravel <= 6.x style mail configuration files.
return $this->app['config']['mail.driver']
? $this->app['config']['mail']
: $this->app['config']["mail.mailers.{$name}"];
}
/**
* Get the default mail driver name.
*
* @return string
*/
public function getDefaultDriver()
{
// Here we will check if the "driver" key exists and if it does we will use
// that as the default driver in order to provide support for old styles
// of the Laravel mail configuration file for backwards compatibility.
return $this->app['config']['mail.driver'] ??
$this->app['config']['mail.default'];
}
/**
* Set the default mail driver name.
*
* @param string $name
* @return void
*/
public function setDefaultDriver(string $name)
{
if ($this->app['config']['mail.driver']) {
$this->app['config']['mail.driver'] = $name;
}
$this->app['config']['mail.default'] = $name;
}
/**
* Disconnect the given mailer and remove from local cache.
*
* @param string|null $name
* @return void
*/
public function purge($name = null)
{
$name = $name ?: $this->getDefaultDriver();
unset($this->mailers[$name]);
}
/**
* Register a custom transport creator Closure.
*
* @param string $driver
* @param \Closure $callback
* @return $this
*/
public function extend($driver, Closure $callback)
{
$this->customCreators[$driver] = $callback;
return $this;
}
/**
* Get the application instance used by the manager.
*
* @return \Illuminate\Contracts\Foundation\Application
*/
public function getApplication()
{
return $this->app;
}
/**
* Set the application instance used by the manager.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return $this
*/
public function setApplication($app)
{
$this->app = $app;
return $this;
}
/**
* Forget all of the resolved mailer instances.
*
* @return $this
*/
public function forgetMailers()
{
$this->mailers = [];
return $this;
}
/**
* Dynamically call the default driver instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->mailer()->$method(...$parameters);
}
}

View File

@@ -2,20 +2,11 @@
namespace Illuminate\Mail;
use Swift_Mailer;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class MailServiceProvider extends ServiceProvider
class MailServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
@@ -23,10 +14,7 @@ class MailServiceProvider extends ServiceProvider
*/
public function register()
{
$this->registerSwiftMailer();
$this->registerIlluminateMailer();
$this->registerMarkdownRenderer();
}
@@ -37,74 +25,12 @@ class MailServiceProvider extends ServiceProvider
*/
protected function registerIlluminateMailer()
{
$this->app->singleton('mailer', function ($app) {
$config = $app->make('config')->get('mail');
// Once we have create the mailer instance, we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new Mailer(
$app['view'], $app['swift.mailer'], $app['events']
);
if ($app->bound('queue')) {
$mailer->setQueue($app['queue']);
}
// Next we will set all of the global addresses on this mailer, which allows
// for easy unification of all "from" addresses as well as easy debugging
// of sent messages since they get be sent into a single email address.
foreach (['from', 'reply_to', 'to'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
$this->app->singleton('mail.manager', function ($app) {
return new MailManager($app);
});
}
/**
* Set a global address on the mailer by type.
*
* @param \Illuminate\Mail\Mailer $mailer
* @param array $config
* @param string $type
* @return void
*/
protected function setGlobalAddress($mailer, array $config, $type)
{
$address = Arr::get($config, $type);
if (is_array($address) && isset($address['address'])) {
$mailer->{'always'.Str::studly($type)}($address['address'], $address['name']);
}
}
/**
* Register the Swift Mailer instance.
*
* @return void
*/
public function registerSwiftMailer()
{
$this->registerSwiftTransport();
// Once we have the transporter registered, we will register the actual Swift
// mailer instance, passing in the transport instances, which allows us to
// override this transporter instances during app start-up if necessary.
$this->app->singleton('swift.mailer', function ($app) {
return new Swift_Mailer($app['swift.transport']->driver());
});
}
/**
* Register the Swift Transport instance.
*
* @return void
*/
protected function registerSwiftTransport()
{
$this->app->singleton('swift.transport', function ($app) {
return new TransportManager($app);
$this->app->bind('mailer', function ($app) {
return $app->make('mail.manager')->mailer();
});
}
@@ -139,7 +65,9 @@ class MailServiceProvider extends ServiceProvider
public function provides()
{
return [
'mailer', 'swift.mailer', 'swift.transport', Markdown::class,
'mail.manager',
'mailer',
Markdown::class,
];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
<?php
namespace Illuminate\Mail\Mailables;
class Address
{
/**
* The recipient's email address.
*
* @var string
*/
public $address;
/**
* The recipient's name.
*
* @var string|null
*/
public $name;
/**
* Create a new address instance.
*
* @param string $address
* @param string|null $name
* @return void
*/
public function __construct(string $address, string $name = null)
{
$this->address = $address;
$this->name = $name;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Illuminate\Mail\Mailables;
use Illuminate\Mail\Attachment as BaseAttachment;
class Attachment extends BaseAttachment
{
// Here for namespace consistency...
}

View File

@@ -0,0 +1,157 @@
<?php
namespace Illuminate\Mail\Mailables;
use Illuminate\Support\Traits\Conditionable;
class Content
{
use Conditionable;
/**
* The Blade view that should be rendered for the mailable.
*
* @var string|null
*/
public $view;
/**
* The Blade view that should be rendered for the mailable.
*
* Alternative syntax for "view".
*
* @var string|null
*/
public $html;
/**
* The Blade view that represents the text version of the message.
*
* @var string|null
*/
public $text;
/**
* The Blade view that represents the Markdown version of the message.
*
* @var string|null
*/
public $markdown;
/**
* The pre-rendered HTML of the message.
*
* @var string|null
*/
public $htmlString;
/**
* The message's view data.
*
* @var array
*/
public $with;
/**
* Create a new content definition.
*
* @param string|null $view
* @param string|null $html
* @param string|null $text
* @param string|null $markdown
* @param array $with
* @param string|null $htmlString
*
* @named-arguments-supported
*/
public function __construct(string $view = null, string $html = null, string $text = null, $markdown = null, array $with = [], string $htmlString = null)
{
$this->view = $view;
$this->html = $html;
$this->text = $text;
$this->markdown = $markdown;
$this->with = $with;
$this->htmlString = $htmlString;
}
/**
* Set the view for the message.
*
* @param string $view
* @return $this
*/
public function view(string $view)
{
$this->view = $view;
return $this;
}
/**
* Set the view for the message.
*
* @param string $view
* @return $this
*/
public function html(string $view)
{
return $this->view($view);
}
/**
* Set the plain text view for the message.
*
* @param string $view
* @return $this
*/
public function text(string $view)
{
$this->text = $view;
return $this;
}
/**
* Set the Markdown view for the message.
*
* @param string $view
* @return $this
*/
public function markdown(string $view)
{
$this->markdown = $view;
return $this;
}
/**
* Set the pre-rendered HTML for the message.
*
* @param string $html
* @return $this
*/
public function htmlString(string $html)
{
$this->htmlString = $html;
return $this;
}
/**
* Add a piece of view data to the message.
*
* @param string $key
* @param mixed|null $value
* @return $this
*/
public function with($key, $value = null)
{
if (is_array($key)) {
$this->with = array_merge($this->with, $key);
} else {
$this->with[$key] = $value;
}
return $this;
}
}

View File

@@ -0,0 +1,369 @@
<?php
namespace Illuminate\Mail\Mailables;
use Closure;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\Conditionable;
class Envelope
{
use Conditionable;
/**
* The address sending the message.
*
* @var \Illuminate\Mail\Mailables\Address|string|null
*/
public $from;
/**
* The recipients of the message.
*
* @var array
*/
public $to;
/**
* The recipients receiving a copy of the message.
*
* @var array
*/
public $cc;
/**
* The recipients receiving a blind copy of the message.
*
* @var array
*/
public $bcc;
/**
* The recipients that should be replied to.
*
* @var array
*/
public $replyTo;
/**
* The subject of the message.
*
* @var string|null
*/
public $subject;
/**
* The message's tags.
*
* @var array
*/
public $tags = [];
/**
* The message's meta data.
*
* @var array
*/
public $metadata = [];
/**
* The message's Symfony Message customization callbacks.
*
* @var array
*/
public $using = [];
/**
* Create a new message envelope instance.
*
* @param \Illuminate\Mail\Mailables\Address|string|null $from
* @param array $to
* @param array $cc
* @param array $bcc
* @param array $replyTo
* @param string|null $subject
* @param array $tags
* @param array $metadata
* @param \Closure|array $using
* @return void
*
* @named-arguments-supported
*/
public function __construct(Address|string $from = null, $to = [], $cc = [], $bcc = [], $replyTo = [], string $subject = null, array $tags = [], array $metadata = [], Closure|array $using = [])
{
$this->from = is_string($from) ? new Address($from) : $from;
$this->to = $this->normalizeAddresses($to);
$this->cc = $this->normalizeAddresses($cc);
$this->bcc = $this->normalizeAddresses($bcc);
$this->replyTo = $this->normalizeAddresses($replyTo);
$this->subject = $subject;
$this->tags = $tags;
$this->metadata = $metadata;
$this->using = Arr::wrap($using);
}
/**
* Normalize the given array of addresses.
*
* @param array $addresses
* @return array
*/
protected function normalizeAddresses($addresses)
{
return collect($addresses)->map(function ($address) {
return is_string($address) ? new Address($address) : $address;
})->all();
}
/**
* Specify who the message will be "from".
*
* @param \Illuminate\Mail\Mailables\Address|string $address
* @param string|null $name
* @return $this
*/
public function from(Address|string $address, $name = null)
{
$this->from = is_string($address) ? new Address($address, $name) : $address;
return $this;
}
/**
* Add a "to" recipient to the message envelope.
*
* @param \Illuminate\Mail\Mailables\Address|array|string $address
* @param string|null $name
* @return $this
*/
public function to(Address|array|string $address, $name = null)
{
$this->to = array_merge($this->to, $this->normalizeAddresses(
is_string($name) ? [new Address($address, $name)] : Arr::wrap($address),
));
return $this;
}
/**
* Add a "cc" recipient to the message envelope.
*
* @param \Illuminate\Mail\Mailables\Address|array|string $address
* @param string|null $name
* @return $this
*/
public function cc(Address|array|string $address, $name = null)
{
$this->cc = array_merge($this->cc, $this->normalizeAddresses(
is_string($name) ? [new Address($address, $name)] : Arr::wrap($address),
));
return $this;
}
/**
* Add a "bcc" recipient to the message envelope.
*
* @param \Illuminate\Mail\Mailables\Address|array|string $address
* @param string|null $name
* @return $this
*/
public function bcc(Address|array|string $address, $name = null)
{
$this->bcc = array_merge($this->bcc, $this->normalizeAddresses(
is_string($name) ? [new Address($address, $name)] : Arr::wrap($address),
));
return $this;
}
/**
* Add a "reply to" recipient to the message envelope.
*
* @param \Illuminate\Mail\Mailables\Address|array|string $address
* @param string|null $name
* @return $this
*/
public function replyTo(Address|array|string $address, $name = null)
{
$this->replyTo = array_merge($this->replyTo, $this->normalizeAddresses(
is_string($name) ? [new Address($address, $name)] : Arr::wrap($address),
));
return $this;
}
/**
* Set the subject of the message.
*
* @param string $subject
* @return $this
*/
public function subject(string $subject)
{
$this->subject = $subject;
return $this;
}
/**
* Add "tags" to the message.
*
* @param array $tags
* @return $this
*/
public function tags(array $tags)
{
$this->tags = array_merge($this->tags, $tags);
return $this;
}
/**
* Add a "tag" to the message.
*
* @param string $tag
* @return $this
*/
public function tag(string $tag)
{
$this->tags[] = $tag;
return $this;
}
/**
* Add metadata to the message.
*
* @param string $key
* @param string|int $value
* @return $this
*/
public function metadata(string $key, string|int $value)
{
$this->metadata[$key] = $value;
return $this;
}
/**
* Add a Symfony Message customization callback to the message.
*
* @param \Closure $callback
* @return $this
*/
public function using(Closure $callback)
{
$this->using[] = $callback;
return $this;
}
/**
* Determine if the message is from the given address.
*
* @param string $address
* @param string|null $name
* @return bool
*/
public function isFrom(string $address, string $name = null)
{
if (is_null($name)) {
return $this->from->address === $address;
}
return $this->from->address === $address &&
$this->from->name === $name;
}
/**
* Determine if the message has the given address as a recipient.
*
* @param string $address
* @param string|null $name
* @return bool
*/
public function hasTo(string $address, string $name = null)
{
return $this->hasRecipient($this->to, $address, $name);
}
/**
* Determine if the message has the given address as a "cc" recipient.
*
* @param string $address
* @param string|null $name
* @return bool
*/
public function hasCc(string $address, string $name = null)
{
return $this->hasRecipient($this->cc, $address, $name);
}
/**
* Determine if the message has the given address as a "bcc" recipient.
*
* @param string $address
* @param string|null $name
* @return bool
*/
public function hasBcc(string $address, string $name = null)
{
return $this->hasRecipient($this->bcc, $address, $name);
}
/**
* Determine if the message has the given address as a "reply to" recipient.
*
* @param string $address
* @param string|null $name
* @return bool
*/
public function hasReplyTo(string $address, string $name = null)
{
return $this->hasRecipient($this->replyTo, $address, $name);
}
/**
* Determine if the message has the given recipient.
*
* @param array $recipients
* @param string $address
* @param string|null $name
* @return bool
*/
protected function hasRecipient(array $recipients, string $address, ?string $name = null)
{
return collect($recipients)->contains(function ($recipient) use ($address, $name) {
if (is_null($name)) {
return $recipient->address === $address;
}
return $recipient->address === $address &&
$recipient->name === $name;
});
}
/**
* Determine if the message has the given subject.
*
* @param string $subject
* @return bool
*/
public function hasSubject(string $subject)
{
return $this->subject === $subject;
}
/**
* Determine if the message has the given metadata.
*
* @param string $key
* @param string $value
* @return bool
*/
public function hasMetadata(string $key, string $value)
{
return isset($this->metadata[$key]) && (string) $this->metadata[$key] === $value;
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Illuminate\Mail\Mailables;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
class Headers
{
use Conditionable;
/**
* The message's message ID.
*
* @var string|null
*/
public $messageId;
/**
* The message IDs that are referenced by the message.
*
* @var array
*/
public $references;
/**
* The message's text headers.
*
* @var array
*/
public $text;
/**
* Create a new instance of headers for a message.
*
* @param string|null $messageId
* @param array $references
* @param array $text
* @return void
*
* @named-arguments-supported
*/
public function __construct(string $messageId = null, array $references = [], array $text = [])
{
$this->messageId = $messageId;
$this->references = $references;
$this->text = $text;
}
/**
* Set the message ID.
*
* @param string $messageId
* @return $this
*/
public function messageId(string $messageId)
{
$this->messageId = $messageId;
return $this;
}
/**
* Set the message IDs referenced by this message.
*
* @param array $references
* @return $this
*/
public function references(array $references)
{
$this->references = array_merge($this->references, $references);
return $this;
}
/**
* Set the headers for this message.
*
* @param array $references
* @return $this
*/
public function text(array $text)
{
$this->text = array_merge($this->text, $text);
return $this;
}
/**
* Get the references header as a string.
*
* @return string
*/
public function referencesString(): string
{
return collect($this->references)->map(function ($messageId) {
return Str::finish(Str::start($messageId, '<'), '>');
})->implode(' ');
}
}

View File

@@ -2,23 +2,34 @@
namespace Illuminate\Mail;
use Swift_Mailer;
use Illuminate\Support\Arr;
use InvalidArgumentException;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Queue\Factory as QueueContract;
use Illuminate\Contracts\Mail\Mailable as MailableContract;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Mail\MailQueue as MailQueueContract;
use Illuminate\Contracts\Queue\Factory as QueueContract;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\Factory;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Mail\Events\MessageSent;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mime\Email;
class Mailer implements MailerContract, MailQueueContract
{
use Macroable;
/**
* The name that is configured for the mailer.
*
* @var string
*/
protected $name;
/**
* The view factory instance.
*
@@ -27,11 +38,11 @@ class Mailer implements MailerContract, MailQueueContract
protected $views;
/**
* The Swift Mailer instance.
* The Symfony Transport instance.
*
* @var \Swift_Mailer
* @var \Symfony\Component\Mailer\Transport\TransportInterface
*/
protected $swift;
protected $transport;
/**
* The event dispatcher instance.
@@ -54,6 +65,13 @@ class Mailer implements MailerContract, MailQueueContract
*/
protected $replyTo;
/**
* The global return path address.
*
* @var array
*/
protected $returnPath;
/**
* The global to address and name.
*
@@ -62,32 +80,27 @@ class Mailer implements MailerContract, MailQueueContract
protected $to;
/**
* The queue implementation.
* The queue factory implementation.
*
* @var \Illuminate\Contracts\Queue\Queue
* @var \Illuminate\Contracts\Queue\Factory
*/
protected $queue;
/**
* Array of failed recipients.
*
* @var array
*/
protected $failedRecipients = [];
/**
* Create a new Mailer instance.
*
* @param string $name
* @param \Illuminate\Contracts\View\Factory $views
* @param \Swift_Mailer $swift
* @param \Symfony\Component\Mailer\Transport\TransportInterface $transport
* @param \Illuminate\Contracts\Events\Dispatcher|null $events
* @return void
*/
public function __construct(Factory $views, Swift_Mailer $swift, Dispatcher $events = null)
public function __construct(string $name, Factory $views, TransportInterface $transport, Dispatcher $events = null)
{
$this->name = $name;
$this->views = $views;
$this->swift = $swift;
$this->events = $events;
$this->transport = $transport;
}
/**
@@ -114,6 +127,17 @@ class Mailer implements MailerContract, MailQueueContract
$this->replyTo = compact('address', 'name');
}
/**
* Set the global return path address.
*
* @param string $address
* @return void
*/
public function alwaysReturnPath($address)
{
$this->returnPath = compact('address');
}
/**
* Set the global to address and name.
*
@@ -137,6 +161,17 @@ class Mailer implements MailerContract, MailQueueContract
return (new PendingMail($this))->to($users);
}
/**
* Begin the process of mailing a mailable class instance.
*
* @param mixed $users
* @return \Illuminate\Mail\PendingMail
*/
public function cc($users)
{
return (new PendingMail($this))->cc($users);
}
/**
* Begin the process of mailing a mailable class instance.
*
@@ -149,11 +184,23 @@ class Mailer implements MailerContract, MailQueueContract
}
/**
* Send a new message when only a raw text part.
* Send a new message with only an HTML part.
*
* @param string $html
* @param mixed $callback
* @return \Illuminate\Mail\SentMessage|null
*/
public function html($html, $callback)
{
return $this->send(['html' => new HtmlString($html)], [], $callback);
}
/**
* Send a new message with only a raw text part.
*
* @param string $text
* @param mixed $callback
* @return void
* @return \Illuminate\Mail\SentMessage|null
*/
public function raw($text, $callback)
{
@@ -161,12 +208,12 @@ class Mailer implements MailerContract, MailQueueContract
}
/**
* Send a new message when only a plain part.
* Send a new message with only a plain part.
*
* @param string $view
* @param array $data
* @param mixed $callback
* @return void
* @return \Illuminate\Mail\SentMessage|null
*/
public function plain($view, array $data, $callback)
{
@@ -174,12 +221,31 @@ class Mailer implements MailerContract, MailQueueContract
}
/**
* Send a new message using a view.
* Render the given message as a view.
*
* @param string|array $view
* @param array $data
* @param \Closure|string $callback
* @return void
* @return string
*/
public function render($view, array $data = [])
{
// First we need to parse the view, which could either be a string or an array
// containing both an HTML and plain text versions of the view which should
// be used when sending an e-mail. We will extract both of them out here.
[$view, $plain, $raw] = $this->parseView($view);
$data['message'] = $this->createMessage();
return $this->renderView($view ?: $plain, $data);
}
/**
* Send a new message using a view.
*
* @param \Illuminate\Contracts\Mail\Mailable|string|array $view
* @param array $data
* @param \Closure|string|null $callback
* @return \Illuminate\Mail\SentMessage|null
*/
public function send($view, array $data = [], $callback = null)
{
@@ -190,46 +256,55 @@ class Mailer implements MailerContract, MailQueueContract
// First we need to parse the view, which could either be a string or an array
// containing both an HTML and plain text versions of the view which should
// be used when sending an e-mail. We will extract both of them out here.
list($view, $plain, $raw) = $this->parseView($view);
[$view, $plain, $raw] = $this->parseView($view);
$data['message'] = $message = $this->createMessage();
// Once we have retrieved the view content for the e-mail we will set the body
// of this message using the HTML type, which will provide a simple wrapper
// to creating view based emails that are able to receive arrays of data.
$this->addContent($message, $view, $plain, $raw, $data);
if (! is_null($callback)) {
$callback($message);
}
call_user_func($callback, $message);
$this->addContent($message, $view, $plain, $raw, $data);
// If a global "to" address has been set, we will set that address on the mail
// message. This is primarily useful during local development in which each
// message should be delivered into a single mail address for inspection.
if (isset($this->to['address'])) {
$this->setGlobalTo($message);
$this->setGlobalToAndRemoveCcAndBcc($message);
}
// Next we will determine if the message should be send. We give the developer
// Next we will determine if the message should be sent. We give the developer
// one final chance to stop this message and then we will send it to all of
// its recipients. We will then fire the sent event for the sent message.
$swiftMessage = $message->getSwiftMessage();
$symfonyMessage = $message->getSymfonyMessage();
if ($this->shouldSendMessage($swiftMessage)) {
$this->sendSwiftMessage($swiftMessage);
if ($this->shouldSendMessage($symfonyMessage, $data)) {
$symfonySentMessage = $this->sendSymfonyMessage($symfonyMessage);
$this->dispatchSentEvent($message);
if ($symfonySentMessage) {
$sentMessage = new SentMessage($symfonySentMessage);
$this->dispatchSentEvent($sentMessage, $data);
return $sentMessage;
}
}
}
/**
* Send the given mailable.
*
* @param MailableContract $mailable
* @return mixed
* @param \Illuminate\Contracts\Mail\Mailable $mailable
* @return \Illuminate\Mail\SentMessage|null
*/
protected function sendMailable(MailableContract $mailable)
{
return $mailable instanceof ShouldQueue
? $mailable->queue($this->queue) : $mailable->send($this);
? $mailable->mailer($this->name)->queue($this->queue)
: $mailable->mailer($this->name)->send($this);
}
/**
@@ -248,7 +323,7 @@ class Mailer implements MailerContract, MailQueueContract
// If the given view is an array with numeric keys, we will just assume that
// both a "pretty" and "plain" view were provided, so we will return this
// array as is, since must should contain both views with numeric keys.
// array as is, since it should contain both views with numerical keys.
if (is_array($view) && isset($view[0])) {
return [$view[0], $view[1], null];
}
@@ -258,9 +333,9 @@ class Mailer implements MailerContract, MailQueueContract
// named keys instead, allowing the developers to use one or the other.
if (is_array($view)) {
return [
Arr::get($view, 'html'),
Arr::get($view, 'text'),
Arr::get($view, 'raw'),
$view['html'] ?? null,
$view['text'] ?? null,
$view['raw'] ?? null,
];
}
@@ -280,19 +355,15 @@ class Mailer implements MailerContract, MailQueueContract
protected function addContent($message, $view, $plain, $raw, $data)
{
if (isset($view)) {
$message->setBody($this->renderView($view, $data), 'text/html');
$message->html($this->renderView($view, $data) ?: ' ');
}
if (isset($plain)) {
$method = isset($view) ? 'addPart' : 'setBody';
$message->$method($this->renderView($plain, $data), 'text/plain');
$message->text($this->renderView($plain, $data) ?: ' ');
}
if (isset($raw)) {
$method = (isset($view) || isset($plain)) ? 'addPart' : 'setBody';
$message->$method($raw, 'text/plain');
$message->text($raw);
}
}
@@ -316,43 +387,48 @@ class Mailer implements MailerContract, MailQueueContract
* @param \Illuminate\Mail\Message $message
* @return void
*/
protected function setGlobalTo($message)
protected function setGlobalToAndRemoveCcAndBcc($message)
{
$message->forgetTo();
$message->to($this->to['address'], $this->to['name'], true);
$message->cc($this->to['address'], $this->to['name'], true);
$message->bcc($this->to['address'], $this->to['name'], true);
$message->forgetCc();
$message->forgetBcc();
}
/**
* Queue a new e-mail message for sending.
*
* @param string|array $view
* @param array $data
* @param \Closure|string $callback
* @param \Illuminate\Contracts\Mail\Mailable|string|array $view
* @param string|null $queue
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function queue($view, array $data = [], $callback = null, $queue = null)
public function queue($view, $queue = null)
{
if (! $view instanceof MailableContract) {
throw new InvalidArgumentException('Only mailables may be queued.');
}
return $view->queue($this->queue);
if (is_string($queue)) {
$view->onQueue($queue);
}
return $view->mailer($this->name)->queue($this->queue);
}
/**
* Queue a new e-mail message for sending on the given queue.
*
* @param string $queue
* @param string|array $view
* @param array $data
* @param \Closure|string $callback
* @param \Illuminate\Contracts\Mail\Mailable $view
* @return mixed
*/
public function onQueue($queue, $view, array $data, $callback)
public function onQueue($queue, $view)
{
return $this->queue($view, $data, $callback, $queue);
return $this->queue($view, $queue);
}
/**
@@ -361,48 +437,46 @@ class Mailer implements MailerContract, MailQueueContract
* This method didn't match rest of framework's "onQueue" phrasing. Added "onQueue".
*
* @param string $queue
* @param string|array $view
* @param array $data
* @param \Closure|string $callback
* @param \Illuminate\Contracts\Mail\Mailable $view
* @return mixed
*/
public function queueOn($queue, $view, array $data, $callback)
public function queueOn($queue, $view)
{
return $this->onQueue($queue, $view, $data, $callback);
return $this->onQueue($queue, $view);
}
/**
* Queue a new e-mail message for sending after (n) seconds.
*
* @param int $delay
* @param string|array $view
* @param array $data
* @param \Closure|string $callback
* @param \DateTimeInterface|\DateInterval|int $delay
* @param \Illuminate\Contracts\Mail\Mailable $view
* @param string|null $queue
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function later($delay, $view, array $data = [], $callback = null, $queue = null)
public function later($delay, $view, $queue = null)
{
if (! $view instanceof MailableContract) {
throw new InvalidArgumentException('Only mailables may be queued.');
}
return $view->later($delay, $this->queue);
return $view->mailer($this->name)->later(
$delay, is_null($queue) ? $this->queue : $queue
);
}
/**
* Queue a new e-mail message for sending after (n) seconds on the given queue.
*
* @param string $queue
* @param int $delay
* @param string|array $view
* @param array $data
* @param \Closure|string $callback
* @param \DateTimeInterface|\DateInterval|int $delay
* @param \Illuminate\Contracts\Mail\Mailable $view
* @return mixed
*/
public function laterOn($queue, $delay, $view, array $data, $callback)
public function laterOn($queue, $delay, $view)
{
return $this->later($delay, $view, $data, $callback, $queue);
return $this->later($delay, $view, $queue);
}
/**
@@ -412,82 +486,86 @@ class Mailer implements MailerContract, MailQueueContract
*/
protected function createMessage()
{
$message = new Message($this->swift->createMessage('message'));
$message = new Message(new Email());
// If a global from address has been specified we will set it on every message
// instances so the developer does not have to repeat themselves every time
// they create a new message. We will just go ahead and push the address.
// instance so the developer does not have to repeat themselves every time
// they create a new message. We'll just go ahead and push this address.
if (! empty($this->from['address'])) {
$message->from($this->from['address'], $this->from['name']);
}
// When a global reply address was specified we will set this on every message
// instances so the developer does not have to repeat themselves every time
// they create a new message. We will just go ahead and push the address.
// instance so the developer does not have to repeat themselves every time
// they create a new message. We will just go ahead and push this address.
if (! empty($this->replyTo['address'])) {
$message->replyTo($this->replyTo['address'], $this->replyTo['name']);
}
if (! empty($this->returnPath['address'])) {
$message->returnPath($this->returnPath['address']);
}
return $message;
}
/**
* Send a Swift Message instance.
* Send a Symfony Email instance.
*
* @param \Swift_Message $message
* @return void
* @param \Symfony\Component\Mime\Email $message
* @return \Symfony\Component\Mailer\SentMessage|null
*/
protected function sendSwiftMessage($message)
protected function sendSymfonyMessage(Email $message)
{
try {
return $this->swift->send($message, $this->failedRecipients);
return $this->transport->send($message, Envelope::create($message));
} finally {
$this->forceReconnection();
//
}
}
/**
* Determines if the message can be sent.
* Determines if the email can be sent.
*
* @param \Swift_Message $message
* @param \Symfony\Component\Mime\Email $message
* @param array $data
* @return bool
*/
protected function shouldSendMessage($message)
protected function shouldSendMessage($message, $data = [])
{
if (! $this->events) {
return true;
}
return $this->events->until(
new Events\MessageSending($message)
new MessageSending($message, $data)
) !== false;
}
/**
* Dispatch the message sent event.
*
* @param \Illuminate\Mail\Message $message
* @param \Illuminate\Mail\SentMessage $message
* @param array $data
* @return void
*/
protected function dispatchSentEvent($message)
protected function dispatchSentEvent($message, $data = [])
{
if ($this->events) {
$this->events->dispatch(
new Events\MessageSent($message->getSwiftMessage())
new MessageSent($message, $data)
);
}
}
/**
* Force the transport to re-connect.
* Get the Symfony Transport instance.
*
* This will prevent errors in daemon queue situations.
*
* @return void
* @return \Symfony\Component\Mailer\Transport\TransportInterface
*/
protected function forceReconnection()
public function getSymfonyTransport()
{
$this->getSwiftMailer()->getTransport()->stop();
return $this->transport;
}
/**
@@ -501,34 +579,14 @@ class Mailer implements MailerContract, MailQueueContract
}
/**
* Get the Swift Mailer instance.
* Set the Symfony Transport instance.
*
* @return \Swift_Mailer
*/
public function getSwiftMailer()
{
return $this->swift;
}
/**
* Get the array of failed recipients.
*
* @return array
*/
public function failures()
{
return $this->failedRecipients;
}
/**
* Set the Swift Mailer instance.
*
* @param \Swift_Mailer $swift
* @param \Symfony\Component\Mailer\Transport\TransportInterface $transport
* @return void
*/
public function setSwiftMailer($swift)
public function setSymfonyTransport(TransportInterface $transport)
{
$this->swift = $swift;
$this->transport = $transport;
}
/**

View File

@@ -2,10 +2,13 @@
namespace Illuminate\Mail;
use Parsedown;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\Contracts\View\Factory as ViewFactory;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\MarkdownConverter;
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
class Markdown
@@ -13,7 +16,7 @@ class Markdown
/**
* The view factory implementation.
*
* @var \Illuminate\View\Factory
* @var \Illuminate\Contracts\View\Factory
*/
protected $view;
@@ -41,8 +44,8 @@ class Markdown
public function __construct(ViewFactory $view, array $options = [])
{
$this->view = $view;
$this->theme = Arr::get($options, 'theme', 'default');
$this->loadComponentsFrom(Arr::get($options, 'paths', []));
$this->theme = $options['theme'] ?? 'default';
$this->loadComponentsFrom($options['paths'] ?? []);
}
/**
@@ -61,13 +64,21 @@ class Markdown
'mail', $this->htmlComponentPaths()
)->make($view, $data)->render();
return new HtmlString(with($inliner ?: new CssToInlineStyles)->convert(
$contents, $this->view->make('mail::themes.'.$this->theme)->render()
if ($this->view->exists($customTheme = Str::start($this->theme, 'mail.'))) {
$theme = $customTheme;
} else {
$theme = str_contains($this->theme, '::')
? $this->theme
: 'mail::themes.'.$this->theme;
}
return new HtmlString(($inliner ?: new CssToInlineStyles)->convert(
$contents, $this->view->make($theme, $data)->render()
));
}
/**
* Render the Markdown template into HTML.
* Render the Markdown template into text.
*
* @param string $view
* @param array $data
@@ -78,7 +89,7 @@ class Markdown
$this->view->flushFinderCache();
$contents = $this->view->replaceNamespace(
'mail', $this->markdownComponentPaths()
'mail', $this->textComponentPaths()
)->make($view, $data)->render();
return new HtmlString(
@@ -90,13 +101,20 @@ class Markdown
* Parse the given Markdown text into HTML.
*
* @param string $text
* @return string
* @return \Illuminate\Support\HtmlString
*/
public static function parse($text)
{
$parsedown = new Parsedown;
$environment = new Environment([
'allow_unsafe_links' => false,
]);
return new HtmlString($parsedown->text($text));
$environment->addExtension(new CommonMarkCoreExtension);
$environment->addExtension(new TableExtension);
$converter = new MarkdownConverter($environment);
return new HtmlString($converter->convert($text)->getContent());
}
/**
@@ -112,14 +130,14 @@ class Markdown
}
/**
* Get the Markdown component paths.
* Get the text component paths.
*
* @return array
*/
public function markdownComponentPaths()
public function textComponentPaths()
{
return array_map(function ($path) {
return $path.'/markdown';
return $path.'/text';
}, $this->componentPaths());
}
@@ -145,4 +163,27 @@ class Markdown
{
$this->componentPaths = $paths;
}
/**
* Set the default theme to be used.
*
* @param string $theme
* @return $this
*/
public function theme($theme)
{
$this->theme = $theme;
return $this;
}
/**
* Get the theme currently being used by the renderer.
*
* @return string
*/
public function getTheme()
{
return $this->theme;
}
}

View File

@@ -2,21 +2,31 @@
namespace Illuminate\Mail;
use Swift_Image;
use Swift_Attachment;
use Illuminate\Contracts\Mail\Attachable;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
/**
* @mixin \Symfony\Component\Mime\Email
*/
class Message
{
use ForwardsCalls;
/**
* The Swift Message instance.
* The Symfony Email instance.
*
* @var \Swift_Message
* @var \Symfony\Component\Mime\Email
*/
protected $swift;
protected $message;
/**
* CIDs of files embedded in the message.
*
* @deprecated Will be removed in a future Laravel version.
*
* @var array
*/
protected $embeddedFiles = [];
@@ -24,12 +34,12 @@ class Message
/**
* Create a new message instance.
*
* @param \Swift_Message $swift
* @param \Symfony\Component\Mime\Email $message
* @return void
*/
public function __construct($swift)
public function __construct(Email $message)
{
$this->swift = $swift;
$this->message = $message;
}
/**
@@ -41,7 +51,9 @@ class Message
*/
public function from($address, $name = null)
{
$this->swift->setFrom($address, $name);
is_array($address)
? $this->message->from(...$address)
: $this->message->from(new Address($address, (string) $name));
return $this;
}
@@ -55,7 +67,9 @@ class Message
*/
public function sender($address, $name = null)
{
$this->swift->setSender($address, $name);
is_array($address)
? $this->message->sender(...$address)
: $this->message->sender(new Address($address, (string) $name));
return $this;
}
@@ -68,7 +82,7 @@ class Message
*/
public function returnPath($address)
{
$this->swift->setReturnPath($address);
$this->message->returnPath($address);
return $this;
}
@@ -84,7 +98,9 @@ class Message
public function to($address, $name = null, $override = false)
{
if ($override) {
$this->swift->setTo($address, $name);
is_array($address)
? $this->message->to(...$address)
: $this->message->to(new Address($address, (string) $name));
return $this;
}
@@ -92,6 +108,22 @@ class Message
return $this->addAddresses($address, $name, 'To');
}
/**
* Remove all "to" addresses from the message.
*
* @return $this
*/
public function forgetTo()
{
if ($header = $this->message->getHeaders()->get('To')) {
$this->addAddressDebugHeader('X-To', $this->message->getTo());
$header->setAddresses([]);
}
return $this;
}
/**
* Add a carbon copy to the message.
*
@@ -103,7 +135,9 @@ class Message
public function cc($address, $name = null, $override = false)
{
if ($override) {
$this->swift->setCc($address, $name);
is_array($address)
? $this->message->cc(...$address)
: $this->message->cc(new Address($address, (string) $name));
return $this;
}
@@ -111,6 +145,22 @@ class Message
return $this->addAddresses($address, $name, 'Cc');
}
/**
* Remove all carbon copy addresses from the message.
*
* @return $this
*/
public function forgetCc()
{
if ($header = $this->message->getHeaders()->get('Cc')) {
$this->addAddressDebugHeader('X-Cc', $this->message->getCC());
$header->setAddresses([]);
}
return $this;
}
/**
* Add a blind carbon copy to the message.
*
@@ -122,7 +172,9 @@ class Message
public function bcc($address, $name = null, $override = false)
{
if ($override) {
$this->swift->setBcc($address, $name);
is_array($address)
? $this->message->bcc(...$address)
: $this->message->bcc(new Address($address, (string) $name));
return $this;
}
@@ -131,7 +183,23 @@ class Message
}
/**
* Add a reply to address to the message.
* Remove all of the blind carbon copy addresses from the message.
*
* @return $this
*/
public function forgetBcc()
{
if ($header = $this->message->getHeaders()->get('Bcc')) {
$this->addAddressDebugHeader('X-Bcc', $this->message->getBcc());
$header->setAddresses([]);
}
return $this;
}
/**
* Add a "reply to" address to the message.
*
* @param string|array $address
* @param string|null $name
@@ -153,14 +221,49 @@ class Message
protected function addAddresses($address, $name, $type)
{
if (is_array($address)) {
$this->swift->{"set{$type}"}($address, $name);
$type = lcfirst($type);
$addresses = collect($address)->map(function ($address, $key) {
if (is_string($key) && is_string($address)) {
return new Address($key, $address);
}
if (is_array($address)) {
return new Address($address['email'] ?? $address['address'], $address['name'] ?? null);
}
if (is_null($address)) {
return new Address($key);
}
return $address;
})->all();
$this->message->{"{$type}"}(...$addresses);
} else {
$this->swift->{"add{$type}"}($address, $name);
$this->message->{"add{$type}"}(new Address($address, (string) $name));
}
return $this;
}
/**
* Add an address debug header for a list of recipients.
*
* @param string $header
* @param \Symfony\Component\Mime\Address[] $addresses
* @return $this
*/
protected function addAddressDebugHeader(string $header, array $addresses)
{
$this->message->getHeaders()->addTextHeader(
$header,
implode(', ', array_map(fn ($a) => $a->toString(), $addresses)),
);
return $this;
}
/**
* Set the subject of the message.
*
@@ -169,7 +272,7 @@ class Message
*/
public function subject($subject)
{
$this->swift->setSubject($subject);
$this->message->subject($subject);
return $this;
}
@@ -182,7 +285,7 @@ class Message
*/
public function priority($level)
{
$this->swift->setPriority($level);
$this->message->priority($level);
return $this;
}
@@ -190,26 +293,23 @@ class Message
/**
* Attach a file to the message.
*
* @param string $file
* @param string|\Illuminate\Contracts\Mail\Attachable|\Illuminate\Mail\Attachment $file
* @param array $options
* @return $this
*/
public function attach($file, array $options = [])
{
$attachment = $this->createAttachmentFromPath($file);
if ($file instanceof Attachable) {
$file = $file->toMailAttachment();
}
return $this->prepAttachment($attachment, $options);
}
if ($file instanceof Attachment) {
return $file->attachTo($this);
}
/**
* Create a Swift Attachment instance.
*
* @param string $file
* @return \Swift_Attachment
*/
protected function createAttachmentFromPath($file)
{
return Swift_Attachment::fromPath($file);
$this->message->attachFromPath($file, $options['as'] ?? null, $options['mime'] ?? null);
return $this;
}
/**
@@ -222,38 +322,45 @@ class Message
*/
public function attachData($data, $name, array $options = [])
{
$attachment = $this->createAttachmentFromData($data, $name);
$this->message->attach($data, $name, $options['mime'] ?? null);
return $this->prepAttachment($attachment, $options);
}
/**
* Create a Swift Attachment instance from data.
*
* @param string $data
* @param string $name
* @return \Swift_Attachment
*/
protected function createAttachmentFromData($data, $name)
{
return Swift_Attachment::newInstance($data, $name);
return $this;
}
/**
* Embed a file in the message and get the CID.
*
* @param string $file
* @param string|\Illuminate\Contracts\Mail\Attachable|\Illuminate\Mail\Attachment $file
* @return string
*/
public function embed($file)
{
if (isset($this->embeddedFiles[$file])) {
return $this->embeddedFiles[$file];
if ($file instanceof Attachable) {
$file = $file->toMailAttachment();
}
return $this->embeddedFiles[$file] = $this->swift->embed(
Swift_Image::fromPath($file)
);
if ($file instanceof Attachment) {
return $file->attachWith(
function ($path) use ($file) {
$cid = $file->as ?? Str::random();
$this->message->embedFromPath($path, $cid, $file->mime);
return "cid:{$cid}";
},
function ($data) use ($file) {
$this->message->embed($data(), $file->as, $file->mime);
return "cid:{$file->as}";
}
);
}
$cid = Str::random(10);
$this->message->embedFromPath($file, $cid);
return "cid:$cid";
}
/**
@@ -266,51 +373,23 @@ class Message
*/
public function embedData($data, $name, $contentType = null)
{
$image = Swift_Image::newInstance($data, $name, $contentType);
$this->message->embed($data, $name, $contentType);
return $this->swift->embed($image);
return "cid:$name";
}
/**
* Prepare and attach the given attachment.
* Get the underlying Symfony Email instance.
*
* @param \Swift_Attachment $attachment
* @param array $options
* @return $this
* @return \Symfony\Component\Mime\Email
*/
protected function prepAttachment($attachment, $options = [])
public function getSymfonyMessage()
{
// First we will check for a MIME type on the message, which instructs the
// mail client on what type of attachment the file is so that it may be
// downloaded correctly by the user. The MIME option is not required.
if (isset($options['mime'])) {
$attachment->setContentType($options['mime']);
}
// If an alternative name was given as an option, we will set that on this
// attachment so that it will be downloaded with the desired names from
// the developer, otherwise the default file names will get assigned.
if (isset($options['as'])) {
$attachment->setFilename($options['as']);
}
$this->swift->attach($attachment);
return $this;
return $this->message;
}
/**
* Get the underlying Swift Message instance.
*
* @return \Swift_Message
*/
public function getSwiftMessage()
{
return $this->swift;
}
/**
* Dynamically pass missing methods to the Swift instance.
* Dynamically pass missing methods to the Symfony instance.
*
* @param string $method
* @param array $parameters
@@ -318,8 +397,6 @@ class Message
*/
public function __call($method, $parameters)
{
$callable = [$this->swift, $method];
return call_user_func_array($callable, $parameters);
return $this->forwardDecoratedCallTo($this->message, $method, $parameters);
}
}

View File

@@ -2,17 +2,29 @@
namespace Illuminate\Mail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Mail\Mailable as MailableContract;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Support\Traits\Conditionable;
class PendingMail
{
use Conditionable;
/**
* The mailer instance.
*
* @var array
* @var \Illuminate\Contracts\Mail\Mailer
*/
protected $mailer;
/**
* The locale of the message.
*
* @var string
*/
protected $locale;
/**
* The "to" recipients of the message.
*
@@ -37,14 +49,27 @@ class PendingMail
/**
* Create a new mailable mailer instance.
*
* @param Mailer $mailer
* @param \Illuminate\Contracts\Mail\Mailer $mailer
* @return void
*/
public function __construct(Mailer $mailer)
public function __construct(MailerContract $mailer)
{
$this->mailer = $mailer;
}
/**
* Set the locale of the message.
*
* @param string $locale
* @return $this
*/
public function locale($locale)
{
$this->locale = $locale;
return $this;
}
/**
* Set the recipients of the message.
*
@@ -55,6 +80,10 @@ class PendingMail
{
$this->to = $users;
if (! $this->locale && $users instanceof HasLocalePreference) {
$this->locale($users->preferredLocale());
}
return $this;
}
@@ -87,25 +116,10 @@ class PendingMail
/**
* Send a new mailable message instance.
*
* @param Mailable $mailable
* @return mixed
* @param \Illuminate\Contracts\Mail\Mailable $mailable
* @return \Illuminate\Mail\SentMessage|null
*/
public function send(Mailable $mailable)
{
if ($mailable instanceof ShouldQueue) {
return $this->queue($mailable);
}
return $this->mailer->send($this->fill($mailable));
}
/**
* Send a mailable message immediately.
*
* @param Mailable $mailable
* @return mixed
*/
public function sendNow(Mailable $mailable)
public function send(MailableContract $mailable)
{
return $this->mailer->send($this->fill($mailable));
}
@@ -113,28 +127,22 @@ class PendingMail
/**
* Push the given mailable onto the queue.
*
* @param Mailable $mailable
* @param \Illuminate\Contracts\Mail\Mailable $mailable
* @return mixed
*/
public function queue(Mailable $mailable)
public function queue(MailableContract $mailable)
{
$mailable = $this->fill($mailable);
if (isset($mailable->delay)) {
return $this->mailer->later($mailable->delay, $mailable);
}
return $this->mailer->queue($mailable);
return $this->mailer->queue($this->fill($mailable));
}
/**
* Deliver the queued message after the given delay.
* Deliver the queued message after (n) seconds.
*
* @param \DateTime|int $delay
* @param Mailable $mailable
* @param \DateTimeInterface|\DateInterval|int $delay
* @param \Illuminate\Contracts\Mail\Mailable $mailable
* @return mixed
*/
public function later($delay, Mailable $mailable)
public function later($delay, MailableContract $mailable)
{
return $this->mailer->later($delay, $this->fill($mailable));
}
@@ -142,13 +150,17 @@ class PendingMail
/**
* Populate the mailable with the addresses.
*
* @param Mailable $mailable
* @return Mailable
* @param \Illuminate\Contracts\Mail\Mailable $mailable
* @return \Illuminate\Mail\Mailable
*/
protected function fill(Mailable $mailable)
protected function fill(MailableContract $mailable)
{
return $mailable->to($this->to)
->cc($this->cc)
->bcc($this->bcc);
return tap($mailable->to($this->to)
->cc($this->cc)
->bcc($this->bcc), function (MailableContract $mailable) {
if ($this->locale) {
$mailable->locale($this->locale);
}
});
}
}

View File

@@ -2,15 +2,20 @@
namespace Illuminate\Mail;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Mail\Factory as MailFactory;
use Illuminate\Contracts\Mail\Mailable as MailableContract;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Queue\InteractsWithQueue;
class SendQueuedMailable
{
use Queueable, InteractsWithQueue;
/**
* The mailable message instance.
*
* @var Mailable
* @var \Illuminate\Contracts\Mail\Mailable
*/
public $mailable;
@@ -28,6 +33,20 @@ class SendQueuedMailable
*/
public $timeout;
/**
* The maximum number of unhandled exceptions to allow before failing.
*
* @return int|null
*/
public $maxExceptions;
/**
* Indicates if the job should be encrypted.
*
* @var bool
*/
public $shouldBeEncrypted = false;
/**
* Create a new job instance.
*
@@ -39,17 +58,61 @@ class SendQueuedMailable
$this->mailable = $mailable;
$this->tries = property_exists($mailable, 'tries') ? $mailable->tries : null;
$this->timeout = property_exists($mailable, 'timeout') ? $mailable->timeout : null;
$this->maxExceptions = property_exists($mailable, 'maxExceptions') ? $mailable->maxExceptions : null;
$this->afterCommit = property_exists($mailable, 'afterCommit') ? $mailable->afterCommit : null;
$this->shouldBeEncrypted = $mailable instanceof ShouldBeEncrypted;
}
/**
* Handle the queued job.
*
* @param \Illuminate\Contracts\Mail\Mailer $mailer
* @param \Illuminate\Contracts\Mail\Factory $factory
* @return void
*/
public function handle(MailerContract $mailer)
public function handle(MailFactory $factory)
{
$this->mailable->send($mailer);
$this->mailable->send($factory);
}
/**
* Get the number of seconds before a released mailable will be available.
*
* @return mixed
*/
public function backoff()
{
if (! method_exists($this->mailable, 'backoff') && ! isset($this->mailable->backoff)) {
return;
}
return $this->mailable->backoff ?? $this->mailable->backoff();
}
/**
* Determine the time at which the job should timeout.
*
* @return \DateTime|null
*/
public function retryUntil()
{
if (! method_exists($this->mailable, 'retryUntil') && ! isset($this->mailable->retryUntil)) {
return;
}
return $this->mailable->retryUntil ?? $this->mailable->retryUntil();
}
/**
* Call the failed method on the mailable instance.
*
* @param \Throwable $e
* @return void
*/
public function failed($e)
{
if (method_exists($this->mailable, 'failed')) {
$this->mailable->failed($e);
}
}
/**
@@ -61,4 +124,14 @@ class SendQueuedMailable
{
return get_class($this->mailable);
}
/**
* Prepare the instance for cloning.
*
* @return void
*/
public function __clone()
{
$this->mailable = clone $this->mailable;
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Illuminate\Mail;
use Illuminate\Support\Traits\ForwardsCalls;
use Symfony\Component\Mailer\SentMessage as SymfonySentMessage;
/**
* @mixin \Symfony\Component\Mailer\SentMessage
*/
class SentMessage
{
use ForwardsCalls;
/**
* The Symfony SentMessage instance.
*
* @var \Symfony\Component\Mailer\SentMessage
*/
protected $sentMessage;
/**
* Create a new SentMessage instance.
*
* @param \Symfony\Component\Mailer\SentMessage $sentMessage
* @return void
*/
public function __construct(SymfonySentMessage $sentMessage)
{
$this->sentMessage = $sentMessage;
}
/**
* Get the underlying Symfony Email instance.
*
* @return \Symfony\Component\Mailer\SentMessage
*/
public function getSymfonySentMessage()
{
return $this->sentMessage;
}
/**
* Dynamically pass missing methods to the Symfony instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->forwardCallTo($this->sentMessage, $method, $parameters);
}
}

View File

@@ -2,13 +2,16 @@
namespace Illuminate\Mail\Transport;
use Swift_Mime_Message;
use Illuminate\Support\Collection;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mime\RawMessage;
class ArrayTransport extends Transport
class ArrayTransport implements TransportInterface
{
/**
* The collection of Swift Messages.
* The collection of Symfony Messages.
*
* @var \Illuminate\Support\Collection
*/
@@ -27,13 +30,9 @@ class ArrayTransport extends Transport
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
{
$this->beforeSendPerformed($message);
$this->messages[] = $message;
return $this->numberOfRecipients($message);
return $this->messages[] = new SentMessage($message, $envelope ?? Envelope::create($message));
}
/**
@@ -55,4 +54,14 @@ class ArrayTransport extends Transport
{
return $this->messages = new Collection;
}
/**
* Get the string representation of the transport.
*
* @return string
*/
public function __toString(): string
{
return 'array';
}
}

View File

@@ -2,11 +2,13 @@
namespace Illuminate\Mail\Transport;
use Swift_Mime_Message;
use Swift_Mime_MimeEntity;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mime\RawMessage;
class LogTransport extends Transport
class LogTransport implements TransportInterface
{
/**
* The Logger instance.
@@ -29,31 +31,30 @@ class LogTransport extends Transport
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
{
$this->beforeSendPerformed($message);
$this->logger->debug($message->toString());
$this->logger->debug($this->getMimeEntityString($message));
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
return new SentMessage($message, $envelope ?? Envelope::create($message));
}
/**
* Get a loggable string out of a Swiftmailer entity.
* Get the logger for the LogTransport instance.
*
* @return \Psr\Log\LoggerInterface
*/
public function logger()
{
return $this->logger;
}
/**
* Get the string representation of the transport.
*
* @param \Swift_Mime_MimeEntity $entity
* @return string
*/
protected function getMimeEntityString(Swift_Mime_MimeEntity $entity)
public function __toString(): string
{
$string = (string) $entity->getHeaders().PHP_EOL.$entity->getBody();
foreach ($entity->getChildren() as $children) {
$string .= PHP_EOL.PHP_EOL.$this->getMimeEntityString($children);
}
return $string;
return 'log';
}
}

View File

@@ -1,168 +0,0 @@
<?php
namespace Illuminate\Mail\Transport;
use Swift_Mime_Message;
use GuzzleHttp\ClientInterface;
class MailgunTransport extends Transport
{
/**
* Guzzle client instance.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* The Mailgun API key.
*
* @var string
*/
protected $key;
/**
* The Mailgun domain.
*
* @var string
*/
protected $domain;
/**
* THe Mailgun API end-point.
*
* @var string
*/
protected $url;
/**
* Create a new Mailgun transport instance.
*
* @param \GuzzleHttp\ClientInterface $client
* @param string $key
* @param string $domain
* @return void
*/
public function __construct(ClientInterface $client, $key, $domain)
{
$this->key = $key;
$this->client = $client;
$this->setDomain($domain);
}
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
{
$this->beforeSendPerformed($message);
$to = $this->getTo($message);
$message->setBcc([]);
$this->client->post($this->url, $this->payload($message, $to));
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
/**
* Get the HTTP payload for sending the Mailgun message.
*
* @param \Swift_Mime_Message $message
* @param string $to
* @return array
*/
protected function payload(Swift_Mime_Message $message, $to)
{
return [
'auth' => [
'api',
$this->key,
],
'multipart' => [
[
'name' => 'to',
'contents' => $to,
],
[
'name' => 'message',
'contents' => $message->toString(),
'filename' => 'message.mime',
],
],
];
}
/**
* Get the "to" payload field for the API request.
*
* @param \Swift_Mime_Message $message
* @return string
*/
protected function getTo(Swift_Mime_Message $message)
{
return collect($this->allContacts($message))->map(function ($display, $address) {
return $display ? $display." <{$address}>" : $address;
})->values()->implode(',');
}
/**
* Get all of the contacts for the message.
*
* @param \Swift_Mime_Message $message
* @return array
*/
protected function allContacts(Swift_Mime_Message $message)
{
return array_merge(
(array) $message->getTo(), (array) $message->getCc(), (array) $message->getBcc()
);
}
/**
* Get the API key being used by the transport.
*
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* Set the API key being used by the transport.
*
* @param string $key
* @return string
*/
public function setKey($key)
{
return $this->key = $key;
}
/**
* Get the domain being used by the transport.
*
* @return string
*/
public function getDomain()
{
return $this->domain;
}
/**
* Set the domain being used by the transport.
*
* @param string $domain
* @return void
*/
public function setDomain($domain)
{
$this->url = 'https://api.mailgun.net/v3/'.$domain.'/messages.mime';
return $this->domain = $domain;
}
}

View File

@@ -1,105 +0,0 @@
<?php
namespace Illuminate\Mail\Transport;
use Swift_Mime_Message;
use GuzzleHttp\ClientInterface;
class MandrillTransport extends Transport
{
/**
* Guzzle client instance.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* The Mandrill API key.
*
* @var string
*/
protected $key;
/**
* Create a new Mandrill transport instance.
*
* @param \GuzzleHttp\ClientInterface $client
* @param string $key
* @return void
*/
public function __construct(ClientInterface $client, $key)
{
$this->key = $key;
$this->client = $client;
}
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
{
$this->beforeSendPerformed($message);
$this->client->post('https://mandrillapp.com/api/1.0/messages/send-raw.json', [
'form_params' => [
'key' => $this->key,
'to' => $this->getTo($message),
'raw_message' => $message->toString(),
'async' => true,
],
]);
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
/**
* Get all the addresses this message should be sent to.
*
* Note that Mandrill still respects CC, BCC headers in raw message itself.
*
* @param \Swift_Mime_Message $message
* @return array
*/
protected function getTo(Swift_Mime_Message $message)
{
$to = [];
if ($message->getTo()) {
$to = array_merge($to, array_keys($message->getTo()));
}
if ($message->getCc()) {
$to = array_merge($to, array_keys($message->getCc()));
}
if ($message->getBcc()) {
$to = array_merge($to, array_keys($message->getBcc()));
}
return $to;
}
/**
* Get the API key being used by the transport.
*
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* Set the API key being used by the transport.
*
* @param string $key
* @return string
*/
public function setKey($key)
{
return $this->key = $key;
}
}

View File

@@ -2,10 +2,15 @@
namespace Illuminate\Mail\Transport;
use Aws\Exception\AwsException;
use Aws\Ses\SesClient;
use Swift_Mime_Message;
use Exception;
use Symfony\Component\Mailer\Header\MetadataHeader;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;
use Symfony\Component\Mime\Message;
class SesTransport extends Transport
class SesTransport extends AbstractTransport
{
/**
* The Amazon SES instance.
@@ -14,35 +19,113 @@ class SesTransport extends Transport
*/
protected $ses;
/**
* The Amazon SES transmission options.
*
* @var array
*/
protected $options = [];
/**
* Create a new SES transport instance.
*
* @param \Aws\Ses\SesClient $ses
* @param array $options
* @return void
*/
public function __construct(SesClient $ses)
public function __construct(SesClient $ses, $options = [])
{
$this->ses = $ses;
$this->options = $options;
parent::__construct();
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
protected function doSend(SentMessage $message): void
{
$this->beforeSendPerformed($message);
$options = $this->options;
$headers = $message->getHeaders();
if ($message->getOriginalMessage() instanceof Message) {
foreach ($message->getOriginalMessage()->getHeaders()->all() as $header) {
if ($header instanceof MetadataHeader) {
$options['Tags'][] = ['Name' => $header->getKey(), 'Value' => $header->getValue()];
}
}
}
$headers->addTextHeader('X-SES-Message-ID', $this->ses->sendRawEmail([
'Source' => key($message->getSender() ?: $message->getFrom()),
'RawMessage' => [
'Data' => $message->toString(),
],
])->get('MessageId'));
try {
$result = $this->ses->sendRawEmail(
array_merge(
$options, [
'Source' => $message->getEnvelope()->getSender()->toString(),
'Destinations' => collect($message->getEnvelope()->getRecipients())
->map
->toString()
->values()
->all(),
'RawMessage' => [
'Data' => $message->toString(),
],
]
)
);
} catch (AwsException $e) {
$reason = $e->getAwsErrorMessage() ?? $e->getMessage();
$this->sendPerformed($message);
throw new Exception(
sprintf('Request to AWS SES API failed. Reason: %s.', $reason),
is_int($e->getCode()) ? $e->getCode() : 0,
$e
);
}
return $this->numberOfRecipients($message);
$messageId = $result->get('MessageId');
$message->getOriginalMessage()->getHeaders()->addHeader('X-Message-ID', $messageId);
$message->getOriginalMessage()->getHeaders()->addHeader('X-SES-Message-ID', $messageId);
}
/**
* Get the string representation of the transport.
*
* @return string
*/
public function __toString(): string
{
return 'ses';
}
/**
* Get the Amazon SES client for the SesTransport instance.
*
* @return \Aws\Ses\SesClient
*/
public function ses()
{
return $this->ses;
}
/**
* Get the transmission options being used by the transport.
*
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Set the transmission options being used by the transport.
*
* @param array $options
* @return array
*/
public function setOptions(array $options)
{
return $this->options = $options;
}
}

View File

@@ -1,159 +0,0 @@
<?php
namespace Illuminate\Mail\Transport;
use Swift_Mime_Message;
use GuzzleHttp\ClientInterface;
class SparkPostTransport extends Transport
{
/**
* Guzzle client instance.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* The SparkPost API key.
*
* @var string
*/
protected $key;
/**
* Transmission options.
*
* @var array
*/
protected $options = [];
/**
* Create a new SparkPost transport instance.
*
* @param \GuzzleHttp\ClientInterface $client
* @param string $key
* @param array $options
* @return void
*/
public function __construct(ClientInterface $client, $key, $options = [])
{
$this->key = $key;
$this->client = $client;
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
{
$this->beforeSendPerformed($message);
$recipients = $this->getRecipients($message);
$message->setBcc([]);
$response = $this->client->post('https://api.sparkpost.com/api/v1/transmissions', [
'headers' => [
'Authorization' => $this->key,
],
'json' => array_merge([
'recipients' => $recipients,
'content' => [
'email_rfc822' => $message->toString(),
],
], $this->options),
]);
$message->getHeaders()->addTextHeader(
'X-SparkPost-Transmission-ID', $this->getTransmissionId($response)
);
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
/**
* Get all the addresses this message should be sent to.
*
* Note that SparkPost still respects CC, BCC headers in raw message itself.
*
* @param \Swift_Mime_Message $message
* @return array
*/
protected function getRecipients(Swift_Mime_Message $message)
{
$recipients = [];
foreach ((array) $message->getTo() as $email => $name) {
$recipients[] = ['address' => compact('name', 'email')];
}
foreach ((array) $message->getCc() as $email => $name) {
$recipients[] = ['address' => compact('name', 'email')];
}
foreach ((array) $message->getBcc() as $email => $name) {
$recipients[] = ['address' => compact('name', 'email')];
}
return $recipients;
}
/**
* Get the transmission ID from the response.
*
* @param \GuzzleHttp\Psr7\Response $response
* @return string
*/
protected function getTransmissionId($response)
{
return object_get(
json_decode($response->getBody()->getContents()), 'results.id'
);
}
/**
* Get the API key being used by the transport.
*
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* Set the API key being used by the transport.
*
* @param string $key
* @return string
*/
public function setKey($key)
{
return $this->key = $key;
}
/**
* Get the transmission options being used by the transport.
*
* @return string
*/
public function getOptions()
{
return $this->options;
}
/**
* Set the transmission options being used by the transport.
*
* @param array $options
* @return array
*/
public function setOptions(array $options)
{
return $this->options = $options;
}
}

View File

@@ -1,100 +0,0 @@
<?php
namespace Illuminate\Mail\Transport;
use Swift_Transport;
use Swift_Mime_Message;
use Swift_Events_SendEvent;
use Swift_Events_EventListener;
abstract class Transport implements Swift_Transport
{
/**
* The plug-ins registered with the transport.
*
* @var array
*/
public $plugins = [];
/**
* {@inheritdoc}
*/
public function isStarted()
{
return true;
}
/**
* {@inheritdoc}
*/
public function start()
{
return true;
}
/**
* {@inheritdoc}
*/
public function stop()
{
return true;
}
/**
* Register a plug-in with the transport.
*
* @param \Swift_Events_EventListener $plugin
* @return void
*/
public function registerPlugin(Swift_Events_EventListener $plugin)
{
array_push($this->plugins, $plugin);
}
/**
* Iterate through registered plugins and execute plugins' methods.
*
* @param \Swift_Mime_Message $message
* @return void
*/
protected function beforeSendPerformed(Swift_Mime_Message $message)
{
$event = new Swift_Events_SendEvent($this, $message);
foreach ($this->plugins as $plugin) {
if (method_exists($plugin, 'beforeSendPerformed')) {
$plugin->beforeSendPerformed($event);
}
}
}
/**
* Iterate through registered plugins and execute plugins' methods.
*
* @param \Swift_Mime_Message $message
* @return void
*/
protected function sendPerformed(Swift_Mime_Message $message)
{
$event = new Swift_Events_SendEvent($this, $message);
foreach ($this->plugins as $plugin) {
if (method_exists($plugin, 'sendPerformed')) {
$plugin->sendPerformed($event);
}
}
}
/**
* Get the number of recipients.
*
* @param \Swift_Mime_Message $message
* @return int
*/
protected function numberOfRecipients(Swift_Mime_Message $message)
{
return count(array_merge(
(array) $message->getTo(), (array) $message->getCc(), (array) $message->getBcc()
));
}
}

View File

@@ -1,210 +0,0 @@
<?php
namespace Illuminate\Mail;
use Aws\Ses\SesClient;
use Illuminate\Support\Arr;
use Psr\Log\LoggerInterface;
use Illuminate\Support\Manager;
use GuzzleHttp\Client as HttpClient;
use Swift_MailTransport as MailTransport;
use Swift_SmtpTransport as SmtpTransport;
use Illuminate\Mail\Transport\LogTransport;
use Illuminate\Mail\Transport\SesTransport;
use Illuminate\Mail\Transport\ArrayTransport;
use Illuminate\Mail\Transport\MailgunTransport;
use Illuminate\Mail\Transport\MandrillTransport;
use Illuminate\Mail\Transport\SparkPostTransport;
use Swift_SendmailTransport as SendmailTransport;
class TransportManager extends Manager
{
/**
* Create an instance of the SMTP Swift Transport driver.
*
* @return \Swift_SmtpTransport
*/
protected function createSmtpDriver()
{
$config = $this->app->make('config')->get('mail');
// The Swift SMTP transport instance will allow us to use any SMTP backend
// for delivering mail such as Sendgrid, Amazon SES, or a custom server
// a developer has available. We will just pass this configured host.
$transport = SmtpTransport::newInstance(
$config['host'], $config['port']
);
if (isset($config['encryption'])) {
$transport->setEncryption($config['encryption']);
}
// Once we have the transport we will check for the presence of a username
// and password. If we have it we will set the credentials on the Swift
// transporter instance so that we'll properly authenticate delivery.
if (isset($config['username'])) {
$transport->setUsername($config['username']);
$transport->setPassword($config['password']);
}
// Next we will set any stream context options specified for the transport
// and then return it. The option is not required any may not be inside
// the configuration array at all so we'll verify that before adding.
if (isset($config['stream'])) {
$transport->setStreamOptions($config['stream']);
}
return $transport;
}
/**
* Create an instance of the Sendmail Swift Transport driver.
*
* @return \Swift_SendmailTransport
*/
protected function createSendmailDriver()
{
return SendmailTransport::newInstance(
$this->app['config']['mail']['sendmail']
);
}
/**
* Create an instance of the Amazon SES Swift Transport driver.
*
* @return \Swift_SendmailTransport
*/
protected function createSesDriver()
{
$config = array_merge($this->app['config']->get('services.ses', []), [
'version' => 'latest', 'service' => 'email',
]);
return new SesTransport(new SesClient(
$this->addSesCredentials($config)
));
}
/**
* Add the SES credentials to the configuration array.
*
* @param array $config
* @return array
*/
protected function addSesCredentials(array $config)
{
if ($config['key'] && $config['secret']) {
$config['credentials'] = Arr::only($config, ['key', 'secret']);
}
return $config;
}
/**
* Create an instance of the Mail Swift Transport driver.
*
* @return \Swift_MailTransport
*/
protected function createMailDriver()
{
return MailTransport::newInstance();
}
/**
* Create an instance of the Mailgun Swift Transport driver.
*
* @return \Illuminate\Mail\Transport\MailgunTransport
*/
protected function createMailgunDriver()
{
$config = $this->app['config']->get('services.mailgun', []);
return new MailgunTransport(
$this->guzzle($config),
$config['secret'], $config['domain']
);
}
/**
* Create an instance of the Mandrill Swift Transport driver.
*
* @return \Illuminate\Mail\Transport\MandrillTransport
*/
protected function createMandrillDriver()
{
$config = $this->app['config']->get('services.mandrill', []);
return new MandrillTransport(
$this->guzzle($config), $config['secret']
);
}
/**
* Create an instance of the SparkPost Swift Transport driver.
*
* @return \Illuminate\Mail\Transport\SparkPostTransport
*/
protected function createSparkPostDriver()
{
$config = $this->app['config']->get('services.sparkpost', []);
return new SparkPostTransport(
$this->guzzle($config), $config['secret'], Arr::get($config, 'options', [])
);
}
/**
* Create an instance of the Log Swift Transport driver.
*
* @return \Illuminate\Mail\Transport\LogTransport
*/
protected function createLogDriver()
{
return new LogTransport($this->app->make(LoggerInterface::class));
}
/**
* Create an instance of the Array Swift Transport Driver.
*
* @return \Illuminate\Mail\Transport\ArrayTransport
*/
protected function createArrayDriver()
{
return new ArrayTransport;
}
/**
* Get a fresh Guzzle HTTP client instance.
*
* @param array $config
* @return \GuzzleHttp\Client
*/
protected function guzzle($config)
{
return new HttpClient(Arr::add(
Arr::get($config, 'guzzle', []), 'connect_timeout', 60
));
}
/**
* Get the default mail driver name.
*
* @return string
*/
public function getDefaultDriver()
{
return $this->app['config']['mail.driver'];
}
/**
* Set the default mail driver name.
*
* @param string $name
* @return void
*/
public function setDefaultDriver($name)
{
$this->app['config']['mail.driver'] = $name;
}
}

View File

@@ -14,14 +14,17 @@
}
],
"require": {
"php": ">=5.6.4",
"erusev/parsedown": "~1.6",
"illuminate/container": "5.4.*",
"illuminate/contracts": "5.4.*",
"illuminate/support": "5.4.*",
"psr/log": "~1.0",
"swiftmailer/swiftmailer": "~5.4",
"tijsverkoyen/css-to-inline-styles": "~2.2"
"php": "^8.0.2",
"ext-json": "*",
"illuminate/collections": "^9.0",
"illuminate/container": "^9.0",
"illuminate/contracts": "^9.0",
"illuminate/macroable": "^9.0",
"illuminate/support": "^9.0",
"league/commonmark": "^2.2",
"psr/log": "^1.0|^2.0|^3.0",
"symfony/mailer": "^6.0",
"tijsverkoyen/css-to-inline-styles": "^2.2.5"
},
"autoload": {
"psr-4": {
@@ -30,12 +33,14 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.4-dev"
"dev-master": "9.x-dev"
}
},
"suggest": {
"aws/aws-sdk-php": "Required to use the SES mail driver (~3.0).",
"guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers (~6.0)."
"aws/aws-sdk-php": "Required to use the SES mail driver (^3.235.5).",
"symfony/http-client": "Required to use the Symfony API mail transports (^6.0).",
"symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).",
"symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0)."
},
"config": {
"sort-packages": true

View File

@@ -1,19 +1,24 @@
<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<a href="{{ $url }}" class="button button-{{ $color or 'blue' }}" target="_blank">{{ $slot }}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
@props([
'url',
'color' => 'primary',
'align' => 'center',
])
<table class="action" align="{{ $align }}" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="{{ $align }}">
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="{{ $align }}">
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
<a href="{{ $url }}" class="button button-{{ $color }}" target="_blank" rel="noopener">{{ $slot }}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -1,11 +1,11 @@
<tr>
<td>
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0">
<tr>
<td class="content-cell" align="center">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
<td>
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>

View File

@@ -1,7 +1,12 @@
@props(['url'])
<tr>
<td class="header">
<a href="{{ $url }}">
{{ $slot }}
</a>
</td>
<td class="header">
<a href="{{ $url }}" style="display: inline-block;">
@if (trim($slot) === 'Laravel')
<img src="https://laravel.com/img/notification-logo.png" class="logo" alt="Laravel Logo">
@else
{{ $slot }}
@endif
</a>
</td>
</tr>

View File

@@ -1,54 +1,56 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="color-scheme" content="light">
<meta name="supported-color-schemes" content="light">
<style>
@media only screen and (max-width: 600px) {
.inner-body {
width: 100% !important;
}
.footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
</head>
<body>
<style>
@media only screen and (max-width: 600px) {
.inner-body {
width: 100% !important;
}
.footer {
width: 100% !important;
}
}
<table class="wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
{{ $header ?? '' }}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
<!-- Email Body -->
<tr>
<td class="body" width="100%" cellpadding="0" cellspacing="0" style="border: hidden !important;">
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<!-- Body content -->
<tr>
<td class="content-cell">
{{ Illuminate\Mail\Markdown::parse($slot) }}
<table class="wrapper" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<table class="content" width="100%" cellpadding="0" cellspacing="0">
{{ $header or '' }}
{{ $subcopy ?? '' }}
</td>
</tr>
</table>
</td>
</tr>
<!-- Email Body -->
<tr>
<td class="body" width="100%" cellpadding="0" cellspacing="0">
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0">
<!-- Body content -->
<tr>
<td class="content-cell">
{{ Illuminate\Mail\Markdown::parse($slot) }}
{{ $subcopy or '' }}
</td>
</tr>
</table>
</td>
</tr>
{{ $footer or '' }}
</table>
</td>
</tr>
</table>
{{ $footer ?? '' }}
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -1,27 +1,27 @@
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
{{ config('app.name') }}
@endcomponent
@endslot
<x-mail::layout>
{{-- Header --}}
<x-slot:header>
<x-mail::header :url="config('app.url')">
{{ config('app.name') }}
</x-mail::header>
</x-slot:header>
{{-- Body --}}
{{ $slot }}
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
@slot('subcopy')
@component('mail::subcopy')
{{ $subcopy }}
@endcomponent
@endslot
@endisset
{{-- Subcopy --}}
@isset($subcopy)
<x-slot:subcopy>
<x-mail::subcopy>
{{ $subcopy }}
</x-mail::subcopy>
</x-slot:subcopy>
@endisset
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
&copy; {{ date('Y') }} {{ config('app.name') }}. All rights reserved.
@endcomponent
@endslot
@endcomponent
{{-- Footer --}}
<x-slot:footer>
<x-mail::footer>
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
</x-mail::footer>
</x-slot:footer>
</x-mail::layout>

View File

@@ -1,13 +1,14 @@
<table class="panel" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td class="panel-content">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td class="panel-item">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>
<table class="panel" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="panel-content">
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="panel-item">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -1,7 +0,0 @@
<table class="promotion" align="center" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>

View File

@@ -1,13 +0,0 @@
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<a href="{{ $url }}" class="button button-green" target="_blank">{{ $slot }}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -1,7 +1,7 @@
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td>
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>

View File

@@ -1,24 +1,22 @@
/* Base */
body, body *:not(html):not(style):not(br):not(tr):not(code) {
font-family: Avenir, Helvetica, sans-serif;
body,
body *:not(html):not(style):not(br):not(tr):not(code) {
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
position: relative;
}
body {
background-color: #f5f8fa;
color: #74787E;
-webkit-text-size-adjust: none;
background-color: #ffffff;
color: #718096;
height: 100%;
hyphens: auto;
line-height: 1.4;
margin: 0;
-moz-hyphens: auto;
-ms-word-break: break-all;
padding: 0;
width: 100% !important;
-webkit-hyphens: auto;
-webkit-text-size-adjust: none;
word-break: break-all;
word-break: break-word;
}
p,
@@ -30,7 +28,7 @@ blockquote {
}
a {
color: #3869D4;
color: #3869d4;
}
a img {
@@ -40,15 +38,14 @@ a img {
/* Typography */
h1 {
color: #2F3133;
font-size: 19px;
color: #3d4852;
font-size: 18px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h2 {
color: #2F3133;
font-size: 16px;
font-weight: bold;
margin-top: 0;
@@ -56,7 +53,6 @@ h2 {
}
h3 {
color: #2F3133;
font-size: 14px;
font-weight: bold;
margin-top: 0;
@@ -64,7 +60,6 @@ h3 {
}
p {
color: #74787E;
font-size: 16px;
line-height: 1.5em;
margin-top: 0;
@@ -82,22 +77,22 @@ img {
/* Layout */
.wrapper {
background-color: #f5f8fa;
margin: 0;
padding: 0;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
background-color: #edf2f7;
margin: 0;
padding: 0;
width: 100%;
}
.content {
margin: 0;
padding: 0;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 0;
padding: 0;
width: 100%;
}
/* Header */
@@ -108,149 +103,177 @@ img {
}
.header a {
color: #bbbfc3;
color: #3d4852;
font-size: 19px;
font-weight: bold;
text-decoration: none;
text-shadow: 0 1px 0 white;
}
/* Logo */
.logo {
height: 75px;
max-height: 75px;
width: 75px;
}
/* Body */
.body {
background-color: #FFFFFF;
border-bottom: 1px solid #EDEFF2;
border-top: 1px solid #EDEFF2;
margin: 0;
padding: 0;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
background-color: #edf2f7;
border-bottom: 1px solid #edf2f7;
border-top: 1px solid #edf2f7;
margin: 0;
padding: 0;
width: 100%;
}
.inner-body {
background-color: #FFFFFF;
margin: 0 auto;
padding: 0;
width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
background-color: #ffffff;
border-color: #e8e5ef;
border-radius: 2px;
border-width: 1px;
box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015);
margin: 0 auto;
padding: 0;
width: 570px;
}
/* Subcopy */
.subcopy {
border-top: 1px solid #EDEFF2;
border-top: 1px solid #e8e5ef;
margin-top: 25px;
padding-top: 25px;
}
.subcopy p {
font-size: 12px;
font-size: 14px;
}
/* Footer */
.footer {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
margin: 0 auto;
padding: 0;
text-align: center;
width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
}
.footer p {
color: #AEAEAE;
color: #b0adc5;
font-size: 12px;
text-align: center;
}
.footer a {
color: #b0adc5;
text-decoration: underline;
}
/* Tables */
.table table {
margin: 30px auto;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 30px auto;
width: 100%;
}
.table th {
border-bottom: 1px solid #EDEFF2;
border-bottom: 1px solid #edeff2;
margin: 0;
padding-bottom: 8px;
}
.table td {
color: #74787E;
color: #74787e;
font-size: 15px;
line-height: 18px;
margin: 0;
padding: 10px 0;
}
.content-cell {
padding: 35px;
max-width: 100vw;
padding: 32px;
}
/* Buttons */
.action {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 30px auto;
padding: 0;
text-align: center;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.button {
border-radius: 3px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
color: #FFF;
display: inline-block;
text-decoration: none;
-webkit-text-size-adjust: none;
border-radius: 4px;
color: #fff;
display: inline-block;
overflow: hidden;
text-decoration: none;
}
.button-blue {
background-color: #3097D1;
border-top: 10px solid #3097D1;
border-right: 18px solid #3097D1;
border-bottom: 10px solid #3097D1;
border-left: 18px solid #3097D1;
.button-blue,
.button-primary {
background-color: #2d3748;
border-bottom: 8px solid #2d3748;
border-left: 18px solid #2d3748;
border-right: 18px solid #2d3748;
border-top: 8px solid #2d3748;
}
.button-green {
background-color: #2ab27b;
border-top: 10px solid #2ab27b;
border-right: 18px solid #2ab27b;
border-bottom: 10px solid #2ab27b;
border-left: 18px solid #2ab27b;
.button-green,
.button-success {
background-color: #48bb78;
border-bottom: 8px solid #48bb78;
border-left: 18px solid #48bb78;
border-right: 18px solid #48bb78;
border-top: 8px solid #48bb78;
}
.button-red {
background-color: #bf5329;
border-top: 10px solid #bf5329;
border-right: 18px solid #bf5329;
border-bottom: 10px solid #bf5329;
border-left: 18px solid #bf5329;
.button-red,
.button-error {
background-color: #e53e3e;
border-bottom: 8px solid #e53e3e;
border-left: 18px solid #e53e3e;
border-right: 18px solid #e53e3e;
border-top: 8px solid #e53e3e;
}
/* Panels */
.panel {
margin: 0 0 21px;
border-left: #2d3748 solid 4px;
margin: 21px 0;
}
.panel-content {
background-color: #EDEFF2;
background-color: #edf2f7;
color: #718096;
padding: 16px;
}
.panel-content p {
color: #718096;
}
.panel-item {
padding: 0;
}
@@ -260,26 +283,8 @@ img {
padding-bottom: 0;
}
/* Promotions */
/* Utilities */
.promotion {
background-color: #FFFFFF;
border: 2px dashed #9BA2AB;
margin: 0;
margin-bottom: 25px;
margin-top: 25px;
padding: 24px;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.promotion h1 {
text-align: center;
}
.promotion p {
font-size: 15px;
text-align: center;
.break-all {
word-break: break-all;
}

View File

@@ -1,27 +0,0 @@
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
{{ config('app.name') }}
@endcomponent
@endslot
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
@slot('subcopy')
@component('mail::subcopy')
{{ $subcopy }}
@endcomponent
@endslot
@endisset
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('app.name') }}. All rights reserved.
@endcomponent
@endslot
@endcomponent

View File

@@ -1 +0,0 @@
[{{ $slot }}]({{ $url }})

View File

@@ -0,0 +1,27 @@
<x-mail::layout>
{{-- Header --}}
<x-slot:header>
<x-mail::header :url="config('app.url')">
{{ config('app.name') }}
</x-mail::header>
</x-slot:header>
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
<x-slot:subcopy>
<x-mail::subcopy>
{{ $subcopy }}
</x-mail::subcopy>
</x-slot:subcopy>
@endisset
{{-- Footer --}}
<x-slot:footer>
<x-mail::footer>
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
</x-mail::footer>
</x-slot:footer>
</x-mail::layout>