1
0
This commit is contained in:
Philip Wagner
2024-08-31 10:01:49 +02:00
commit 78b6c0d381
1169 changed files with 235103 additions and 0 deletions

71
kirby/src/Email/Body.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
namespace Kirby\Email;
use Kirby\Toolkit\Properties;
/**
* Representation of a an Email body
* with a text and optional html version
*
* @package Kirby Email
* @author Bastian Allgeier <bastian@getkirby.com>,
* Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
class Body
{
protected string|null $html;
protected string|null $text;
/**
* Email body constructor
*/
public function __construct(array $props = [])
{
$this->html = $props['html'] ?? null;
$this->text = $props['text'] ?? null;
}
/**
* Creates a new instance while
* merging initial and new properties
* @deprecated 4.0.0
*/
public function clone(array $props = []): static
{
return new static(array_merge_recursive([
'html' => $this->html,
'text' => $this->text
], $props));
}
/**
* Returns the HTML content of the email body
*/
public function html(): string
{
return $this->html ?? '';
}
/**
* Returns the plain text content of the email body
*/
public function text(): string
{
return $this->text ?? '';
}
/**
* @since 4.0.0
*/
public function toArray(): array
{
return [
'html' => $this->html(),
'text' => $this->text()
];
}
}

296
kirby/src/Email/Email.php Normal file
View File

