no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
{
"name": "nette/mail",
"description": "📧 Nette Mail: handy email creation and transfer library for PHP with both text and MIME-compliant support.",
"keywords": ["nette", "mail", "mailer", "smtp", "mime"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=5.6.0",
"ext-iconv": "*",
"nette/utils": "^2.4 || ~3.0.0"
},
"require-dev": {
"nette/di": "^2.4 || ~3.0.0",
"nette/tester": "^2.0",
"tracy/tracy": "^2.4"
},
"conflict": {
"nette/nette": "<2.2"
},
"suggest": {
"ext-fileinfo": "to detect type of attached files"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
}
}
+33
View File
@@ -0,0 +1,33 @@
How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:
+60
View File
@@ -0,0 +1,60 @@
Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
+86
View File
@@ -0,0 +1,86 @@
Nette Mail: Sending E-mails
===========================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/mail.svg)](https://packagist.org/packages/nette/mail)
[![Build Status](https://travis-ci.org/nette/mail.svg?branch=master)](https://travis-ci.org/nette/mail)
[![Coverage Status](https://coveralls.io/repos/github/nette/mail/badge.svg?branch=master)](https://coveralls.io/github/nette/mail?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/mail/v/stable)](https://github.com/nette/mail/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/mail/blob/master/license.md)
Almost every web application needs to send e-mails, whether newsletters or order confirmations. That's why Nette Framework provides necessary tools.
Example of creating an e-mail using `Nette\Mail\Message` class:
```php
use Nette\Mail\Message;
$mail = new Message;
$mail->setFrom('John <john@example.com>')
->addTo('peter@example.com')
->addTo('jack@example.com')
->setSubject('Order Confirmation')
->setBody("Hello, Your order has been accepted.");
```
All parameters must be encoded in UTF-8.
And sending:
```php
use Nette\Mail\SendmailMailer;
$mailer = new SendmailMailer;
$mailer->send($mail);
```
In addition to specifying recipient with `addTo()`, it's possible to specify recipient of copy with `addCc()` and recipient of blind copy: `addBcc()`.
In all these methods, including `setFrom()`, we can specifiy addressee in three ways:
```php
$mail->setFrom('john.doe@example.com');
$mail->setFrom('john.doe@example.com', 'John Doe');
$mail->setFrom('John Doe <john.doe@example.com>');
```
HTML content can be defined using `setHtmlBody()` method:
```php
$mail->setHTMLBody('<b>Sample HTML</b> <img src="background.gif">');
```
Embedded images can be inserted using `$mail->addEmbeddedFile('background.gif')`, but it is not necessary.
Why? Because Nette Framework finds and inserts all files referenced in the HTML code automatically.
This behavior can be supressed by adding `false` as a second parameter of the `setHtmlBody()` method.
If a HTML e-mail has no plain-text alternative, it will be automatically generated. And if it has no subject set, it will be taken from the `<title>` element.
Of course, it's possible to add attachments to the e-mail:
```php
$mail->addAttachment('example.zip');
```
Can e-mail sending be even easier?
Custom mailer
-------------
Default mailer uses PHP function `mail`. If you need to send mail through a SMTP server, you can use `SmtpMailer`.
```php
$mailer = new Nette\Mail\SmtpMailer([
'host' => 'smtp.gmail.com',
'username' => 'john@gmail.com',
'password' => '*****',
'secure' => 'ssl',
'context' => [
'ssl' => [
'capath' => '/path/to/my/trusted/ca/folder',
],
],
]);
$mailer->send($mail);
```
You can also create your own mailer - it's a class implementing Nette\Mail\IMailer interface.
+49
View File
@@ -0,0 +1,49 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\MailDI;
use Nette;
/**
* Mail extension for Nette DI.
*/
class MailExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'smtp' => false,
'host' => null,
'port' => null,
'username' => null,
'password' => null,
'secure' => null,
'timeout' => null,
'clientHost' => null,
'persistent' => false,
];
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$config = $this->validateConfig($this->defaults);
$mailer = $builder->addDefinition($this->prefix('mailer'))
->setClass(Nette\Mail\IMailer::class);
if (empty($config['smtp'])) {
$mailer->setFactory(Nette\Mail\SendmailMailer::class);
} else {
$mailer->setFactory(Nette\Mail\SmtpMailer::class, [$config]);
}
if ($this->name === 'mail') {
$builder->addAlias('nette.mailer', $this->prefix('mailer'));
}
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Mail;
use Nette;
class FallbackMailer implements IMailer
{
use Nette\SmartObject;
/** @var callable[] function (FallbackMailer $sender, SendException $e, IMailer $mailer, Message $mail) */
public $onFailure;
/** @var IMailer[] */
private $mailers;
/** @var int */
private $retryCount;
/** @var int in miliseconds */
private $retryWaitTime;
/**
* @param IMailer[]
* @param int
* @param int in miliseconds
*/
public function __construct(array $mailers, $retryCount = 3, $retryWaitTime = 1000)
{
$this->mailers = $mailers;
$this->retryCount = $retryCount;
$this->retryWaitTime = $retryWaitTime;
}
/**
* Sends email.
* @return void
* @throws FallbackMailerException
*/
public function send(Message $mail)
{
if (!$this->mailers) {
throw new Nette\InvalidArgumentException('At least one mailer must be provided.');
}
for ($i = 0; $i < $this->retryCount; $i++) {
if ($i > 0) {
usleep($this->retryWaitTime * 1000);
}
foreach ($this->mailers as $mailer) {
try {
$mailer->send($mail);
return;
} catch (SendException $e) {
$failures[] = $e;
$this->onFailure($this, $e, $mailer, $mail);
}
}
}
$e = new FallbackMailerException('All mailers failed to send the message.');
$e->failures = $failures;
throw $e;
}
/**
* @return static
*/
public function addMailer(IMailer $mailer)
{
$this->mailers[] = $mailer;
return $this;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Mail;
/**
* Mailer interface.
*/
interface IMailer
{
/**
* Sends email.
* @return void
* @throws SendException
*/
function send(Message $mail);
}
+444
View File
@@ -0,0 +1,444 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Mail;
use Nette;
use Nette\Utils\Strings;
/**
* Mail provides functionality to compose and send both text and MIME-compliant multipart email messages.
*
* @property string $subject
* @property string $htmlBody
*/
class Message extends MimePart
{
/** Priority */
const HIGH = 1,
NORMAL = 3,
LOW = 5;
/** @var array */
public static $defaultHeaders = [
'MIME-Version' => '1.0',
'X-Mailer' => 'Nette Framework',
];
/** @var array */
private $attachments = [];
/** @var array */
private $inlines = [];
/** @var string */
private $htmlBody = '';
public function __construct()
{
foreach (static::$defaultHeaders as $name => $value) {
$this->setHeader($name, $value);
}
$this->setHeader('Date', date('r'));
}
/**
* Sets the sender of the message.
* @param string email or format "John Doe" <doe@example.com>
* @param string
* @return static
*/
public function setFrom($email, $name = null)
{
$this->setHeader('From', $this->formatEmail($email, $name));
return $this;
}
/**
* Returns the sender of the message.
* @return array
*/
public function getFrom()
{
return $this->getHeader('From');
}
/**
* Adds the reply-to address.
* @param string email or format "John Doe" <doe@example.com>
* @param string
* @return static
*/
public function addReplyTo($email, $name = null)
{
$this->setHeader('Reply-To', $this->formatEmail($email, $name), true);
return $this;
}
/**
* Sets the subject of the message.
* @param string
* @return static
*/
public function setSubject($subject)
{
$this->setHeader('Subject', $subject);
return $this;
}
/**
* Returns the subject of the message.
* @return string|null
*/
public function getSubject()
{
return $this->getHeader('Subject');
}
/**
* Adds email recipient.
* @param string email or format "John Doe" <doe@example.com>
* @param string
* @return static
*/
public function addTo($email, $name = null) // addRecipient()
{
$this->setHeader('To', $this->formatEmail($email, $name), true);
return $this;
}
/**
* Adds carbon copy email recipient.
* @param string email or format "John Doe" <doe@example.com>
* @param string
* @return static
*/
public function addCc($email, $name = null)
{
$this->setHeader('Cc', $this->formatEmail($email, $name), true);
return $this;
}
/**
* Adds blind carbon copy email recipient.
* @param string email or format "John Doe" <doe@example.com>
* @param string
* @return static
*/
public function addBcc($email, $name = null)
{
$this->setHeader('Bcc', $this->formatEmail($email, $name), true);
return $this;
}
/**
* Formats recipient email.
* @param string
* @param string|null
* @return array
*/
private function formatEmail($email, $name)
{
if (!$name && preg_match('#^(.+) +<(.*)>\z#', $email, $matches)) {
return [$matches[2] => $matches[1]];
} else {
return [$email => $name];
}
}
/**
* Sets the Return-Path header of the message.
* @param string email
* @return static
*/
public function setReturnPath($email)
{
$this->setHeader('Return-Path', $email);
return $this;
}
/**
* Returns the Return-Path header.
* @return string
*/
public function getReturnPath()
{
return $this->getHeader('Return-Path');
}
/**
* Sets email priority.
* @param int
* @return static
*/
public function setPriority($priority)
{
$this->setHeader('X-Priority', (int) $priority);
return $this;
}
/**
* Returns email priority.
* @return int
*/
public function getPriority()
{
return $this->getHeader('X-Priority');
}
/**
* Sets HTML body.
* @param string
* @param string
* @return static
*/
public function setHtmlBody($html, $basePath = null)
{
$html = (string) $html;
if ($basePath) {
$cids = [];
$matches = Strings::matchAll(
$html,
'#
(<img[^<>]*\s src\s*=\s*
|<body[^<>]*\s background\s*=\s*
|<[^<>]+\s style\s*=\s* ["\'][^"\'>]+[:\s] url\(
|<style[^>]*>[^<]+ [:\s] url\()
(["\']?)(?![a-z]+:|[/\\#])([^"\'>)\s]+)
|\[\[ ([\w()+./@~-]+) \]\]
#ix',
PREG_OFFSET_CAPTURE
);
foreach (array_reverse($matches) as $m) {
$file = rtrim($basePath, '/\\') . '/' . (isset($m[4]) ? $m[4][0] : urldecode($m[3][0]));
if (!isset($cids[$file])) {
$cids[$file] = substr($this->addEmbeddedFile($file)->getHeader('Content-ID'), 1, -1);
}
$html = substr_replace($html,
"{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}",
$m[0][1], strlen($m[0][0])
);
}
}
if ($this->getSubject() == null) { // intentionally ==
$html = Strings::replace($html, '#<title>(.+?)</title>#is', function ($m) {
$this->setSubject(html_entity_decode($m[1], ENT_QUOTES, 'UTF-8'));
});
}
$this->htmlBody = ltrim(str_replace("\r", '', $html), "\n");
if ($this->getBody() === '' && $html !== '') {
$this->setBody($this->buildText($html));
}
return $this;
}
/**
* Gets HTML body.
* @return string
*/
public function getHtmlBody()
{
return $this->htmlBody;
}
/**
* Adds embedded file.
* @param string
* @param string
* @param string
* @return MimePart
*/
public function addEmbeddedFile($file, $content = null, $contentType = null)
{
return $this->inlines[$file] = $this->createAttachment($file, $content, $contentType, 'inline')
->setHeader('Content-ID', $this->getRandomId());
}
/**
* Adds inlined Mime Part.
* @param MimePart
* @return static
*/
public function addInlinePart(MimePart $part)
{
$this->inlines[] = $part;
return $this;
}
/**
* Adds attachment.
* @param string
* @param string
* @param string
* @return MimePart
*/
public function addAttachment($file, $content = null, $contentType = null)
{
return $this->attachments[] = $this->createAttachment($file, $content, $contentType, 'attachment');
}
/**
* Gets all email attachments.
* @return MimePart[]
*/
public function getAttachments()
{
return $this->attachments;
}
/**
* Creates file MIME part.
* @param string
* @param string|null
* @param string|null
* @param string
* @return MimePart
*/
private function createAttachment($file, $content, $contentType, $disposition)
{
$part = new MimePart;
if ($content === null) {
$content = @file_get_contents($file); // @ is escalated to exception
if ($content === false) {
throw new Nette\FileNotFoundException("Unable to read file '$file'.");
}
} else {
$content = (string) $content;
}
if (!$contentType) {
$contentType = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content);
}
if (!strcasecmp($contentType, 'message/rfc822')) { // not allowed for attached files
$contentType = 'application/octet-stream';
} elseif (!strcasecmp($contentType, 'image/svg')) { // Troublesome for some mailers...
$contentType = 'image/svg+xml';
}
$part->setBody($content);
$part->setContentType($contentType);
$part->setEncoding(preg_match('#(multipart|message)/#A', $contentType) ? self::ENCODING_8BIT : self::ENCODING_BASE64);
$part->setHeader('Content-Disposition', $disposition . '; filename="' . Strings::fixEncoding(basename($file)) . '"');
return $part;
}
/********************* building and sending ****************d*g**/
/**
* Returns encoded message.
* @return string
*/
public function generateMessage()
{
return $this->build()->getEncodedMessage();
}
/**
* Builds email. Does not modify itself, but returns a new object.
* @return static
*/
protected function build()
{
$mail = clone $this;
$mail->setHeader('Message-ID', $this->getRandomId());
$cursor = $mail;
if ($mail->attachments) {
$tmp = $cursor->setContentType('multipart/mixed');
$cursor = $cursor->addPart();
foreach ($mail->attachments as $value) {
$tmp->addPart($value);
}
}
if ($mail->htmlBody !== '') {
$tmp = $cursor->setContentType('multipart/alternative');
$cursor = $cursor->addPart();
$alt = $tmp->addPart();
if ($mail->inlines) {
$tmp = $alt->setContentType('multipart/related');
$alt = $alt->addPart();
foreach ($mail->inlines as $value) {
$tmp->addPart($value);
}
}
$alt->setContentType('text/html', 'UTF-8')
->setEncoding(preg_match('#[^\n]{990}#', $mail->htmlBody)
? self::ENCODING_QUOTED_PRINTABLE
: (preg_match('#[\x80-\xFF]#', $mail->htmlBody) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
->setBody($mail->htmlBody);
}
$text = $mail->getBody();
$mail->setBody('');
$cursor->setContentType('text/plain', 'UTF-8')
->setEncoding(preg_match('#[^\n]{990}#', $text)
? self::ENCODING_QUOTED_PRINTABLE
: (preg_match('#[\x80-\xFF]#', $text) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
->setBody($text);
return $mail;
}
/**
* Builds text content.
* @param string
* @return string
*/
protected function buildText($html)
{
$text = Strings::replace($html, [
'#<(style|script|head).*</\\1>#Uis' => '',
'#<t[dh][ >]#i' => ' $0',
'#<a\s[^>]*href=(?|"([^"]+)"|\'([^\']+)\')[^>]*>(.*?)</a>#is' => '$2 &lt;$1&gt;',
'#[\r\n]+#' => ' ',
'#<(/?p|/?h\d|li|br|/tr)[ >/]#i' => "\n$0",
]);
$text = html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8');
$text = Strings::replace($text, '#[ \t]+#', ' ');
return trim($text);
}
/** @return string */
private function getRandomId()
{
return '<' . Nette\Utils\Random::generate() . '@'
. preg_replace('#[^\w.-]+#', '', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n'))
. '>';
}
}
+331
View File
@@ -0,0 +1,331 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Mail;
use Nette;
use Nette\Utils\Strings;
/**
* MIME message part.
*
* @property string $body
*/
class MimePart
{
use Nette\SmartObject;
/** encoding */
const ENCODING_BASE64 = 'base64',
ENCODING_7BIT = '7bit',
ENCODING_8BIT = '8bit',
ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
/** @internal */
const EOL = "\r\n";
const LINE_LENGTH = 76;
/** @var array */
private $headers = [];
/** @var array */
private $parts = [];
/** @var string */
private $body = '';
/**
* Sets a header.
* @param string
* @param string|array value or pair email => name
* @param bool
* @return static
*/
public function setHeader($name, $value, $append = false)
{
if (!$name || preg_match('#[^a-z0-9-]#i', $name)) {
throw new Nette\InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
}
if ($value == null) { // intentionally ==
if (!$append) {
unset($this->headers[$name]);
}
} elseif (is_array($value)) { // email
$tmp = &$this->headers[$name];
if (!$append || !is_array($tmp)) {
$tmp = [];
}
foreach ($value as $email => $recipient) {
if ($recipient === null) {
// continue
} elseif (!Strings::checkEncoding($recipient)) {
Nette\Utils\Validators::assert($recipient, 'unicode', "header '$name'");
} elseif (preg_match('#[\r\n]#', $recipient)) {
throw new Nette\InvalidArgumentException('Name must not contain line separator.');
}
Nette\Utils\Validators::assert($email, 'email', "header '$name'");
$tmp[$email] = $recipient;
}
} else {
$value = (string) $value;
if (!Strings::checkEncoding($value)) {
throw new Nette\InvalidArgumentException('Header is not valid UTF-8 string.');
}
$this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
}
return $this;
}
/**
* Returns a header.
* @param string
* @return mixed
*/
public function getHeader($name)
{
return isset($this->headers[$name]) ? $this->headers[$name] : null;
}
/**
* Removes a header.
* @param string
* @return static
*/
public function clearHeader($name)
{
unset($this->headers[$name]);
return $this;
}
/**
* Returns an encoded header.
* @param string
* @return string|null
*/
public function getEncodedHeader($name)
{
$offset = strlen($name) + 2; // colon + space
if (!isset($this->headers[$name])) {
return null;
} elseif (is_array($this->headers[$name])) {
$s = '';
foreach ($this->headers[$name] as $email => $name) {
if ($name != null) { // intentionally ==
$s .= self::encodeHeader($name, $offset, true);
$email = " <$email>";
}
$s .= self::append($email . ',', $offset);
}
return ltrim(substr($s, 0, -1)); // last comma
} elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"\z#', $this->headers[$name], $m)) { // Content-Disposition
$offset += strlen($m[1]);
return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';
} else {
return ltrim(self::encodeHeader($this->headers[$name], $offset));
}
}
/**
* Returns all headers.
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Sets Content-Type header.
* @param string
* @param string
* @return static
*/
public function setContentType($contentType, $charset = null)
{
$this->setHeader('Content-Type', $contentType . ($charset ? "; charset=$charset" : ''));
return $this;
}
/**
* Sets Content-Transfer-Encoding header.
* @param string
* @return static
*/
public function setEncoding($encoding)
{
$this->setHeader('Content-Transfer-Encoding', $encoding);
return $this;
}
/**
* Returns Content-Transfer-Encoding header.
* @return string
*/
public function getEncoding()
{
return $this->getHeader('Content-Transfer-Encoding');
}
/**
* Adds or creates new multipart.
* @return self
*/
public function addPart(self $part = null)
{
return $this->parts[] = $part === null ? new self : $part;
}
/**
* Sets textual body.
* @param string
* @return static
*/
public function setBody($body)
{
$this->body = (string) $body;
return $this;
}
/**
* Gets textual body.
* @return string
*/
public function getBody()
{
return $this->body;
}
/********************* building ****************d*g**/
/**
* Returns encoded message.
* @return string
*/
public function getEncodedMessage()
{
$output = '';
$boundary = '--------' . Nette\Utils\Random::generate();
foreach ($this->headers as $name => $value) {
$output .= $name . ': ' . $this->getEncodedHeader($name);
if ($this->parts && $name === 'Content-Type') {
$output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
}
$output .= self::EOL;
}
$output .= self::EOL;
$body = $this->body;
if ($body !== '') {
switch ($this->getEncoding()) {
case self::ENCODING_QUOTED_PRINTABLE:
$output .= quoted_printable_encode($body);
break;
case self::ENCODING_BASE64:
$output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
break;
case self::ENCODING_7BIT:
$body = preg_replace('#[\x80-\xFF]+#', '', $body);
// break omitted
case self::ENCODING_8BIT:
$body = str_replace(["\x00", "\r"], '', $body);
$body = str_replace("\n", self::EOL, $body);
$output .= $body;
break;
default:
throw new Nette\InvalidStateException('Unknown encoding.');
}
}
if ($this->parts) {
if (substr($output, -strlen(self::EOL)) !== self::EOL) {
$output .= self::EOL;
}
foreach ($this->parts as $part) {
$output .= '--' . $boundary . self::EOL . $part->getEncodedMessage() . self::EOL;
}
$output .= '--' . $boundary . '--';
}
return $output;
}
/********************* QuotedPrintable helpers ****************d*g**/
/**
* Converts a 8 bit header to a string.
* @param string
* @param int
* @param bool
* @return string
*/
private static function encodeHeader($s, &$offset = 0, $quotes = false)
{
if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~=? _\r\n\t") === strlen($s)) {
if ($quotes && preg_match('#[^ a-zA-Z0-9!\#$%&\'*+/?^_`{|}~-]#', $s)) { // RFC 2822 atext except =
return self::append('"' . addcslashes($s, '"\\') . '"', $offset);
}
return self::append($s, $offset);
}
$o = '';
if ($offset >= 55) { // maximum for iconv_mime_encode
$o = self::EOL . "\t";
$offset = 1;
}
$s = iconv_mime_encode(str_repeat(' ', $old = $offset), $s, [
'scheme' => 'B', // Q is broken
'input-charset' => 'UTF-8',
'output-charset' => 'UTF-8',
]);
$offset = strlen($s) - strrpos($s, "\n");
$s = str_replace("\n ", "\n\t", substr($s, $old + 2)); // adds ': '
return $o . $s;
}
private static function append($s, &$offset = 0)
{
if ($offset + strlen($s) > self::LINE_LENGTH) {
$offset = 1;
$s = self::EOL . "\t" . $s;
}
$offset += strlen($s);
return $s;
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Mail;
use Nette;
/**
* Sends emails via the PHP internal mail() function.
*/
class SendmailMailer implements IMailer
{
use Nette\SmartObject;
/** @var string|null */
public $commandArgs;
/**
* Sends email.
* @return void
* @throws SendException
*/
public function send(Message $mail)
{
if (!function_exists('mail')) {
throw new SendException('Unable to send email: mail() has been disabled.');
}
$tmp = clone $mail;
$tmp->setHeader('Subject', null);
$tmp->setHeader('To', null);
$parts = explode(Message::EOL . Message::EOL, $tmp->generateMessage(), 2);
$args = [
str_replace(Message::EOL, PHP_EOL, $mail->getEncodedHeader('To')),
str_replace(Message::EOL, PHP_EOL, $mail->getEncodedHeader('Subject')),
str_replace(Message::EOL, PHP_EOL, $parts[1]),
str_replace(Message::EOL, PHP_EOL, $parts[0]),
];
if ($this->commandArgs) {
$args[] = (string) $this->commandArgs;
}
$res = Nette\Utils\Callback::invokeSafe('mail', $args, function ($message) use (&$info) {
$info = ": $message";
});
if ($res === false) {
throw new SendException("Unable to send email$info.");
}
}
}
+220
View File
@@ -0,0 +1,220 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Mail;
use Nette;
/**
* Sends emails via the SMTP server.
*/
class SmtpMailer implements IMailer
{
use Nette\SmartObject;
/** @var resource */
private $connection;
/** @var string */
private $host;
/** @var int */
private $port;
/** @var string */
private $username;
/** @var string */
private $password;
/** @var string ssl | tls | (empty) */
private $secure;
/** @var int */
private $timeout;
/** @var resource */
private $context;
/** @var bool */
private $persistent;
/** @var string */
private $clientHost;
public function __construct(array $options = [])
{
if (isset($options['host'])) {
$this->host = $options['host'];
$this->port = isset($options['port']) ? (int) $options['port'] : null;
} else {
$this->host = ini_get('SMTP');
$this->port = (int) ini_get('smtp_port');
}
$this->username = isset($options['username']) ? $options['username'] : '';
$this->password = isset($options['password']) ? $options['password'] : '';
$this->secure = isset($options['secure']) ? $options['secure'] : '';
$this->timeout = isset($options['timeout']) ? (int) $options['timeout'] : 20;
$this->context = isset($options['context']) ? stream_context_create($options['context']) : stream_context_get_default();
if (!$this->port) {
$this->port = $this->secure === 'ssl' ? 465 : 25;
}
$this->persistent = !empty($options['persistent']);
if (isset($options['clientHost'])) {
$this->clientHost = $options['clientHost'];
} else {
$this->clientHost = isset($_SERVER['HTTP_HOST']) && preg_match('#^[\w.-]+\z#', $_SERVER['HTTP_HOST'])
? $_SERVER['HTTP_HOST']
: 'localhost';
}
}
/**
* Sends email.
* @return void
* @throws SmtpException
*/
public function send(Message $mail)
{
$tmp = clone $mail;
$tmp->setHeader('Bcc', null);
$data = $tmp->generateMessage();
try {
if (!$this->connection) {
$this->connect();
}
if (($from = $mail->getHeader('Return-Path'))
|| ($from = key($mail->getHeader('From')))
) {
$this->write("MAIL FROM:<$from>", 250);
}
foreach (array_merge(
(array) $mail->getHeader('To'),
(array) $mail->getHeader('Cc'),
(array) $mail->getHeader('Bcc')
) as $email => $name) {
$this->write("RCPT TO:<$email>", [250, 251]);
}
$this->write('DATA', 354);
$data = preg_replace('#^\.#m', '..', $data);
$this->write($data);
$this->write('.', 250);
if (!$this->persistent) {
$this->write('QUIT', 221);
$this->disconnect();
}
} catch (SmtpException $e) {
if ($this->connection) {
$this->disconnect();
}
throw $e;
}
}
/**
* Connects and authenticates to SMTP server.
* @return void
*/
protected function connect()
{
$this->connection = @stream_socket_client(// @ is escalated to exception
($this->secure === 'ssl' ? 'ssl://' : '') . $this->host . ':' . $this->port,
$errno, $error, $this->timeout, STREAM_CLIENT_CONNECT, $this->context
);
if (!$this->connection) {
throw new SmtpException($error ?: error_get_last()['message'], $errno);
}
stream_set_timeout($this->connection, $this->timeout, 0);
$this->read(); // greeting
$this->write("EHLO $this->clientHost");
$ehloResponse = $this->read();
if ((int) $ehloResponse !== 250) {
$this->write("HELO $this->clientHost", 250);
}
if ($this->secure === 'tls') {
$this->write('STARTTLS', 220);
if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
throw new SmtpException('Unable to connect via TLS.');
}
$this->write("EHLO $this->clientHost", 250);
}
if ($this->username != null && $this->password != null) {
$authMechanisms = [];
if (preg_match('~^250[ -]AUTH (.*)$~im', $ehloResponse, $matches)) {
$authMechanisms = explode(' ', trim($matches[1]));
}
if (in_array('PLAIN', $authMechanisms, true)) {
$credentials = $this->username . "\0" . $this->username . "\0" . $this->password;
$this->write('AUTH PLAIN ' . base64_encode($credentials), 235, 'PLAIN credentials');
} else {
$this->write('AUTH LOGIN', 334);
$this->write(base64_encode($this->username), 334, 'username');
$this->write(base64_encode($this->password), 235, 'password');
}
}
}
/**
* Disconnects from SMTP server.
* @return void
*/
protected function disconnect()
{
fclose($this->connection);
$this->connection = null;
}
/**
* Writes data to server and checks response against expected code if some provided.
* @param string
* @param int|int[] response code
* @param string error message
* @return void
*/
protected function write($line, $expectedCode = null, $message = null)
{
fwrite($this->connection, $line . Message::EOL);
if ($expectedCode) {
$response = $this->read();
if (!in_array((int) $response, (array) $expectedCode, true)) {
throw new SmtpException('SMTP server did not accept ' . ($message ?: $line) . ' with error: ' . trim($response));
}
}
}
/**
* Reads response from server.
* @return string
*/
protected function read()
{
$s = '';
while (($line = fgets($this->connection, 1000)) != null) { // intentionally ==
$s .= $line;
if (substr($line, 3, 1) === ' ') {
break;
}
}
return $s;
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Mail;
use Nette;
/**
* Exception thrown when a mail sending error is encountered.
*/
class SendException extends Nette\InvalidStateException
{
}
/**
* SMTP mailer exception.
*/
class SmtpException extends SendException
{
}
class FallbackMailerException extends SendException
{
/** @var SendException[] */
public $failures;
}