@@ -0,0 +1,296 @@
<?php
namespace Kirby\Email;
use Closure;
use Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\V;
/**
* Wrapper for email libraries
*
* @package Kirby Email
* @author Bastian Allgeier <bastian@getkirby.com>,
* Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
class Email
{
/**
* If set to `true`, the debug mode is enabled
* for all emails
*/
public static bool $debug = false;
/**
* Store for sent emails when `Email::$debug`
* is set to `true`
*/
public static array $emails = [];
protected bool $isSent = false;
protected array $attachments;
protected Body $body;
protected array $bcc;
protected Closure|null $beforeSend;
protected array $cc;
protected string $from;
protected string|null $fromName;
protected string $replyTo;
protected string|null $replyToName;
protected string $subject;
protected array $to;
protected array|null $transport;
/**
* Email constructor
*/
public function __construct(array $props = [], bool $debug = false)
{
foreach (['body', 'from', 'to', 'subject'] as $required) {
if (isset($props[$required]) === false) {
throw new InvalidArgumentException('The property "' . $required . '" is required');
}
}
if (is_string($props['body']) === true) {
$props['body'] = ['text' => $props['body']];
}
$this->attachments = $props['attachments'] ?? [];
$this->bcc = $this->resolveEmail($props['bcc'] ?? null);
$this->beforeSend = $props['beforeSend'] ?? null;
$this->body = new Body($props['body']);
$this->cc = $this->resolveEmail($props['cc'] ?? null);
$this->from = $this->resolveEmail($props['from'], false);
$this->fromName = $props['fromName'] ?? null;
$this->replyTo = $this->resolveEmail($props['replyTo'] ?? null, false);
$this->replyToName = $props['replyToName'] ?? null;
$this->subject = $props['subject'];
$this->to = $this->resolveEmail($props['to']);
$this->transport = $props['transport'] ?? null;
// @codeCoverageIgnoreStart
if (static::$debug === false && $debug === false) {
$this->send();
} elseif (static::$debug === true) {
static::$emails[] = $this;
}
// @codeCoverageIgnoreEnd
}
/**
* Returns the email attachments
*/
public function attachments(): array
{
return $this->attachments;
}
/**
* Returns the email body
*/
public function body(): Body|null
{
return $this->body;
}
/**
* Returns "bcc" recipients
*/
public function bcc(): array
{
return $this->bcc;
}
/**
* Returns the beforeSend callback closure,
* which has access to the PHPMailer instance
*/
public function beforeSend(): Closure|null
{
return $this->beforeSend;
}
/**
* Returns "cc" recipients
*/
public function cc(): array
{
return $this->cc;
}
/**
* Creates a new instance while
* merging initial and new properties
* @deprecated 4.0.0
*/
public function clone(array $props = []): static
{
return new static(array_merge_recursive([
'attachments' => $this->attachments,
'bcc' => $this->bcc,
'beforeSend' => $this->beforeSend,
'body' => $this->body->toArray(),
'cc' => $this->cc,
'from' => $this->from,
'fromName' => $this->fromName,
'replyTo' => $this->replyTo,
'replyToName' => $this->replyToName,
'subject' => $this->subject,
'to' => $this->to,
'transport' => $this->transport
], $props));
}
/**
* Returns default transport settings
*/
protected function defaultTransport(): array
{
return [
'type' => 'mail'
];
}
/**
* Returns the "from" email address
*/
public function from(): string
{
return $this->from;
}
/**
* Returns the "from" name
*/
public function fromName(): string|null
{
return $this->fromName;
}
/**
* Checks if the email has an HTML body
*/
public function isHtml(): bool
{
return empty($this->body()->html()) === false;
}
/**
* Checks if the email has been sent successfully
*/
public function isSent(): bool
{
return $this->isSent;
}
/**
* Returns the "reply to" email address
*/
public function replyTo(): string
{
return $this->replyTo;
}
/**
* Returns the "reply to" name
*/
public function replyToName(): string|null
{
return $this->replyToName;
}
/**
* Converts single or multiple email addresses to a sanitized format
*
* @throws \Exception
*/
protected function resolveEmail(
string|array|null $email = null,
bool $multiple = true
): array|string {
if ($email === null) {
return $multiple === true ? [] : '';
}
if (is_array($email) === false) {
$email = [$email => null];
}
$result = [];
foreach ($email as $address => $name) {
// convert simple email arrays to associative arrays
if (is_int($address) === true) {
// the value is the address, there is no name
$address = $name;
$result[$address] = null;
} else {
$result[$address] = $name;
}
// ensure that the address is valid
if (V::email($address) === false) {
throw new Exception(sprintf('"%s" is not a valid email address', $address));
}
}
return $multiple === true ? $result : array_keys($result)[0];
}
/**
* Sends the email
*/
public function send(): bool
{
return $this->isSent = true;
}
/**
* Returns the email subject
*/
public function subject(): string
{
return $this->subject;
}
/**
* Returns the email recipients
*/
public function to(): array
{
return $this->to;
}
/**
* Returns the email transports settings
*/
public function transport(): array
{
return $this->transport ?? $this->defaultTransport();
}
/**
* @since 4.0.0
*/
public function toArray(): array
{
return [
'attachments' => $this->attachments(),
'bcc' => $this->bcc(),
'body' => $this->body()->toArray(),
'cc' => $this->cc(),
'from' => $this->from(),
'fromName' => $this->fromName(),
'replyTo' => $this->replyTo(),
'replyToName' => $this->replyToName(),
'subject' => $this->subject(),
'to' => $this->to(),
'transport' => $this->transport()
];
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Kirby\Email;
use Closure;
use Kirby\Exception\InvalidArgumentException;
use PHPMailer\PHPMailer\PHPMailer as Mailer;
/**
* Wrapper for PHPMailer library
*
* @package Kirby Email
* @author Bastian Allgeier <bastian@getkirby.com>,
* Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
class PHPMailer extends Email
{
/**
* Sends email via PHPMailer library
*
* @throws \Kirby\Exception\InvalidArgumentException
*/
public function send(bool $debug = false): bool
{
$mailer = new Mailer(true);
// set sender's address
$mailer->setFrom($this->from(), $this->fromName() ?? '');
// optional reply-to address
if ($replyTo = $this->replyTo()) {
$mailer->addReplyTo($replyTo, $this->replyToName() ?? '');
}
// add (multiple) recipient, CC & BCC addresses
foreach ($this->to() as $email => $name) {
$mailer->addAddress($email, $name ?? '');
}
foreach ($this->cc() as $email => $name) {
$mailer->addCC($email, $name ?? '');
}
foreach ($this->bcc() as $email => $name) {
$mailer->addBCC($email, $name ?? '');
}
$mailer->Subject = $this->subject();
$mailer->CharSet = 'UTF-8';
// set body according to html/text
if ($this->isHtml()) {
$mailer->isHTML(true);
$mailer->Body = $this->body()->html();
$mailer->AltBody = $this->body()->text();
} else {
$mailer->Body = $this->body()->text();
}
// add attachments
foreach ($this->attachments() as $attachment) {
$mailer->addAttachment($attachment);
}
// smtp transport settings
if (($this->transport()['type'] ?? 'mail') === 'smtp') {
$mailer->isSMTP();
$mailer->Host = $this->transport()['host'] ?? null;
$mailer->SMTPAuth = $this->transport()['auth'] ?? false;
$mailer->Username = $this->transport()['username'] ?? null;
$mailer->Password = $this->transport()['password'] ?? null;
$mailer->SMTPSecure = $this->transport()['security'] ?? 'ssl';
$mailer->Port = $this->transport()['port'] ?? null;
if ($mailer->SMTPSecure === true) {
switch ($mailer->Port) {
case null:
case 587:
$mailer->SMTPSecure = 'tls';
$mailer->Port = 587;
break;
case 465:
$mailer->SMTPSecure = 'ssl';
break;
default:
throw new InvalidArgumentException(
'Could not automatically detect the "security" protocol from the ' .
'"port" option, please set it explicitly to "tls" or "ssl".'
);
}
}
}
// accessible phpMailer instance
$beforeSend = $this->beforeSend();
if ($beforeSend instanceof Closure) {
$mailer = $beforeSend->call($this, $mailer) ?? $mailer;
if ($mailer instanceof Mailer === false) {
throw new InvalidArgumentException('"beforeSend" option return should be instance of PHPMailer\PHPMailer\PHPMailer class');
}
}
if ($debug === true) {
return $this->isSent = true;
}
return $this->isSent = $mailer->send(); // @codeCoverageIgnore
}
}