(Grav GitSync) Automatic Commit from RealStickman

This commit is contained in:
RealStickman 2023-05-12 15:06:01 +02:00 committed by GitSync
parent 344e5c6628
commit 711e975be2
732 changed files with 35532 additions and 32076 deletions

View File

@ -1,3 +1,25 @@
# v1.10.41.2
## 05/11/2023
1. [](#improved)
* Fixed an issue with `lastBackup()` that caused admin dashboard to fail with an error.
# v1.10.41.1
## 05/09/2023
1. [](#improved)
* Fixed another Toolbox deprecation error for `lastBackup()`
# v1.10.41
## 05/09/2023
1. [](#new)
* Updated to use new `BaconQRCode` version `2.0.8` for new SVG features + PHP 8.2+ fixes
1. [](#improved)
* Require Grav `v1.7.41`
* Fixed a deprecated message where `Admin::$routes` was being dynamically defined
* Fixes to use non-deprecated methods in `ScssCompiler`
# v1.10.40 # v1.10.40
## 03/22/2023 ## 03/22/2023

View File

@ -1,7 +1,7 @@
name: Admin Panel name: Admin Panel
slug: admin slug: admin
type: plugin type: plugin
version: 1.10.40 version: 1.10.41.2
description: Adds an advanced administration panel to manage your site description: Adds an advanced administration panel to manage your site
icon: empire icon: empire
author: author:
@ -15,7 +15,7 @@ docs: https://github.com/getgrav/grav-plugin-admin/blob/develop/README.md
license: MIT license: MIT
dependencies: dependencies:
- { name: grav, version: '>=1.7.32' } - { name: grav, version: '>=1.7.41' }
- { name: form, version: '>=6.0.1' } - { name: form, version: '>=6.0.1' }
- { name: login, version: '>=3.7.0' } - { name: login, version: '>=3.7.0' }
- { name: email, version: '>=3.1.6' } - { name: email, version: '>=3.1.6' }

View File

@ -102,6 +102,9 @@ class Admin
/** @var array */ /** @var array */
public $languages_enabled = []; public $languages_enabled = [];
/** @var Uri $uri */ /** @var Uri $uri */
/** @var array */
public $routes = [];
protected $uri; protected $uri;
/** @var array */ /** @var array */
protected $pages = []; protected $pages = [];
@ -1435,9 +1438,15 @@ class Admin
*/ */
public function lastBackup() public function lastBackup()
{ {
$file = JsonFile::instance($this->grav['locator']->findResource('log://backup.log')); $backup_file = $this->grav['locator']->findResource('log://backup.log');
$content = $file->content(); $content = null;
if (empty($content)) {
if ($backup_file) {
$file = JsonFile::instance((string) $backup_file);
$content = $file->content() ?? null;
}
if (!file_exists($backup_file) || is_null($content) || !isset($content['time'])) {
return [ return [
'days' => '∞', 'days' => '∞',
'chart_fill' => 100, 'chart_fill' => 100,
@ -1448,10 +1457,8 @@ class Admin
$backup = new \DateTime(); $backup = new \DateTime();
$backup->setTimestamp($content['time']); $backup->setTimestamp($content['time']);
$diff = $backup->diff(new \DateTime()); $diff = $backup->diff(new \DateTime());
$days = $diff->days;
$days = $diff->days;
$chart_fill = $days > 30 ? 100 : round($days / 30 * 100); $chart_fill = $days > 30 ? 100 : round($days / 30 * 100);
return [ return [
'days' => $days, 'days' => $days,
'chart_fill' => $chart_fill, 'chart_fill' => $chart_fill,

View File

@ -10,6 +10,7 @@
namespace Grav\Plugin\Admin; namespace Grav\Plugin\Admin;
use ScssPhp\ScssPhp\Compiler; use ScssPhp\ScssPhp\Compiler;
use ScssPhp\ScssPhp\ValueConverter;
class ScssCompiler class ScssCompiler
{ {
@ -31,7 +32,13 @@ class ScssCompiler
public function setVariables(array $variables) public function setVariables(array $variables)
{ {
$this->compiler()->setVariables($variables); // $parsed = ValueConverter::fromPhp($variables);
$parsed = [];
foreach ($variables as $key => $value) {
$parsed[$key] = ValueConverter::parseValue($value);
}
$this->compiler()->addVariables($parsed);
return $this; return $this;
} }
@ -55,7 +62,7 @@ class ScssCompiler
foreach ($input_paths as $input_file) { foreach ($input_paths as $input_file) {
$input .= trim(file_get_contents($input_file)) . "\n\n"; $input .= trim(file_get_contents($input_file)) . "\n\n";
} }
$output = $this->compiler()->compile($input); $output = $this->compiler()->compileString($input)->getCss();
file_put_contents($output_file, $output); file_put_contents($output_file, $output);
return $this; return $this;
} }

View File

@ -22,7 +22,7 @@
"require": { "require": {
"php": "^7.3.6 || ^8.0", "php": "^7.3.6 || ^8.0",
"ext-json": "*", "ext-json": "*",
"scssphp/scssphp": "^1.7", "scssphp/scssphp": "^1.11",
"laminas/laminas-zendframework-bridge": "^1.4", "laminas/laminas-zendframework-bridge": "^1.4",
"p3k/picofeed": "@stable" "p3k/picofeed": "@stable"
}, },

View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "1f6a6bec99c3a85ac753a1548c22f5d7", "content-hash": "bc8a9ba032ce28a2c93282b6f1a6e7b9",
"packages": [ "packages": [
{ {
"name": "laminas/laminas-xml", "name": "laminas/laminas-xml",
@ -900,16 +900,16 @@
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "1.9.0", "version": "1.9.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318" "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b",
"reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -928,11 +928,6 @@
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
}, },
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": { "autoload": {
"files": [ "files": [
"src/functions_include.php" "src/functions_include.php"
@ -990,7 +985,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/1.9.0" "source": "https://github.com/guzzle/psr7/tree/1.9.1"
}, },
"funding": [ "funding": [
{ {
@ -1006,7 +1001,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-06-20T21:43:03+00:00" "time": "2023-04-17T16:00:37+00:00"
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
@ -1834,25 +1829,25 @@
}, },
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "1.0.1", "version": "1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-message.git", "url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.0" "php": "^7.2 || ^8.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0.x-dev" "dev-master": "1.1.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -1881,9 +1876,9 @@
"response" "response"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-message/tree/master" "source": "https://github.com/php-fig/http-message/tree/1.1"
}, },
"time": "2016-08-06T14:39:51+00:00" "time": "2023-04-04T09:50:52+00:00"
}, },
{ {
"name": "ralouphie/getallheaders", "name": "ralouphie/getallheaders",
@ -2060,16 +2055,16 @@
}, },
{ {
"name": "sebastian/diff", "name": "sebastian/diff",
"version": "3.0.3", "version": "3.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/diff.git", "url": "https://github.com/sebastianbergmann/diff.git",
"reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6296a0c086dd0117c1b78b059374d7fcbe7545ae",
"reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2114,7 +2109,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/diff/issues", "issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" "source": "https://github.com/sebastianbergmann/diff/tree/3.0.4"
}, },
"funding": [ "funding": [
{ {
@ -2122,7 +2117,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2020-11-30T07:59:04+00:00" "time": "2023-05-07T05:30:20+00:00"
}, },
{ {
"name": "sebastian/environment", "name": "sebastian/environment",

View File

@ -98,7 +98,7 @@ class InstalledVersions
{ {
foreach (self::getInstalled() as $installed) { foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) { if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
} }
} }
@ -119,7 +119,7 @@ class InstalledVersions
*/ */
public static function satisfies(VersionParser $parser, $packageName, $constraint) public static function satisfies(VersionParser $parser, $packageName, $constraint)
{ {
$constraint = $parser->parseConstraints($constraint); $constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint); return $provided->matches($constraint);
@ -328,7 +328,9 @@ class InstalledVersions
if (isset(self::$installedByVendor[$vendorDir])) { if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir]; $installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) { } elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1]; self::$installed = $installed[count($installed) - 1];
} }
@ -340,12 +342,17 @@ class InstalledVersions
// only require the installed.php file if this file is loaded from its dumped location, // only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') { if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php'; /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else { } else {
self::$installed = array(); self::$installed = array();
} }
} }
$installed[] = self::$installed;
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed; return $installed;
} }

View File

@ -3,7 +3,7 @@
'name' => 'getgrav/grav-plugin-admin', 'name' => 'getgrav/grav-plugin-admin',
'pretty_version' => 'dev-develop', 'pretty_version' => 'dev-develop',
'version' => 'dev-develop', 'version' => 'dev-develop',
'reference' => '5f1b3e1e4a413bf5e45e99b8d5aa174b6806c12c', 'reference' => 'be85fb4194474700fbd8fe60b48b27613b47ce59',
'type' => 'grav-plugin', 'type' => 'grav-plugin',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -13,7 +13,7 @@
'getgrav/grav-plugin-admin' => array( 'getgrav/grav-plugin-admin' => array(
'pretty_version' => 'dev-develop', 'pretty_version' => 'dev-develop',
'version' => 'dev-develop', 'version' => 'dev-develop',
'reference' => '5f1b3e1e4a413bf5e45e99b8d5aa174b6806c12c', 'reference' => 'be85fb4194474700fbd8fe60b48b27613b47ce59',
'type' => 'grav-plugin', 'type' => 'grav-plugin',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),

View File

@ -1,3 +1,45 @@
# v4.0.0
## 05/09/2023
1. [](#new)
* Migrated from `Swiftmailer` (which has been deprecated) to `Symfony/Mailer`, a more modern and extensible mailing library.
* Built-in support for `SMTP`, `SMTPS`, `Sendmail` and `Native` (uses best solution per-platform) engines
* Added ability to have 3rd party plugins that provide new engines (e.g. `SendGrid`, `MailJet`, etc.) with `SMTP`, `API` or `HTTPS` transports for faster and more reliable email sending
* More flexible options for email formatting including RFC822 `name-addr` format (`Joe Bloggs <user@example.com>`) and simple arrays (`[user@example.com, Joe Bloggs]`)
* Support `tags` and `metadata` in `Email::buildMessage()`
* Two new events `onEmailEngines` and `onEmailTransportDsn` to facilitate the integration of custom engines via plugins
# v4.0.0-rc.4
## 02/27/2023
1. [](#bugfix)
* Fixed for multiple recipients [#167](https://github.com/getgrav/grav-plugin-email/issues/167)
* Fix for simple array format with names which wasn't working
# v4.0.0-rc.3
## 10/27/2022
1. [](#bugfix)
* Fixed an issue with array based email address
# v4.0.0-rc.2
## 10/19/2022
1. [](#improved)
* Set `sendmail` as default engine to provide better fallback for unsupported `mailer` option
* Added info about available engine plugins in `README.md`
# v4.0.0-rc.1
## 10/05/2022
1. [](#new)
* Migrated from `Swiftmailer` (which has been deprecated) to `Symfony/Mailer`, a more modern and extensible mailing library.
* Built-in support for `SMTP`, `SMTPS`, `Sendmail` and `Native` (uses best solution per-platform) engines
* Added ability to have 3rd party plugins that provide new engines (e.g. `SendGrid`, `MailJet`, etc.) with `SMTP`, `API` or `HTTPS` transports for faster and more reliable email sending
* More flexible options for email formatting including RFC822 `name-addr` format (`Joe Bloggs <user@example.com>`) and simple arrays (`[user@example.com, Joe Bloggs]`)
* Support `tags` and `metadata` in `Email::buildMessage()`
* Two new events `onEmailEngines` and `onEmailTransportDsn` to facilitate the integration of custom engines via plugins
# v3.2.0 # v3.2.0
## 03/28/2022 ## 03/28/2022

View File

@ -1,6 +1,8 @@
# Grav Email Plugin # Grav Email Plugin
The **email plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to send email. This is particularly useful for the **admin** and **login** plugins. The **email plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to send email utilizing the `symfony/mailer` package. This is particularly useful for the **admin** and **login** plugins.
> IMPORTANT: **Version 4.0** replaced the old deprecated **SwiftMailer** library with **Symfony/Mailer** package. This is a modern and well supported library that also has the capability to support 3rd party transport engines such as `SendGrid`, `MailJet`, `MailGun`, `MailChimp`, etc. This library should be backwards compatible with existing configurations, but please create an issue if you run into any problems.
# Installation # Installation
@ -12,28 +14,20 @@ $ bin/gpm install email
# Configuration # Configuration
By default, the plugin uses PHP Mail as the mail engine. The plugin uses `sendmail` binary as the default mail engine.
``` ```
enabled: true enabled: true
from: from:
from_name:
to: to:
to_name:
queue:
enabled: true
flush_frequency: '* * * * *'
flush_msg_limit: 10
flush_time_limit: 100
mailer: mailer:
engine: sendmail engine: sendmail
smtp: smtp:
server: localhost server: localhost
port: 25 port: 25
encryption: none encryption: none
user: '' user:
password: '' password:
auth_mode: ''
sendmail: sendmail:
bin: '/usr/sbin/sendmail -bs' bin: '/usr/sbin/sendmail -bs'
content_type: text/html content_type: text/html
@ -48,9 +42,51 @@ The first setting you'd likely change is your `Email from` / `Email to` names an
Also, you'd likely want to setup a SMTP server instead of using PHP Mail, as the latter is not 100% reliable and you might experience problems with emails. Also, you'd likely want to setup a SMTP server instead of using PHP Mail, as the latter is not 100% reliable and you might experience problems with emails.
Valid values for `auth_mode` include `plain`, `login`, `cram-md5`, or `null`. ## Built-in Engines
> NOTE: `engine: mail` has been deprecated from the SwiftMail library that this plugin uses as it does not funtion at all. Please use `smtp` if at all possibe, and `sendmail` if SMTP is not an option. By default Email 4.0 supports 4 native engines:
* SMTP - Standard "Simple Mail Transport Protocol" - The default for most providers
* SMTPS - "Simple Mail Transport Protocol Secure" - Not very commonly used
* Sendmail - Uses the built-in `sendmail` binary file available on many Linux and Mac systems
* Native - Uses `sendmail_path` of `php.ini` for Mac + Linux, and `smtp` and `smtp_port` on Windows
Due to the modular nature of Symfony/Mailer, 3rd party engines are supported via Grav plugins.
## 3rd-Party Engines Plugin Support
Along with the **Email** `v4.0` release, there has also been several custom provider plugins released to provide support for `SMTP`, `API`, and sometimes even `HTTPS` support for 3rd party providers such as **Sendgrid**, **MailJet**, **MailGun**, **Amazon SES**, **Mailchimp/Mandrill**, and others! `API` or `HTTPS` will provide a faster email sending experience compared to `SMTP` which is an older protocol and requires more back-and-forth negotiation and communication compared to the single-request of `API` or `HTTPS` solutions.
Examples of the currently available plugins include:
* https://github.com/getgrav/grav-plugin-email-sendgrid - Sengrid Mailer
* https://github.com/getgrav/grav-plugin-email-amazon - Amazon SES
* https://github.com/getgrav/grav-plugin-email-mandrill - Mailchimp Mandrill Mailer
* https://github.com/getgrav/grav-plugin-email-mailersend - Mailersend Mailer
More plugins will be released soon to support `Gmail`, `Mailgun`, `Mailjet`, `OhMySMTP`, `Postmark`, and `SendInBlue`.
## SMTP Configurations for popular solutions:
### Google Email
A popular option for sending email is to simply use your Google Accounts SMTP server. To set this up you will need to do 2 things first:
As Gmail no longer supports the "allow less secure apps" option, you now need to have 2FA enabled on the account and setup an "App Password" to create a specific password rather than your general account password. Follow these instructions: [https://support.google.com/accounts/answer/185833](https://support.google.com/accounts/answer/185833)
Then configure the Email plugin:
```
mailer:
engine: smtp
smtp:
server: smtp.gmail.com
port: 587
user: 'YOUR_GOOGLE_EMAIL_ADDRESS'
password: 'YOUR_GOOGLE_PASSWORD'
```
> NOTE: Check your email sending limits: https://support.google.com/a/answer/166852?hl=en
### Mailtrap.io ### Mailtrap.io
@ -73,30 +109,7 @@ That service will intercept emails and show them on their web-based interface in
You can try and fine tune the emails there while testing. You can try and fine tune the emails there while testing.
### Google Email ### Sparkpost
A popular option for sending email is to simply use your Google Accounts SMTP server. To set this up you will need to do 2 things first:
1. Enable IMAP in your Gmail `Settings` -> `Forwarding and POP/IMAP` -> `IMAP Access`
2. Enable `Less secure apps` in your [user account settings](https://myaccount.google.com/lesssecureapps)
3. If you have 2-factor authentication, you will need to create a unique application password to use rather than your personal password
Then configure the Email plugin:
```
mailer:
engine: smtp
smtp:
server: smtp.gmail.com
port: 587
encryption: tls
user: 'YOUR_GOOGLE_EMAIL_ADDRESS'
password: 'YOUR_GOOGLE_PASSWORD'
```
> NOTE: Check your email sending limits: https://support.google.com/a/answer/166852?hl=en
#### Sparkpost
Generous email sending limits even in the free tier, and simple setup, make [Sparkpost](https://www.sparkpost.com) a great option for email sending. You just need to create an account, then setup a verified sending domain. Sparkpost does a nice job of making this process very easy and undertandable. Then just click on the SMTP Relay option to get your details for the configuration: Generous email sending limits even in the free tier, and simple setup, make [Sparkpost](https://www.sparkpost.com) a great option for email sending. You just need to create an account, then setup a verified sending domain. Sparkpost does a nice job of making this process very easy and undertandable. Then just click on the SMTP Relay option to get your details for the configuration:
@ -106,14 +119,13 @@ mailer:
smtp: smtp:
server: smtp.sparkpostmail.com server: smtp.sparkpostmail.com
port: 587 port: 587
encryption: tls
user: 'SMTP_Injection' user: 'SMTP_Injection'
password: 'SEND_EMAIL_API_KEY' password: 'SEND_EMAIL_API_KEY'
``` ```
Then try sending a test email... Then try sending a test email...
#### Sendgrid ### Sendgrid
[Sendgrid](https://sendgrid.com) offers a very easy-to-setup serivce with 100 emails/day for free. The next level allows you to send 40k/email a day for just $10/month. Configuration is pretty simple, just create an account, then click SMTP integration and click the button to create an API key. The configuration is as follows: [Sendgrid](https://sendgrid.com) offers a very easy-to-setup serivce with 100 emails/day for free. The next level allows you to send 40k/email a day for just $10/month. Configuration is pretty simple, just create an account, then click SMTP integration and click the button to create an API key. The configuration is as follows:
@ -123,12 +135,11 @@ mailer:
smtp: smtp:
server: smtp.sendgrid.net server: smtp.sendgrid.net
port: 587 port: 587
encryption: tls
user: 'apikey' user: 'apikey'
password: 'YOUR_SENDGRID_API_KEY' password: 'YOUR_SENDGRID_API_KEY'
``` ```
#### Mailgun ### Mailgun
[Mailgun is a great service](https://www.mailgun.com/) that offers 10k/emails per month for free. Setup does require SPIF domain verification so that means you need to add at least a TXT entry in your DNS. This is pretty standard for SMTP sending services and does provide verification for remote email servers and makes your email sending more reliable. The Mailgun site, walks you through this process however, and the verification process is simple and fast. [Mailgun is a great service](https://www.mailgun.com/) that offers 10k/emails per month for free. Setup does require SPIF domain verification so that means you need to add at least a TXT entry in your DNS. This is pretty standard for SMTP sending services and does provide verification for remote email servers and makes your email sending more reliable. The Mailgun site, walks you through this process however, and the verification process is simple and fast.
@ -138,14 +149,13 @@ mailer:
smtp: smtp:
server: smtp.mailgun.org server: smtp.mailgun.org
port: 587 port: 587
encryption: tls
user: 'MAILGUN_EMAIL_ADDRESS' user: 'MAILGUN_EMAIL_ADDRESS'
password: 'MAILGUN_EMAIL_PASSWORD' password: 'MAILGUN_EMAIL_PASSWORD'
``` ```
Adjust these configurations for your account. Adjust these configurations for your account.
#### MailJet ### MailJet
Mailjet is another great service that is easy to quickly setup and get started sending email. The free account gives you 200 emails/day or 600 emails/month. Just signup and setup your SPF and DKIM entries for your domain. Then click on the SMTP settings and use those to configure the email plugin: Mailjet is another great service that is easy to quickly setup and get started sending email. The free account gives you 200 emails/day or 600 emails/month. Just signup and setup your SPF and DKIM entries for your domain. Then click on the SMTP settings and use those to configure the email plugin:
@ -155,18 +165,15 @@ mailer:
smtp: smtp:
server: in-v3.mailjet.com server: in-v3.mailjet.com
port: 587 port: 587
encryption: tls
user: 'MAILJUST_USERNAME_API_KEY' user: 'MAILJUST_USERNAME_API_KEY'
password: 'MAILJUST_PASSWORD_SECRET_KEY' password: 'MAILJUST_PASSWORD_SECRET_KEY'
``` ```
It's that easy! ### ZOHO
#### ZOHO
ZOHO is a popular solution for hosted email due to it's great 'FREE' tier. It's paid options are also very reasonable and combined with the latest UI updates and outstanding security features, it's a solid email option. ZOHO is a popular solution for hosted email due to it's great 'FREE' tier. It's paid options are also very reasonable and combined with the latest UI updates and outstanding security features, it's a solid email option.
In order to get ZOHO working with Grav, you need to send email via a user account. You can either use your users' password or generate an **App Password** via your ZOHO account (clicking on your avatar once logged in), then navigating to `Two Factor Authentication -> App Passwords -> Generate`. Just enter a unique app name (i.e. `Grav Website`). In order to get ZOHO working with Grav, you need to send email via a user account. You can either use your users' password or generate an **App Password** via your ZOHO account (clicking on your avatar once logged in), then navigating to `My Account -> Security -> App Passwords -> Generate`. Just enter a unique app name (i.e. `Grav Website`).
NOTE: The SMTP host required can be found in `Settings -> Mail - > Mail Accounts -> POP/IMAP -> SMTP`. This will provide the SMTP server for this account (it may not be `imap.zoho.com` depending on what region you are in) NOTE: The SMTP host required can be found in `Settings -> Mail - > Mail Accounts -> POP/IMAP -> SMTP`. This will provide the SMTP server for this account (it may not be `imap.zoho.com` depending on what region you are in)
@ -176,12 +183,11 @@ mailer:
smtp: smtp:
server: smtp.zoho.com server: smtp.zoho.com
port: 587 port: 587
encryption: tls
user: 'ZOHO_EMAIL_ADDRESS' user: 'ZOHO_EMAIL_ADDRESS'
password: 'ZOHO_EMAIL_PASSWORD' password: 'ZOHO_EMAIL_PASSWORD'
``` ```
#### Sendmail ### Sendmail
Although not as reliable as SMTP not providing as much debug information, sendmail is a simple option as long as your hosting provider is not blocking the default SMTP port `25`: Although not as reliable as SMTP not providing as much debug information, sendmail is a simple option as long as your hosting provider is not blocking the default SMTP port `25`:
@ -194,7 +200,7 @@ mailer:
Simply adjust your binary command line to suite your environment Simply adjust your binary command line to suite your environment
### SMTP Email Services ## SMTP Email Services
Solid SMTP options that even provide a FREE tier for low email volumes include: Solid SMTP options that even provide a FREE tier for low email volumes include:
@ -206,30 +212,18 @@ Solid SMTP options that even provide a FREE tier for low email volumes include:
If you are still unsure why should be using one in the first place, check out this article: https://zapier.com/learn/email-marketing/best-transactional-email-sending-services/ If you are still unsure why should be using one in the first place, check out this article: https://zapier.com/learn/email-marketing/best-transactional-email-sending-services/
## Email Queue
For performance reasons, it's often desirable to queue emails and send them in batches, rather than forcing Grav to wait while an email is sent. This is because email servers are sometimes slow and you might not want to wait for the whole email-sending process before continuing with Grav processing.
To address this, you can enable the **Email Queue** and this will ensure all email's in Grav are actually sent to the queue, and not sent directly. In order for the emails to be actually sent, you need to flush the queue. By default this is handled by the **Grav Scheduler**, so you need to ensure you have that enabled and setup correctly or **your emails will not be sent!!!**.
You can also manually flush the queue by using the provided CLI command:
```
$ bin/plugin email flush-queue
```
## Testing with CLI Command ## Testing with CLI Command
You can test your email configuration with the following CLI Command: You can test your email configuration with the following CLI Command:
``` ```
$ bin/plugin email test-email -t steve@apple.com $ bin/plugin email test-email -t test@email.com
``` ```
You can also pass in a configuration environment: You can also pass in a configuration environment:
``` ```
$ bin/plugin email test-email -t steve@apple.com -e mysite.com $ bin/plugin email test-email -t test@email.com --env=mysite.com
``` ```
This will use the email configuration you have located in `user/mysite.com/config/plugins/email.yaml`. Read the docs to find out more about environment-based configuration: https://learn.getgrav.org/advanced/environment-config This will use the email configuration you have located in `user/mysite.com/config/plugins/email.yaml`. Read the docs to find out more about environment-based configuration: https://learn.getgrav.org/advanced/environment-config
@ -271,11 +265,8 @@ form:
email: email:
subject: "[Custom form] {{ form.value.name|e }}" subject: "[Custom form] {{ form.value.name|e }}"
body: "{% include 'forms/data.txt.twig' %}" body: "{% include 'forms/data.txt.twig' %}"
from: sender@example.com from: Custom Sender <sender@example.com>
from_name: 'Custom sender name' to: Custom Recipient <recipient@example.com>
to: recipient@example.com
to_name: 'Custom recipient name'
content_type: 'text/plain'
process_markdown: true process_markdown: true
``` ```
@ -298,14 +289,14 @@ form:
- -
subject: "[Custom Email 1] {{ form.value.name|e }}" subject: "[Custom Email 1] {{ form.value.name|e }}"
body: "{% include 'forms/data.txt.twig' %}" body: "{% include 'forms/data.txt.twig' %}"
from: {mail: "owner@mysite.com", name: "Site OWner"} from: Site Owner <owner@mysite.com>
to: {mail: "recepient_1@example.com", name: "Recepient 1"} to: Recipient 1 <recepient_1@example.com>
template: "email/base.html.twig" template: "email/base.html.twig"
- -
subject: "[Custom Email 2] {{ form.value.name|e }}" subject: "[Custom Email 2] {{ form.value.name|e }}"
body: "{% include 'forms/data.txt.twig' %}" body: "{% include 'forms/data.txt.twig' %}"
from: {mail: "owner@mysite.com", name: "Site OWner"} from: Site Owner <owner@mysite.com>
to: {mail: "recepient_2@example.com", name: "Recepient 1"} to: Recipient 2 <recepient_2@example.com>
template: "email/base.html.twig" template: "email/base.html.twig"
``` ```
@ -379,7 +370,6 @@ To have more control over your generated email, you may also use the following a
* `reply_to`: Set one or more addresses that should be used to reply to the message. * `reply_to`: Set one or more addresses that should be used to reply to the message.
* `cc` _(Carbon copy)_: Add one or more addresses to the delivery list. Many email clients will mark email in one's inbox differently depending on whether they are in the `To:` or `Cc:` list. * `cc` _(Carbon copy)_: Add one or more addresses to the delivery list. Many email clients will mark email in one's inbox differently depending on whether they are in the `To:` or `Cc:` list.
* `bcc` _(Blind carbon copy)_: Add one or more addresses to the delivery list that should (usually) not be listed in the message data, remaining invisible to other recipients. * `bcc` _(Blind carbon copy)_: Add one or more addresses to the delivery list that should (usually) not be listed in the message data, remaining invisible to other recipients.
* `charset`: Explicitly set a charset for the generated email body (only takes effect if `body` parameter is a string, defaults to `utf-8`)
### Specifying email addresses ### Specifying email addresses
@ -391,6 +381,12 @@ Email-related parameters (`from`, `to`, `reply_to`, `cc`and `bcc`) allow differe
to: mail@example.com to: mail@example.com
``` ```
#### `name-addr` RFC822 Formatted string
```
to: Joe Bloggs <maiil@example.com>
```
#### Multiple email address strings #### Multiple email address strings
``` ```
@ -400,49 +396,64 @@ to:
- mail+2@example.com - mail+2@example.com
``` ```
#### Single email address with name or in `name-addr` format:
``` ```
to: to:
mail: mail@example.com - Joe Bloggs <mail@example.com>
name: Human-readable name - Jane Doe <mail+1@example.com>
- Jasper Jesperson <mail+2@example.com>
```
#### Simple array format with names
```
to: [mail@exmaple.com, Joe Bloggs]
```
#### Formatted email address with names
```
to:
email: mail@example.com
name: Joe Bloggs
``` ```
or inline: or inline:
``` ```
to: {mail: 'mail@example.com', name: 'Human-readable name'} to: {email: 'mail@example.com', name: 'Joe Bloggs'}
``` ```
#### Multiple email addresses (with and without names) #### Multiple email addresses (with and without names)
```
to:
- [mail@example.com, Joe Bloggs]
- [mail+2@example.com, Jane Doe]
```
``` ```
to: to:
- -
mail: mail@example.com email: mail@example.com
name: Human-readable name name: Joe Bloggs
- -
mail: mail+2@example.com email: mail+2@example.com
name: Another human-readable name name: Jane Doe
-
mail+3@example.com
-
mail+4@example.com
``` ```
or inline: or inline:
``` ```
to: to:
- {mail: 'mail@example.com', name: 'Human-readable name'} - {email: 'mail@example.com', name: 'Joe Bloggs'}
- {mail: 'mail+2@example.com', name: 'Another human-readable name'} - {email: 'mail+2@example.com', name: 'Jane Doe'}
- mail+3@example.com
- mail+4@example.com
``` ```
## Multi-part MIME messages ## Multi-part MIME messages
Apart from a simple string, an email body may contain different MIME parts (e.g. HTML body with plain text fallback). You may even specify a different charset for each part (default to `utf-8`): Apart from a simple string, an email body may contain different MIME parts (e.g. HTML body with plain text fallback):
``` ```
body: body:
@ -452,7 +463,7 @@ body:
- -
content_type: 'text/plain' content_type: 'text/plain'
body: "{% include 'forms/default/data.txt.twig' %}" body: "{% include 'forms/default/data.txt.twig' %}"
charset: 'iso-8859-1'
``` ```
# Troubleshooting # Troubleshooting

View File

@ -1,7 +1,7 @@
name: Email name: Email
slug: email slug: email
type: plugin type: plugin
version: 3.2.0 version: 4.0.0
testing: false testing: false
description: Enables the emailing system for Grav description: Enables the emailing system for Grav
icon: envelope icon: envelope
@ -15,8 +15,8 @@ bugs: https://github.com/getgrav/grav-plugin-email/issues
license: MIT license: MIT
dependencies: dependencies:
- { name: grav, version: '>=1.7.32' } - { name: grav, version: '>=1.7.41' }
- { name: form, version: '>=6.0.0' } - { name: form, version: '>=7.0.0' }
form: form:
validation: loose validation: loose
@ -37,106 +37,8 @@ form:
type: select type: select
label: PLUGIN_EMAIL.MAIL_ENGINE label: PLUGIN_EMAIL.MAIL_ENGINE
size: medium size: medium
options: description: PLUGIN_EMAIL.MAIL_ENGINE_DESC
none: PLUGIN_ADMIN.DISABLED data-options@: '\Grav\Plugin\EmailPlugin::getEngines'
smtp: SMTP
sendmail: Sendmail
content_type:
type: select
label: PLUGIN_EMAIL.CONTENT_TYPE
size: medium
default: 'text/html'
options:
'text/plain': PLUGIN_EMAIL.CONTENT_TYPE_PLAIN_TEXT
'text/html': HTML
charset:
type: text
size: medium
label: PLUGIN_EMAIL.CHARSET
placeholder: PLUGIN_EMAIL.CHARSET_PLACEHOLDER
email_Defaults:
type: section
title: PLUGIN_EMAIL.EMAIL_DEFAULTS
underline: true
from:
type: email
size: medium
label: PLUGIN_EMAIL.EMAIL_FORM
placeholder: PLUGIN_EMAIL.EMAIL_FORM_PLACEHOLDER
validate:
required: true
type: email
from_name:
type: text
size: medium
label: PLUGIN_EMAIL.EMAIL_FROM_NAME
placeholder: PLUGIN_EMAIL.EMAIL_FROM_NAME_PLACEHOLDER
to:
type: email
size: medium
label: PLUGIN_EMAIL.EMAIL_TO
placeholder: PLUGIN_EMAIL.EMAIL_TO_PLACEHOLDER
multiple: true
validate:
required: true
type: email
to_name:
type: text
size: medium
label: PLUGIN_EMAIL.EMAIL_TO_NAME
placeholder: PLUGIN_EMAIL.EMAIL_TO_NAME_PLACEHOLDER
cc:
type: email
size: medium
label: PLUGIN_EMAIL.EMAIL_CC
placeholder: PLUGIN_EMAIL.EMAIL_CC_PLACEHOLDER
multiple: true
validate:
type: email
cc_name:
type: text
size: medium
label: PLUGIN_EMAIL.EMAIL_CC_NAME
placeholder: PLUGIN_EMAIL.EMAIL_CC_NAME_PLACEHOLDER
bcc:
type: email
size: medium
label: PLUGIN_EMAIL.EMAIL_BCC
placeholder: PLUGIN_EMAIL.EMAIL_BCC_PLACEHOLDER
multiple: true
validate:
type: email
reply_to:
type: email
size: medium
label: PLUGIN_EMAIL.EMAIL_REPLY_TO
placeholder: PLUGIN_EMAIL.EMAIL_REPLY_TO_PLACEHOLDER
multiple: true
validate:
type: email
reply_to_name:
type: text
size: medium
label: PLUGIN_EMAIL.EMAIL_REPLY_TO_NAME
placeholder: PLUGIN_EMAIL.EMAIL_REPLY_TO_NAME_PLACEHOLDER
body:
type: textarea
size: medium
label: PLUGIN_EMAIL.EMAIL_BODY
placeholder: PLUGIN_EMAIL.EMAIL_BODY_PLACEHOLDER
smtp_config: smtp_config:
type: section type: section
@ -180,11 +82,6 @@ form:
autocomplete: 'new-password' autocomplete: 'new-password'
label: PLUGIN_EMAIL.SMTP_PASSWORD label: PLUGIN_EMAIL.SMTP_PASSWORD
mailer.smtp.auth_mode:
type: text
size: medium
label: PLUGIN_EMAIL.SMTP_AUTH_MODE
sendmail_config: sendmail_config:
type: section type: section
title: PLUGIN_EMAIL.SENDMAIL_CONFIGURATION title: PLUGIN_EMAIL.SENDMAIL_CONFIGURATION
@ -196,43 +93,67 @@ form:
label: PLUGIN_EMAIL.PATH_TO_SENDMAIL label: PLUGIN_EMAIL.PATH_TO_SENDMAIL
placeholder: "/usr/sbin/sendmail" placeholder: "/usr/sbin/sendmail"
queue_section:
email_Defaults:
type: section type: section
title: PLUGIN_EMAIL.QUEUE_TITLE title: PLUGIN_EMAIL.EMAIL_DEFAULTS
text: PLUGIN_EMAIL.QUEUE_DESC
markdown: true
underline: true underline: true
queue.enabled: content_type:
type: toggle type: select
label: PLUGIN_EMAIL.QUEUE_ENABLED label: PLUGIN_EMAIL.CONTENT_TYPE
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
queue.flush_frequency:
type: cron
label: PLUGIN_EMAIL.QUEUE_FLUSH_FREQUENCY
size: medium size: medium
help: PLUGIN_EMAIL.QUEUE_FLUSH_FREQUENCY_HELP default: 'text/html'
default: '* * * * *' options:
placeholder: '* * * * *' 'text/plain': PLUGIN_EMAIL.CONTENT_TYPE_PLAIN_TEXT
'text/html': HTML
queue.flush_msg_limit: from:
type: number type: text
label: PLUGIN_EMAIL.QUEUE_FLUSH_MSG_LIMIT size: large
size: x-small label: PLUGIN_EMAIL.EMAIL_FORM
append: PLUGIN_EMAIL.QUEUE_FLUSH_MSG_LIMIT_APPEND placeholder: PLUGIN_EMAIL.EMAIL_FORM_PLACEHOLDER
help: PLUGIN_EMAIL.EMAIL_FORMAT
validate:
required: true
queue.flush_time_limit: to:
type: number type: text
label: PLUGIN_EMAIL.QUEUE_FLUSH_TIME_LIMIT size: large
size: x-small label: PLUGIN_EMAIL.EMAIL_TO
append: PLUGIN_EMAIL.QUEUE_FLUSH_TIME_LIMIT_APPEND placeholder: PLUGIN_EMAIL.EMAIL_TO_PLACEHOLDER
help: PLUGIN_EMAIL.EMAIL_FORMAT
validate:
required: true
cc:
type: text
size: large
label: PLUGIN_EMAIL.EMAIL_CC
placeholder: PLUGIN_EMAIL.EMAIL_CC_PLACEHOLDER
help: PLUGIN_EMAIL.EMAIL_FORMAT
bcc:
type: text
size: large
label: PLUGIN_EMAIL.EMAIL_BCC
placeholder: PLUGIN_EMAIL.EMAIL_BCC_PLACEHOLDER
help: PLUGIN_EMAIL.EMAIL_FORMAT
reply_to:
type: text
size: large
label: PLUGIN_EMAIL.EMAIL_REPLY_TO
placeholder: PLUGIN_EMAIL.EMAIL_REPLY_TO_PLACEHOLDER
help: PLUGIN_EMAIL.EMAIL_FORMAT
body:
type: textarea
size: large
rows: 10
label: PLUGIN_EMAIL.EMAIL_BODY
placeholder: PLUGIN_EMAIL.EMAIL_BODY_PLACEHOLDER
advanced_section: advanced_section:
type: section type: section

View File

@ -3,33 +3,46 @@ namespace Grav\Plugin\Email;
use Grav\Common\Config\Config; use Grav\Common\Config\Config;
use Grav\Common\Grav; use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Common\Language\Language; use Grav\Common\Language\Language;
use Grav\Common\Markdown\Parsedown; use Grav\Common\Markdown\Parsedown;
use Grav\Common\Twig\Twig; use Grav\Common\Twig\Twig;
use Grav\Framework\Form\Interfaces\FormInterface; use Grav\Framework\Form\Interfaces\FormInterface;
use \Monolog\Logger; use \Monolog\Logger;
use \Monolog\Handler\StreamHandler; use \Monolog\Handler\StreamHandler;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\Header\MetadataHeader;
use Symfony\Component\Mailer\Header\TagHeader;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mime\Address;
class Email class Email
{ {
/** /** @var Mailer */
* @var \Swift_Transport
*/
protected $mailer; protected $mailer;
/** /** @var TransportInterface */
* @var \Swift_Plugins_LoggerPlugin protected $transport;
*/
protected $logger;
protected $queue_path; protected $log;
public function __construct()
{
$this->initMailer();
$this->initLog();
}
/** /**
* Returns true if emails have been enabled in the system. * Returns true if emails have been enabled in the system.
* *
* @return bool * @return bool
*/ */
public static function enabled() public static function enabled(): bool
{ {
return Grav::instance()['config']->get('plugins.email.mailer.engine') !== 'none'; return Grav::instance()['config']->get('plugins.email.mailer.engine') !== 'none';
} }
@ -39,7 +52,7 @@ class Email
* *
* @return bool * @return bool
*/ */
public static function debug() public static function debug(): bool
{ {
return Grav::instance()['config']->get('plugins.email.debug') == 'true'; return Grav::instance()['config']->get('plugins.email.debug') == 'true';
} }
@ -47,82 +60,57 @@ class Email
/** /**
* Creates an email message. * Creates an email message.
* *
* @param string $subject * @param string|null $subject
* @param string $body * @param string|null $body
* @param string $contentType * @param string|null $contentType
* @param string $charset * @param string|null $charset @deprecated
* @return \Swift_Message * @return Message
*/ */
public function message($subject = null, $body = null, $contentType = null, $charset = null) public function message(string $subject = null, string $body = null, string $contentType = null, string $charset = null): Message
{ {
return new \Swift_Message($subject, $body, $contentType, $charset); $message = new Message();
} $message->subject($subject);
if ($contentType === 'text/html') {
$message->html($body);
} else {
$message->text($body);
}
/** return $message;
* Creates an attachment.
*
* @param string $data
* @param string $filename
* @param string $contentType
* @return \Swift_Attachment
*/
public function attachment($data = null, $filename = null, $contentType = null)
{
return new \Swift_Attachment($data, $filename, $contentType);
}
/**
* Creates an embedded attachment.
*
* @param string $data
* @param string $filename
* @param string $contentType
* @return \Swift_EmbeddedFile
*/
public function embedded($data = null, $filename = null, $contentType = null)
{
return new \Swift_EmbeddedFile($data, $filename, $contentType);
}
/**
* Creates an image attachment.
*
* @param string $data
* @param string $filename
* @param string $contentType
* @return \Swift_Image
*/
public function image($data = null, $filename = null, $contentType = null)
{
return new \Swift_Image($data, $filename, $contentType);
} }
/** /**
* Send email. * Send email.
* *
* @param \Swift_Message $message * @param Message $message
* @param array|null $failedRecipients * @param Envelope|null $envelope
* @return int * @return int
*/ */
public function send($message, &$failedRecipients = null) public function send(Message $message, Envelope $envelope = null): int
{ {
$mailer = $this->getMailer(); $status = '🛑 ';
$sent_msg = null;
$debug = null;
$result = $mailer ? $mailer->send($message, $failedRecipients) : 0; try {
$sent_msg = $this->transport->send($message->getEmail(), $envelope);
// Check if emails and debugging are both enabled. $return = 1;
if ($mailer && $this->debug()) { $status = '✅';
$debug = $sent_msg->getDebug();
$log = new Logger('email'); } catch (TransportExceptionInterface $e) {
$locator = Grav::instance()['locator']; $return = 0;
$log_file = $locator->findResource('log://email.log', true, true); $status .= $e->getMessage();
$log->pushHandler(new StreamHandler($log_file, Logger::DEBUG)); $debug = $e->getDebug();
// Append the SwiftMailer log to the log.
$log->addDebug($this->getLogs());
} }
return $result; if ($this->debug()) {
$log_msg = "Email sent to %s at %s -> %s\n%s";
$to = $this->jsonifyRecipients($message->getEmail()->getTo());
$msg = sprintf($log_msg, $to, date('Y-m-d H:i:s'), $status, $debug);
$this->log->addInfo($msg);
}
return $return;
} }
/** /**
@ -130,9 +118,9 @@ class Email
* *
* @param array $params * @param array $params
* @param array $vars * @param array $vars
* @return \Swift_Message * @return Message
*/ */
public function buildMessage(array $params, array $vars = []) public function buildMessage(array $params, array $vars = []): Message
{ {
/** @var Twig $twig */ /** @var Twig $twig */
$twig = Grav::instance()['twig']; $twig = Grav::instance()['twig'];
@ -145,7 +133,9 @@ class Email
$language = Grav::instance()['language']; $language = Grav::instance()['language'];
// Create message object. // Create message object.
$message = $this->message(); $message = new Message();
$headers = $message->getEmail()->getHeaders();
$email = $message->getEmail();
// Extend parameters with defaults. // Extend parameters with defaults.
$params += [ $params += [
@ -174,61 +164,28 @@ class Email
throw new \RuntimeException($language->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_FROM_ADDRESS')); throw new \RuntimeException($language->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_FROM_ADDRESS'));
} }
// make email configuration available to templates // make email configuration available to templates
$vars += [ $vars += [
'email' => $params, 'email' => $params,
]; ];
$params = $this->processParams($params, $vars);
// Process parameters. // Process parameters.
foreach ($params as $key => $value) { foreach ($params as $key => $value) {
switch ($key) { switch ($key) {
case 'body': case 'body':
if (is_string($value)) { if (is_string($value)) {
if (strpos($value, '{{') !== false || strpos($value, '{%') !== false) { $this->processBody($message, $params, $vars, $twig, $value);
$body = $twig->processString($value, $vars);
} else {
$body = $value;
}
if ($params['process_markdown'] && $params['content_type'] === 'text/html') {
$parsedown = new Parsedown();
$body = $parsedown->text($body);
}
if ($params['template']) {
$body = $twig->processTemplate($params['template'], ['content' => $body] + $vars);
}
$content_type = !empty($params['content_type']) ? $twig->processString($params['content_type'], $vars) : null;
$charset = !empty($params['charset']) ? $twig->processString($params['charset'], $vars) : null;
$message->setBody($body, $content_type, $charset);
} elseif (is_array($value)) { } elseif (is_array($value)) {
foreach ($value as $body_part) { foreach ($value as $body_part) {
$body_part += [ $params_part = $params;
'charset' => $params['charset'], if (isset($body_part['content_type'])) {
'content_type' => $params['content_type'], $params_part['content_type'] = $body_part['content_type'];
];
$body = !empty($body_part['body']) ? $twig->processString($body_part['body'], $vars) : null;
if ($params['process_markdown'] && $body_part['content_type'] === 'text/html') {
$parsedown = new Parsedown();
$body = $parsedown->text($body);
} }
if (isset($body_part['body'])) {
if (isset($body_part['template'])) { $this->processBody($message, $params_part, $vars, $twig, $body_part['body']);
$body = $twig->processTemplate($body_part['template'], ['content' => $body] + $vars);
}
$content_type = !empty($body_part['content_type']) ? $twig->processString($body_part['content_type'], $vars) : null;
$charset = !empty($body_part['charset']) ? $twig->processString($body_part['charset'], $vars) : null;
if (!$message->getBody()) {
$message->setBody($body, $content_type, $charset);
}
else {
$message->addPart($body, $content_type, $charset);
} }
} }
} }
@ -236,68 +193,34 @@ class Email
case 'subject': case 'subject':
if ($value) { if ($value) {
$message->setSubject($twig->processString($language->translate($value), $vars)); $message->subject($language->translate($value));
} }
break; break;
case 'to': case 'to':
if (is_string($value) && !empty($params['to_name'])) {
$value = [
'mail' => $twig->processString($value, $vars),
'name' => $twig->processString($params['to_name'], $vars),
];
}
foreach ($this->parseAddressValue($value, $vars) as $address) {
$message->addTo($address->mail, $address->name);
}
break;
case 'cc':
if (is_string($value) && !empty($params['cc_name'])) {
$value = [
'mail' => $twig->processString($value, $vars),
'name' => $twig->processString($params['cc_name'], $vars),
];
}
foreach ($this->parseAddressValue($value, $vars) as $address) {
$message->addCc($address->mail, $address->name);
}
break;
case 'bcc':
foreach ($this->parseAddressValue($value, $vars) as $address) {
$message->addBcc($address->mail, $address->name);
}
break;
case 'from': case 'from':
if (is_string($value) && !empty($params['from_name'])) { case 'cc':
$value = [ case 'bcc':
'mail' => $twig->processString($value, $vars),
'name' => $twig->processString($params['from_name'], $vars),
];
}
foreach ($this->parseAddressValue($value, $vars) as $address) {
$message->addFrom($address->mail, $address->name);
}
break;
case 'reply_to': case 'reply_to':
if (is_string($value) && !empty($params['reply_to_name'])) { if ($recipients = $this->processRecipients($key, $params)) {
$value = [ $key = $key === 'reply_to' ? 'replyTo' : $key;
'mail' => $twig->processString($value, $vars), $email->$key(...$recipients);
'name' => $twig->processString($params['reply_to_name'], $vars), }
]; break;
} case 'tags':
foreach ((array) $value as $tag) {
foreach ($this->parseAddressValue($value, $vars) as $address) { if (is_string($tag)) {
$message->addReplyTo($address->mail, $address->name); $headers->add(new TagHeader($tag));
}
}
break;
case 'metadata':
foreach ((array) $value as $k => $v) {
if (is_string($k) && is_string($v)) {
$headers->add(new MetadataHeader($k, $v));
}
} }
break; break;
} }
} }
@ -305,260 +228,282 @@ class Email
} }
/** /**
* Return parsed e-mail address value. * @param string $type
* * @param array $params
* @param string|string[] $value
* @param array $vars
* @return array * @return array
*/ */
public function parseAddressValue($value, array $vars = []) protected function processRecipients(string $type, array $params): array
{ {
$parsed = []; $recipients = $params[$type] ?? Grav::instance()['config']->get('plugins.email.'.$type) ?? [];
/** @var Twig $twig */ $list = [];
$twig = Grav::instance()['twig'];
// Single e-mail address string if (!empty($recipients)) {
if (is_string($value)) { if (is_array($recipients) && Utils::isAssoc($recipients)) {
$parsed[] = (object) [ $list[] = $this->createAddress($recipients);
'mail' => $twig->processString($value, $vars), } else {
'name' => null, if (is_array($recipients)) {
]; if (count($recipients) ===2 && $this->isValidEmail($recipients[0]) && is_string($recipients[1])) {
} $list[] = $this->createAddress($recipients);
} else {
else { foreach ($recipients as $recipient) {
// Cast value as array $list[] = $this->createAddress($recipient);
$value = (array) $value; }
}
// Single e-mail address array } else {
if (!empty($value['mail'])) { if (is_string($recipients) && Utils::contains($recipients, ',')) {
$parsed[] = (object) [ $recipients = array_map('trim', explode(',', $recipients));
'mail' => $twig->processString($value['mail'], $vars), foreach ($recipients as $recipient) {
'name' => !empty($value['name']) ? $twig->processString($value['name'], $vars) : NULL, $list[] = $this->createAddress($recipient);
]; }
} } else {
if (!Utils::contains($recipients, ['<','>']) && ($params[$type."_name"])) {
// Multiple addresses (either as strings or arrays) $recipients = [$recipients, $params[$type."_name"]];
elseif (!(empty($value['mail']) && !empty($value['name']))) { }
foreach ($value as $y => $itemx) { $list[] = $this->createAddress($recipients);
$addresses = $this->parseAddressValue($itemx, $vars);
if (($address = reset($addresses))) {
$parsed[] = $address;
} }
} }
} }
} }
return $parsed; return $list;
} }
/** /**
* Return debugging logs if enabled * @param $data
* * @return Address
* @return string
*/ */
public function getLogs() protected function createAddress($data): Address
{ {
if ($this->debug()) { if (is_string($data)) {
return $this->logger->dump(); preg_match('/^(.*)\<(.*)\>$/', $data, $matches);
if (isset($matches[2])) {
$email = trim($matches[2]);
$name = trim($matches[1]);
} else {
$email = $data;
$name = '';
}
} elseif (Utils::isAssoc($data)) {
$first_key = array_key_first($data);
if (filter_var($first_key, FILTER_VALIDATE_EMAIL)) {
$email = $first_key;
$name = $data[$first_key];
} else {
$email = $data['email'] ?? $data['mail'] ?? $data['address'] ?? '';
$name = $data['name'] ?? $data['fullname'] ?? '';
}
} else {
$email = $data[0] ?? '';
$name = $data[1] ?? '';
} }
return ''; return new Address($email, $name);
} }
/** /**
* @return null|Mailer
* @internal * @internal
* @return null|\Swift_Mailer
*/ */
protected function getMailer() protected function initMailer(): ?Mailer
{ {
if (!$this->enabled()) { if (!$this->enabled()) {
return null; return null;
} }
if (!$this->mailer) { if (!$this->mailer) {
/** @var Config $config */ $this->transport = $this->getTransport();
$config = Grav::instance()['config'];
$queue_enabled = $config->get('plugins.email.queue.enabled');
$transport = $queue_enabled === true ? $this->getQueue() : $this->getTransport();
// Create the Mailer using your created Transport // Create the Mailer using your created Transport
$this->mailer = new \Swift_Mailer($transport); $this->mailer = new Mailer($this->transport);
// Register the logger if we're debugging.
if ($this->debug()) {
$this->logger = new \Swift_Plugins_Loggers_ArrayLogger();
$this->mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($this->logger));
}
} }
return $this->mailer; return $this->mailer;
} }
protected static function getQueuePath() /**
* @return void
* @throws \Exception
*/
protected function initLog()
{ {
$queue_path = Grav::instance()['locator']->findResource('user://data', true) . '/email-queue'; $log_file = Grav::instance()['locator']->findResource('log://email.log', true, true);
$this->log = new Logger('email');
if (!file_exists($queue_path)) { /** @var UniformResourceLocator $locator */
mkdir($queue_path); $this->log->pushHandler(new StreamHandler($log_file, Logger::DEBUG));
}
return $queue_path;
} }
protected static function getQueue() /**
* @param array $params
* @param array $vars
* @return array
*/
protected function processParams(array $params, array $vars = []): array
{ {
$queue_path = static::getQueuePath(); $twig = Grav::instance()['twig'];
array_walk_recursive($params, function(&$value) use ($twig, $vars) {
$spool = new \Swift_FileSpool($queue_path); if (is_string($value)) {
$transport = new \Swift_SpoolTransport($spool); $value = $twig->processString($value, $vars);
return $transport;
}
public static function flushQueue()
{
$grav = Grav::instance();
$grav['debugger']->enabled(false);
$config = $grav['config']->get('plugins.email.queue');
try {
$queue = static::getQueue();
$spool = $queue->getSpool();
$spool->setMessageLimit($config['flush_msg_limit']);
$spool->setTimeLimit($config['flush_time_limit']);
$failures = [];
$result = $spool->flushQueue(static::getTransport(), $failures);
return $result . ' messages flushed from queue...';
} catch (\Exception $e) {
$grav['log']->error($e->getMessage());
return $e->getMessage();
}
}
public static function clearQueueFailures()
{
$grav = Grav::instance();
$grav['debugger']->enabled(false);
$preferences = \Swift_Preferences::getInstance();
$preferences->setTempDir(sys_get_temp_dir());
/** @var \Swift_Transport $transport */
$transport = static::getTransport();
if (!$transport->isStarted()) {
$transport->start();
}
$queue_path = static::getQueuePath();
foreach (new \GlobIterator($queue_path . '/*.sending') as $file) {
$final_message = $file->getPathname();
/** @var \Swift_Message $message */
$message = unserialize(file_get_contents($final_message));
echo(sprintf(
'Retrying "%s" to "%s"',
$message->getSubject(),
implode(', ', array_keys($message->getTo()))
) . "\n");
try {
$clean = static::cloneMessage($message);
$transport->send($clean);
echo("sent!\n");
// DOn't want to trip up any errors from sending too fast
sleep(1);
} catch (\Swift_TransportException $e) {
echo("ERROR: Send failed - deleting spooled message\n");
} }
});
return $params;
}
// Remove the file /**
unlink($final_message); * @param $message
* @param $params
* @param $vars
* @param $twig
* @param $body
* @return void
*/
protected function processBody($message, $params, $vars, $twig, $body)
{
if ($params['process_markdown'] && $params['content_type'] === 'text/html') {
$body = (new Parsedown())->text($body);
}
if ($params['template']) {
$body = $twig->processTemplate($params['template'], ['content' => $body] + $vars);
}
$content_type = !empty($params['content_type']) ? $twig->processString($params['content_type'], $vars) : null;
if ($content_type === 'text/html') {
$message->html($body);
} else {
$message->text($body);
} }
} }
/** /**
* Clean copy a message * @return TransportInterface
*
* @param \Swift_Message $message
*/ */
public static function cloneMessage($message) protected static function getTransport(): Transport\TransportInterface
{
$clean = new \Swift_Message();
$clean->setBoundary($message->getBoundary());
$clean->setBcc($message->getBcc());
$clean->setBody($message->getBody());
$clean->setCharset($message->getCharset());
$clean->setChildren($message->getChildren());
$clean->setContentType($message->getContentType());
$clean->setCc($message->getCc());
$clean->setDate($message->getDate());
$clean->setDescription($message->getDescription());
$clean->setEncoder($message->getEncoder());
$clean->setFormat($message->getFormat());
$clean->setFrom($message->getFrom());
$clean->setId($message->getId());
$clean->setMaxLineLength($message->getMaxLineLength());
$clean->setPriority($message->getPriority());
$clean->setReplyTo($message->getReplyTo());
$clean->setReturnPath($message->getReturnPath());
$clean->setSender($message->getSender());
$clean->setSubject($message->getSubject());
$clean->setTo($message->getTo());
$clean->setAuthMode($message->getAuthMode());
return $clean;
}
protected static function getTransport()
{ {
/** @var Config $config */ /** @var Config $config */
$config = Grav::instance()['config']; $config = Grav::instance()['config'];
$engine = $config->get('plugins.email.mailer.engine'); $engine = $config->get('plugins.email.mailer.engine');
$dsn = 'null://default';
// Create the Transport and initialize it. // Create the Transport and initialize it.
switch ($engine) { switch ($engine) {
case 'smtps':
case 'smtp': case 'smtp':
$transport = new \Swift_SmtpTransport();
$options = $config->get('plugins.email.mailer.smtp'); $options = $config->get('plugins.email.mailer.smtp');
if (!empty($options['server'])) { $dsn = $engine . '://';
$transport->setHost($options['server']); $auth = '';
if (isset($options['encryption']) && $options['encryption'] === 'none') {
$options['options']['verify_peer'] = 0;
} }
if (!empty($options['port'])) { if (isset($options['user'])) {
$transport->setPort($options['port']); $auth .= urlencode($options['user']);
} }
if (!empty($options['encryption']) && $options['encryption'] !== 'none') { if (isset($options['password'])) {
$transport->setEncryption($options['encryption']); $auth .= ':'. urlencode($options['password']);
} }
if (!empty($options['user'])) { if (!empty($auth)) {
$transport->setUsername($options['user']); $dsn .= "$auth@";
} }
if (!empty($options['password'])) { if (isset($options['server'])) {
$transport->setPassword($options['password']); $dsn .= urlencode($options['server']);
} }
if (!empty($options['auth_mode'])) { if (isset($options['port'])) {
$transport->setAuthMode($options['auth_mode']); $dsn .= ":{$options['port']}";
} }
if (isset($options['options'])) {
$dsn .= '?' . http_build_query($options['options']);
}
break;
case 'mail':
case 'native':
$dsn = 'native://default';
break; break;
case 'sendmail': case 'sendmail':
default: $dsn = 'sendmail://default';
$options = $config->get('plugins.email.mailer.sendmail'); $bin = $config->get('plugins.email.mailer.sendmail.bin');
$bin = !empty($options['bin']) ? $options['bin'] : '/usr/sbin/sendmail'; if (isset($bin)) {
$transport = new \Swift_SendmailTransport($bin); $dsn .= '?command=' . urlencode($bin);
}
break; break;
default:
$e = new Event(['engine' => $engine, ]);
Grav::instance()->fireEvent('onEmailTransportDsn', $e);
if (isset($e['dsn'])) {
$dsn = $e['dsn'];
}
break;
}
if ($dsn instanceof TransportInterface) {
$transport = $dsn;
} else {
$transport = Transport::fromDsn($dsn) ;
} }
return $transport; return $transport;
} }
/**
* @param array $recipients
* @return string
*/
protected function jsonifyRecipients(array $recipients): string
{
$json = [];
foreach ($recipients as $recipient) {
$json[] = str_replace('"', "", $recipient->toString());
}
return json_encode($json);
}
protected function isValidEmail($email): bool
{
return is_string($email) && filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* @return void
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
*/
public static function flushQueue() {}
/**
* @return void
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
*/
public static function clearQueueFailures() {}
/**
* Creates an attachment.
*
* @param string $data
* @param string $filename
* @param string $contentType
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
* @return void
*/
public function attachment($data = null, $filename = null, $contentType = null) {}
/**
* Creates an embedded attachment.
*
* @param string $data
* @param string $filename
* @param string $contentType
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
* @return void
*/
public function embedded($data = null, $filename = null, $contentType = null) {}
/**
* Creates an image attachment.
*
* @param string $data
* @param string $filename
* @param string $contentType
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
* @return void
*/
public function image($data = null, $filename = null, $contentType = null) {}
} }

View File

@ -0,0 +1,97 @@
<?php
namespace Grav\Plugin\Email;
use Symfony\Component\Mime\Email as SymfonyEmail;
class Message
{
/** @var SymfonyEmail */
protected $email;
public function __construct() {
$this->email = new SymfonyEmail();
}
public function subject($subject): self
{
$this->email->subject($subject);
return $this;
}
public function to($to): self
{
$this->email->to($to);
return $this;
}
public function from($from): self
{
$this->email->from($from);
return $this;
}
public function cc($cc): self
{
$this->email->cc($cc);
return $this;
}
public function bcc($bcc): self
{
$this->email->bcc($bcc);
return $this;
}
public function replyTo($reply_to): self
{
$this->email->replyTo($reply_to);
return $this;
}
public function text($text): self
{
$this->email->text($text);
return $this;
}
public function html($html): self
{
$this->email->html($html);
return $this;
}
public function attachFromPath($path): self
{
$this->email->attachFromPath($path);
return $this;
}
public function embedFromPath($path): self
{
$this->email->embedFromPath($path);
return $this;
}
public function reply_to($reply_to): self
{
$this->replyTo($reply_to);
return $this;
}
public function setFrom($from): self
{
$this->from($from);
return $this;
}
public function setTo($to): self
{
$this->to($to);
return $this;
}
public function getEmail(): SymfonyEmail
{
return $this->email;
}
}

View File

@ -9,6 +9,7 @@ use Symfony\Component\Console\Input\InputOption;
/** /**
* Class FlushQueueCommand * Class FlushQueueCommand
* @package Grav\Console\Cli\ * @package Grav\Console\Cli\
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
*/ */
class ClearQueueFailuresCommand extends ConsoleCommand class ClearQueueFailuresCommand extends ConsoleCommand
{ {
@ -23,40 +24,16 @@ class ClearQueueFailuresCommand extends ConsoleCommand
$this $this
->setName('clear-queue-failures') ->setName('clear-queue-failures')
->setAliases(['clearqueue']) ->setAliases(['clearqueue'])
->addOption( ->setDescription('DEPRECATED: Clears any queue failures that have accumulated')
'env',
'e',
InputOption::VALUE_OPTIONAL,
'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com'
)
->setDescription('Clears any queue failures that have accumulated')
->setHelp('The <info>clear-queue-failures</info> command clears any queue failures that have accumulated'); ->setHelp('The <info>clear-queue-failures</info> command clears any queue failures that have accumulated');
} }
/** /**
* @return int * @return int
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
*/ */
protected function serve() protected function serve()
{ {
// TODO: remove when requiring Grav 1.7+
if (method_exists($this, 'initializeGrav')) {
$this->initializeGrav();
}
$this->output->writeln('');
$this->output->writeln('<yellow>Current Configuration:</yellow>');
$this->output->writeln('');
$grav = Grav::instance();
dump($grav['config']->get('plugins.email'));
$this->output->writeln('');
require_once __DIR__ . '/../vendor/autoload.php';
Email::clearQueueFailures();
return 0; return 0;
} }
} }

View File

@ -9,6 +9,7 @@ use Symfony\Component\Console\Input\InputOption;
/** /**
* Class FlushQueueCommand * Class FlushQueueCommand
* @package Grav\Console\Cli\ * @package Grav\Console\Cli\
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
*/ */
class FlushQueueCommand extends ConsoleCommand class FlushQueueCommand extends ConsoleCommand
{ {
@ -23,41 +24,16 @@ class FlushQueueCommand extends ConsoleCommand
$this $this
->setName('flush-queue') ->setName('flush-queue')
->setAliases(['flushqueue']) ->setAliases(['flushqueue'])
->addOption( ->setDescription('DEPRECATED: Flushes the email queue of any pending emails')
'env',
'e',
InputOption::VALUE_OPTIONAL,
'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com'
)
->setDescription('Flushes the email queue of any pending emails')
->setHelp('The <info>flush-queue</info> command flushes the email queue of any pending emails'); ->setHelp('The <info>flush-queue</info> command flushes the email queue of any pending emails');
} }
/** /**
* @return int * @return int
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
*/ */
protected function serve() protected function serve()
{ {
// TODO: remove when requiring Grav 1.7+
if (method_exists($this, 'initializeGrav')) {
$this->initializeGrav();
}
$this->output->writeln('');
$this->output->writeln('<yellow>Current Configuration:</yellow>');
$this->output->writeln('');
$grav = Grav::instance();
dump($grav['config']->get('plugins.email'));
$this->output->writeln('');
require_once __DIR__ . '/../vendor/autoload.php';
$output = Email::flushQueue();
$this->output->writeln('<green>' . $output . '</green>');
return 0; return 0;
} }
} }

View File

@ -79,8 +79,6 @@ class TestEmailCommand extends ConsoleCommand
$this->output->writeln(''); $this->output->writeln('');
$grav['Email'] = new Email();
$to = $this->input->getOption('to') ?: $grav['config']->get('plugins.email.to'); $to = $this->input->getOption('to') ?: $grav['config']->get('plugins.email.to');
$subject = $this->input->getOption('subject'); $subject = $this->input->getOption('subject');
$body = $this->input->getOption('body'); $body = $this->input->getOption('body');

View File

@ -21,7 +21,8 @@
}, },
"require": { "require": {
"php": ">=7.3.6", "php": ">=7.3.6",
"swiftmailer/swiftmailer": "~6.0" "symfony/mailer": "^5.4",
"symfony/messenger": "^5.4"
}, },
"replace": { "replace": {
"symfony/polyfill-iconv": "*", "symfony/polyfill-iconv": "*",

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,9 @@ namespace Grav\Plugin;
use Composer\Autoload\ClassLoader; use Composer\Autoload\ClassLoader;
use Grav\Common\Data\Data; use Grav\Common\Data\Data;
use Grav\Common\Grav;
use Grav\Common\Plugin; use Grav\Common\Plugin;
use Grav\Common\Utils;
use Grav\Plugin\Email\Email; use Grav\Plugin\Email\Email;
use RocketTheme\Toolbox\Event\Event; use RocketTheme\Toolbox\Event\Event;
@ -67,8 +69,6 @@ class EmailPlugin extends Plugin
/** @var Data $obj */ /** @var Data $obj */
$obj = $event['object']; $obj = $event['object'];
if ($obj instanceof Data && $obj->blueprints()->getFilename() === 'email/blueprints') { if ($obj instanceof Data && $obj->blueprints()->getFilename() === 'email/blueprints') {
$current_pw = $this->grav['config']->get('plugins.email.mailer.smtp.password'); $current_pw = $this->grav['config']->get('plugins.email.mailer.smtp.password');
$new_pw = $obj->get('mailer.smtp.password'); $new_pw = $obj->get('mailer.smtp.password');
@ -109,7 +109,7 @@ class EmailPlugin extends Plugin
$this->grav->fireEvent('onEmailSend', new Event(['params' => &$params, 'vars' => &$vars])); $this->grav->fireEvent('onEmailSend', new Event(['params' => &$params, 'vars' => &$vars]));
if ($this->isAssocArray($params)) { if (Utils::isAssoc($params)) {
$this->sendFormEmail($form, $params, $vars); $this->sendFormEmail($form, $params, $vars);
} else { } else {
foreach ($params as $email) { foreach ($params as $email) {
@ -121,25 +121,6 @@ class EmailPlugin extends Plugin
} }
} }
/**
* Add index job to Grav Scheduler
* Requires Grav 1.6.0 - Scheduler
*/
public function onSchedulerInitialized(Event $e)
{
if ($this->config->get('plugins.email.queue.enabled')) {
/** @var Scheduler $scheduler */
$scheduler = $e['scheduler'];
$at = $this->config->get('plugins.email.queue.flush_frequency');
$logs = 'logs/email-queue.out';
$job = $scheduler->addFunction('Grav\Plugin\Email\Email::flushQueue', [], 'email-flushqueue');
$job->at($at);
$job->output($logs);
$job->backlink('/plugins/email');
}
}
protected function sendFormEmail($form, $params, $vars) protected function sendFormEmail($form, $params, $vars)
{ {
// Build message // Build message
@ -158,7 +139,7 @@ class EmailPlugin extends Plugin
} }
try { try {
$message->attach(\Swift_Attachment::fromPath($filename)); $message->attachFromPath($filename);
} catch (\Exception $e) { } catch (\Exception $e) {
// Log any issues // Log any issues
$this->grav['log']->error($e->getMessage()); $this->grav['log']->error($e->getMessage());
@ -177,12 +158,30 @@ class EmailPlugin extends Plugin
$this->grav->fireEvent('onEmailSent', new Event(['message' => $message, 'params' => $params, 'form' => $form])); $this->grav->fireEvent('onEmailSent', new Event(['message' => $message, 'params' => $params, 'form' => $form]));
} }
protected function isAssocArray(array $arr) /**
* Used for dynamic blueprint field
*
* @return array
*/
public static function getEngines(): array
{ {
if (array() === $arr) return false; $engines = (object) [
$keys = array_keys($arr); 'sendmail' => 'Sendmail',
$index_keys = range(0, count($arr) - 1); 'smtp' => 'SMTP',
return $keys !== $index_keys; 'smtps' => 'SMTPS',
'native' => 'Native',
'none' => 'PLUGIN_ADMIN.DISABLED',
];
Grav::instance()->fireEvent('onEmailEngines', new Event(['engines' => $engines]));
return (array) $engines;
}
/**
* @deprecated 4.0 Switched from Swiftmailer to Symfony/Mailer - No longer supported
*/
public function onSchedulerInitialized(Event $e)
{
} }
} }

View File

@ -1,22 +1,14 @@
enabled: true enabled: true
from: from:
from_name:
to: to:
to_name:
queue:
enabled: false
flush_frequency: '* * * * *'
flush_msg_limit: 10
flush_time_limit: 100
mailer: mailer:
engine: sendmail engine: sendmail
smtp: smtp:
server: localhost server: localhost
port: 25 port: 25
encryption: none encryption: none
user: '' user:
password: '' password:
auth_mode: ''
sendmail: sendmail:
bin: '/usr/sbin/sendmail -bs' bin: '/usr/sbin/sendmail -bs'
content_type: text/html content_type: text/html

15
plugins/email/hebe.json Normal file
View File

@ -0,0 +1,15 @@
{
"project":"grav-plugin-email",
"platforms":{
"grav":{
"nodes":{
"plugin":[
{
"source":"/",
"destination":"/user/plugins/email"
}
]
}
}
}
}

View File

@ -2,6 +2,7 @@ en:
PLUGIN_EMAIL: PLUGIN_EMAIL:
MAIL_ENGINE: "Mail Engine" MAIL_ENGINE: "Mail Engine"
MAIL_ENGINE_DISABLED: "Disabled" MAIL_ENGINE_DISABLED: "Disabled"
MAIL_ENGINE_DESC: "NOTE: If you select an engine provided by another plugin, you should configure options in that plugin."
CONTENT_TYPE: "Content type" CONTENT_TYPE: "Content type"
CONTENT_TYPE_PLAIN_TEXT: "Plain text" CONTENT_TYPE_PLAIN_TEXT: "Plain text"
CHARSET: "Charset" CHARSET: "Charset"
@ -35,14 +36,14 @@ en:
SMTP_LOGIN_NAME: "SMTP login name" SMTP_LOGIN_NAME: "SMTP login name"
SMTP_PASSWORD: "SMTP password" SMTP_PASSWORD: "SMTP password"
SMTP_AUTH_MODE: "SMTP auth mode" SMTP_AUTH_MODE: "SMTP auth mode"
PATH_TO_SENDMAIL: "Path to sendmail" PATH_TO_SENDMAIL: "Path to sendmail binary"
DEBUG: "Debug" DEBUG: "Debug"
EMAIL_NOT_CONFIGURED: "Email not configured" EMAIL_NOT_CONFIGURED: "Email not configured"
PLEASE_CONFIGURE_A_TO_ADDRESS: "Please configure a 'to' address in the Email Plugin settings, or in the form" PLEASE_CONFIGURE_A_TO_ADDRESS: "Please configure a 'to' address in the Email Plugin settings, or in the form"
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Please configure a 'from' address in the Email Plugin settings, or in the form" PLEASE_CONFIGURE_A_FROM_ADDRESS: "Please configure a 'from' address in the Email Plugin settings, or in the form"
TEST_EMAIL_BODY: "<h1>Testing Email</h1><p>This test email has been sent based on the following configuration:</p> <p><pre>%1$s</pre></p>" TEST_EMAIL_BODY: "<h1>Testing Email</h1><p>This test email has been sent based on the following configuration:</p> <p><pre>%1$s</pre></p>"
EMAIL_DEFAULTS: "Email Defaults" EMAIL_DEFAULTS: "Email Defaults"
SMTP_CONFIGURATION: "SMTP Configuration" SMTP_CONFIGURATION: "SMTP/S Configuration"
SENDMAIL_CONFIGURATION: "Sendmail Configuration" SENDMAIL_CONFIGURATION: "Sendmail Configuration"
ADVANCED: "Advanced" ADVANCED: "Advanced"
EMAIL_FOOTER: "GetGrav.org" EMAIL_FOOTER: "GetGrav.org"
@ -55,8 +56,7 @@ en:
QUEUE_FLUSH_MSG_LIMIT_APPEND: "Messages" QUEUE_FLUSH_MSG_LIMIT_APPEND: "Messages"
QUEUE_FLUSH_TIME_LIMIT: "Flush time limit" QUEUE_FLUSH_TIME_LIMIT: "Flush time limit"
QUEUE_FLUSH_TIME_LIMIT_APPEND: "Seconds" QUEUE_FLUSH_TIME_LIMIT_APPEND: "Seconds"
EMAIL_FORMAT: "Use the `addr` format: `email@address.org` or `name-addr` format: `Your Name <email@address.org>`. Comma separated for multiple addresses"
da: da:
PLUGIN_EMAIL: PLUGIN_EMAIL:
@ -76,11 +76,61 @@ es:
fr: fr:
PLUGIN_EMAIL: PLUGIN_EMAIL:
MAIL_ENGINE: "Moteur de messagerie"
MAIL_ENGINE_DISABLED: "Désactivé"
CONTENT_TYPE: "Type de contenu"
CONTENT_TYPE_PLAIN_TEXT: "Texte brut"
CHARSET: "Jeu de caractères"
CHARSET_PLACEHOLDER: "Defaults to UTF-8"
EMAIL_FORM: "Adresse de lexpéditeur"
EMAIL_FORM_PLACEHOLDER: "Adresse e-mail par défaut de lexpéditeur"
EMAIL_FROM_NAME: "Nom de lexpéditeur"
EMAIL_FROM_NAME_PLACEHOLDER: "Nom par défaut de lexpéditeur"
EMAIL_TO: "Adresse du destinataire"
EMAIL_TO_PLACEHOLDER: "Adresse e-mail par défaut du destinataire"
EMAIL_TO_NAME: "Nom du destinataire"
EMAIL_TO_NAME_PLACEHOLDER: "Nom par défaut du destinataire"
EMAIL_CC: "Adresse en CC"
EMAIL_CC_PLACEHOLDER: "Adresse e-mail par défaut en CC"
EMAIL_CC_NAME: "Nom en CC"
EMAIL_CC_NAME_PLACEHOLDER: "Nom par défaut en CC"
EMAIL_BCC: "Adresse en BCC"
EMAIL_BCC_PLACEHOLDER: "Adresse e-mail par défaut en BCC"
EMAIL_REPLY_TO: "Adresse de réponse"
EMAIL_REPLY_TO_PLACEHOLDER: "Adresse e-mail par défaut pour la réponse"
EMAIL_REPLY_TO_NAME: "Nom pour la réponse"
EMAIL_REPLY_TO_NAME_PLACEHOLDER: "Nom par défaut pour la réponse"
EMAIL_BODY: "Corps"
EMAIL_BODY_PLACEHOLDER: "Table de tous les champs de formulaire par défaut"
SMTP_SERVER: "Serveur SMTP"
SMTP_SERVER_PLACEHOLDER: "ex. smtp.google.com"
SMTP_PORT: "Port SMTP"
SMTP_PORT_PLACEHOLDER: "25 (texte brut) / 587 (encrypté) par défaut"
SMTP_ENCRYPTION: "cryptage SMTP"
SMTP_ENCRYPTION_NONE: "Aucun"
SMTP_LOGIN_NAME: "Nom d'utilisateur SMTP"
SMTP_PASSWORD: "Mot de passe SMTP"
SMTP_AUTH_MODE: "Mode d'authentification SMTP"
PATH_TO_SENDMAIL: "Chemin vers sendmail"
DEBUG: "Débogage"
EMAIL_NOT_CONFIGURED: "Le-mail nest pas configuré" EMAIL_NOT_CONFIGURED: "Le-mail nest pas configuré"
PLEASE_CONFIGURE_A_TO_ADDRESS: "Veuillez configurer une adresse de 'destinataire' dans les paramètres du Plugin ou dans le formulaire." PLEASE_CONFIGURE_A_TO_ADDRESS: "Veuillez configurer une adresse de 'destinataire' dans les paramètres du plugin de-mail ou dans le formulaire."
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Veuillez configurer une adresse 'd'expéditeur' dans les paramètres du Plugin ou dans le formulaire." PLEASE_CONFIGURE_A_FROM_ADDRESS: "Veuillez configurer une adresse 'dexpéditeur' dans les paramètres du plugin de-mail ou dans le formulaire."
TEST_EMAIL_BODY: "<h1>E-mail de test</h1><p>Cet e-mail de test est basé sur la configuration suivante :</p> <p><pre>%1$s</pre></p>" TEST_EMAIL_BODY: "<h1>E-mail de test</h1><p>Cet e-mail de test est basé sur la configuration suivante :</p> <p><pre>%1$s</pre></p>"
EMAIL_DEFAULTS: "E-mail par défaut"
SMTP_CONFIGURATION: "Configuration SMTP"
SENDMAIL_CONFIGURATION: "Configuration Sendmail"
ADVANCED: "Avancée"
EMAIL_FOOTER: "GetGrav.org" EMAIL_FOOTER: "GetGrav.org"
QUEUE_TITLE: "Fil dattente de-mails"
QUEUE_DESC: "Lorsque vous activez la file dattente des e-mails, ils ne sont pas <i>envoyés immédiatement</i>. Au contraire, ils sont envoyés dans la file dattente, puis le planificateur Grav <strong>videra la file dattente</strong> et enverra les e-mails en fonction de la fréquence configurée. Cela permet de sassurer que Grav nattend pas la fin des connexions pour les envoyer"
QUEUE_ENABLED: "Activé"
QUEUE_FLUSH_FREQUENCY: "Fréquence de Flush"
QUEUE_FLUSH_FREQUENCY_HELP: "Utiliser le format 'cron'"
QUEUE_FLUSH_MSG_LIMIT: "Messages par Flush"
QUEUE_FLUSH_MSG_LIMIT_APPEND: "Messages"
QUEUE_FLUSH_TIME_LIMIT: "Délai de Flush"
QUEUE_FLUSH_TIME_LIMIT_APPEND: "Seconds"
hr: hr:
PLUGIN_EMAIL: PLUGIN_EMAIL:
@ -142,7 +192,7 @@ ru:
QUEUE_TITLE: "Очередь Email" QUEUE_TITLE: "Очередь Email"
QUEUE_DESC: "Когда вы включаете очередь email, электронная почта не <i>отправляется немедленно</i>, а отправляется в очередь, затем планировщик Grav <strong>обрабатывает очередь</strong> и на основе настроенной частоты фактически отправляет электронную почту. Это гарантирует, что Grav не ждет завершения подключения к электронной почте." QUEUE_DESC: "Когда вы включаете очередь email, электронная почта не <i>отправляется немедленно</i>, а отправляется в очередь, затем планировщик Grav <strong>обрабатывает очередь</strong> и на основе настроенной частоты фактически отправляет электронную почту. Это гарантирует, что Grav не ждет завершения подключения к электронной почте."
QUEUE_ENABLED: "Включено" QUEUE_ENABLED: "Включено"
QUEUE_FLUSH_FREQUENCY: "Частота обраблтки" QUEUE_FLUSH_FREQUENCY: "Частота обработки"
QUEUE_FLUSH_FREQUENCY_HELP: "Использовать формат 'cron'" QUEUE_FLUSH_FREQUENCY_HELP: "Использовать формат 'cron'"
QUEUE_FLUSH_MSG_LIMIT: "Количество сообщений на задачу" QUEUE_FLUSH_MSG_LIMIT: "Количество сообщений на задачу"
QUEUE_FLUSH_MSG_LIMIT_APPEND: "Сообщений" QUEUE_FLUSH_MSG_LIMIT_APPEND: "Сообщений"

View File

@ -2,6 +2,11 @@
// autoload.php @generated by Composer // autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
exit(1);
}
require_once __DIR__ . '/composer/autoload_real.php'; require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit73924571ea6ee98bb12d10ff20aff2ab::getLoader(); return ComposerAutoloaderInit73924571ea6ee98bb12d10ff20aff2ab::getLoader();

View File

@ -21,12 +21,14 @@ use Composer\Semver\VersionParser;
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
* *
* To require its presence, you can require `composer-runtime-api ^2.0` * To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/ */
class InstalledVersions class InstalledVersions
{ {
/** /**
* @var mixed[]|null * @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/ */
private static $installed; private static $installed;
@ -37,7 +39,7 @@ class InstalledVersions
/** /**
* @var array[] * @var array[]
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/ */
private static $installedByVendor = array(); private static $installedByVendor = array();
@ -241,7 +243,7 @@ class InstalledVersions
/** /**
* @return array * @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/ */
public static function getRootPackage() public static function getRootPackage()
{ {
@ -255,7 +257,7 @@ class InstalledVersions
* *
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[] * @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/ */
public static function getRawData() public static function getRawData()
{ {
@ -278,7 +280,7 @@ class InstalledVersions
* Returns the raw data of all installed.php which are currently loaded for custom implementations * Returns the raw data of all installed.php which are currently loaded for custom implementations
* *
* @return array[] * @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/ */
public static function getAllRawData() public static function getAllRawData()
{ {
@ -301,7 +303,7 @@ class InstalledVersions
* @param array[] $data A vendor/composer/installed.php data set * @param array[] $data A vendor/composer/installed.php data set
* @return void * @return void
* *
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/ */
public static function reload($data) public static function reload($data)
{ {
@ -311,7 +313,7 @@ class InstalledVersions
/** /**
* @return array[] * @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/ */
private static function getInstalled() private static function getInstalled()
{ {

View File

@ -2,11 +2,16 @@
// autoload_classmap.php @generated by Composer // autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__)); $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Grav\\Plugin\\EmailPlugin' => $baseDir . '/email.php', 'Grav\\Plugin\\EmailPlugin' => $baseDir . '/email.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
); );

View File

@ -2,11 +2,12 @@
// autoload_files.php @generated by Composer // autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__)); $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',
); );

View File

@ -2,7 +2,7 @@
// autoload_namespaces.php @generated by Composer // autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__)); $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(

View File

@ -2,12 +2,25 @@
// autoload_psr4.php @generated by Composer // autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__)); $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir); $baseDir = dirname($vendorDir);
return array( return array(
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'),
'Symfony\\Component\\Messenger\\Bridge\\Redis\\' => array($vendorDir . '/symfony/redis-messenger'),
'Symfony\\Component\\Messenger\\Bridge\\Doctrine\\' => array($vendorDir . '/symfony/doctrine-messenger'),
'Symfony\\Component\\Messenger\\Bridge\\Amqp\\' => array($vendorDir . '/symfony/amqp-messenger'),
'Symfony\\Component\\Messenger\\' => array($vendorDir . '/symfony/messenger'),
'Symfony\\Component\\Mailer\\' => array($vendorDir . '/symfony/mailer'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Grav\\Plugin\\Email\\' => array($baseDir . '/classes'), 'Grav\\Plugin\\Email\\' => array($baseDir . '/classes'),
'Grav\\Plugin\\Console\\' => array($baseDir . '/cli'), 'Grav\\Plugin\\Console\\' => array($baseDir . '/cli'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'), 'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),

View File

@ -25,38 +25,15 @@ class ComposerAutoloaderInit73924571ea6ee98bb12d10ff20aff2ab
require __DIR__ . '/platform_check.php'; require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit73924571ea6ee98bb12d10ff20aff2ab', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInit73924571ea6ee98bb12d10ff20aff2ab', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit73924571ea6ee98bb12d10ff20aff2ab', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInit73924571ea6ee98bb12d10ff20aff2ab', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); require __DIR__ . '/autoload_static.php';
if ($useStaticLoader) { call_user_func(\Composer\Autoload\ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::getInitializer($loader));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true); $loader->register(true);
if ($useStaticLoader) { $includeFiles = \Composer\Autoload\ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::$files;
$includeFiles = Composer\Autoload\ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) { foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire73924571ea6ee98bb12d10ff20aff2ab($fileIdentifier, $file); composerRequire73924571ea6ee98bb12d10ff20aff2ab($fileIdentifier, $file);
} }

View File

@ -7,16 +7,33 @@ namespace Composer\Autoload;
class ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab class ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab
{ {
public static $files = array ( public static $files = array (
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
'2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php',
); );
public static $prefixLengthsPsr4 = array ( public static $prefixLengthsPsr4 = array (
'S' => 'S' =>
array ( array (
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Idn\\' => 26, 'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
'Symfony\\Contracts\\Service\\' => 26,
'Symfony\\Contracts\\EventDispatcher\\' => 34,
'Symfony\\Component\\Mime\\' => 23,
'Symfony\\Component\\Messenger\\Bridge\\Redis\\' => 41,
'Symfony\\Component\\Messenger\\Bridge\\Doctrine\\' => 44,
'Symfony\\Component\\Messenger\\Bridge\\Amqp\\' => 40,
'Symfony\\Component\\Messenger\\' => 28,
'Symfony\\Component\\Mailer\\' => 25,
'Symfony\\Component\\EventDispatcher\\' => 34,
),
'P' =>
array (
'Psr\\Log\\' => 8,
'Psr\\EventDispatcher\\' => 20,
'Psr\\Container\\' => 14,
), ),
'G' => 'G' =>
array ( array (
@ -34,6 +51,10 @@ class ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab
); );
public static $prefixDirsPsr4 = array ( public static $prefixDirsPsr4 = array (
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
@ -42,6 +63,54 @@ class ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn', 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn',
), ),
'Symfony\\Contracts\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/service-contracts',
),
'Symfony\\Contracts\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
),
'Symfony\\Component\\Mime\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/mime',
),
'Symfony\\Component\\Messenger\\Bridge\\Redis\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/redis-messenger',
),
'Symfony\\Component\\Messenger\\Bridge\\Doctrine\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/doctrine-messenger',
),
'Symfony\\Component\\Messenger\\Bridge\\Amqp\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/amqp-messenger',
),
'Symfony\\Component\\Messenger\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/messenger',
),
'Symfony\\Component\\Mailer\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/mailer',
),
'Symfony\\Component\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'Psr\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'Grav\\Plugin\\Email\\' => 'Grav\\Plugin\\Email\\' =>
array ( array (
0 => __DIR__ . '/../..' . '/classes', 0 => __DIR__ . '/../..' . '/classes',
@ -61,9 +130,14 @@ class ComposerStaticInit73924571ea6ee98bb12d10ff20aff2ab
); );
public static $classMap = array ( public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Grav\\Plugin\\EmailPlugin' => __DIR__ . '/../..' . '/email.php', 'Grav\\Plugin\\EmailPlugin' => __DIR__ . '/../..' . '/email.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
); );
public static function getInitializer(ClassLoader $loader) public static function getInitializer(ClassLoader $loader)

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +1,151 @@
<?php return array( <?php return array(
'root' => array( 'root' => array(
'name' => 'getgrav/grav-plugin-email',
'pretty_version' => 'dev-develop', 'pretty_version' => 'dev-develop',
'version' => 'dev-develop', 'version' => 'dev-develop',
'reference' => 'f6eb15486887c489363e6bf79fa49ea4a3d833c6',
'type' => 'grav-plugin', 'type' => 'grav-plugin',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'reference' => '6ab4ab743d351b196fa32aa7885651e6b7e2e6d4', 'dev' => true,
'name' => 'getgrav/grav-plugin-email',
'dev' => false,
), ),
'versions' => array( 'versions' => array(
'doctrine/lexer' => array( 'doctrine/lexer' => array(
'pretty_version' => '1.2.3', 'pretty_version' => '1.2.3',
'version' => '1.2.3.0', 'version' => '1.2.3.0',
'reference' => 'c268e882d4dbdd85e36e4ad69e02dc284f89d229',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/lexer', 'install_path' => __DIR__ . '/../doctrine/lexer',
'aliases' => array(), 'aliases' => array(),
'reference' => 'c268e882d4dbdd85e36e4ad69e02dc284f89d229',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'egulias/email-validator' => array( 'egulias/email-validator' => array(
'pretty_version' => '3.1.2', 'pretty_version' => '3.2.1',
'version' => '3.1.2.0', 'version' => '3.2.1.0',
'reference' => 'f88dcf4b14af14a98ad96b14b2b317969eab6715',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../egulias/email-validator', 'install_path' => __DIR__ . '/../egulias/email-validator',
'aliases' => array(), 'aliases' => array(),
'reference' => 'ee0db30118f661fb166bcffbf5d82032df484697',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'getgrav/grav-plugin-email' => array( 'getgrav/grav-plugin-email' => array(
'pretty_version' => 'dev-develop', 'pretty_version' => 'dev-develop',
'version' => 'dev-develop', 'version' => 'dev-develop',
'reference' => 'f6eb15486887c489363e6bf79fa49ea4a3d833c6',
'type' => 'grav-plugin', 'type' => 'grav-plugin',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'reference' => '6ab4ab743d351b196fa32aa7885651e6b7e2e6d4',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'swiftmailer/swiftmailer' => array( 'psr/container' => array(
'pretty_version' => 'v6.3.0', 'pretty_version' => '1.1.1',
'version' => '6.3.0.0', 'version' => '1.1.1.0',
'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../swiftmailer/swiftmailer', 'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/event-dispatcher' => array(
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/event-dispatcher',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/event-dispatcher-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/log' => array(
'pretty_version' => '1.1.4',
'version' => '1.1.4.0',
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/amqp-messenger' => array(
'pretty_version' => 'v5.4.13',
'version' => '5.4.13.0',
'reference' => 'def93f2a7841cfa1a4a1fa487b84054d0d53e521',
'type' => 'symfony-messenger-bridge',
'install_path' => __DIR__ . '/../symfony/amqp-messenger',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v2.5.2',
'version' => '2.5.2.0',
'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/doctrine-messenger' => array(
'pretty_version' => 'v5.4.12',
'version' => '5.4.12.0',
'reference' => '7649a80e917b47c5072480a2d763c2422da239d2',
'type' => 'symfony-messenger-bridge',
'install_path' => __DIR__ . '/../symfony/doctrine-messenger',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/event-dispatcher' => array(
'pretty_version' => 'v5.4.9',
'version' => '5.4.9.0',
'reference' => '8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/event-dispatcher',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/event-dispatcher-contracts' => array(
'pretty_version' => 'v2.5.2',
'version' => '2.5.2.0',
'reference' => 'f98b54df6ad059855739db6fcbc2d36995283fe1',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/event-dispatcher-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '2.0',
),
),
'symfony/mailer' => array(
'pretty_version' => 'v5.4.13',
'version' => '5.4.13.0',
'reference' => '63bf36a5150ac0bfed1c4d0a4e0b114a57b77e11',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/mailer',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/messenger' => array(
'pretty_version' => 'v5.4.13',
'version' => '5.4.13.0',
'reference' => '8f8d3425991e627902f8288088609a8d8f6c6ea4',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/messenger',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/mime' => array(
'pretty_version' => 'v5.4.13',
'version' => '5.4.13.0',
'reference' => 'bb2ccf759e2b967dcd11bdee5bdf30dddd2290bd',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/mime',
'aliases' => array(), 'aliases' => array(),
'reference' => '8a5d5072dca8f48460fce2f4131fcc495eec654c',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-iconv' => array( 'symfony/polyfill-iconv' => array(
@ -53,21 +155,21 @@
), ),
), ),
'symfony/polyfill-intl-idn' => array( 'symfony/polyfill-intl-idn' => array(
'pretty_version' => 'v1.25.0', 'pretty_version' => 'v1.26.0',
'version' => '1.25.0.0', 'version' => '1.26.0.0',
'reference' => '59a8d271f00dd0e4c2e518104cc7963f655a1aa8',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn',
'aliases' => array(), 'aliases' => array(),
'reference' => '749045c69efb97c70d25d7463abba812e91f3a44',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-intl-normalizer' => array( 'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.25.0', 'pretty_version' => 'v1.26.0',
'version' => '1.25.0.0', 'version' => '1.26.0.0',
'reference' => '219aa369ceff116e673852dce47c3a41794c14bd',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
'aliases' => array(), 'aliases' => array(),
'reference' => '8590a5f561694770bdcd3f9b5c69dde6945028e8',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-mbstring' => array( 'symfony/polyfill-mbstring' => array(
@ -82,5 +184,32 @@
0 => '*', 0 => '*',
), ),
), ),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.26.0',
'version' => '1.26.0.0',
'reference' => 'cfa0ae98841b9e461207c13ab093d76b0fa7bace',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/redis-messenger' => array(
'pretty_version' => 'v5.4.13',
'version' => '5.4.13.0',
'reference' => 'd3028b772de91e9aa0342c92ff71c77b130ac9c4',
'type' => 'symfony-messenger-bridge',
'install_path' => __DIR__ . '/../symfony/redis-messenger',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/service-contracts' => array(
'pretty_version' => 'v2.5.2',
'version' => '2.5.2.0',
'reference' => '4b426aac47d6427cc1a1d0f7e2ac724627f5966c',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/service-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
), ),
); );

View File

@ -14,7 +14,7 @@ When doing a PR to v2 remember that you also have to do the PR port to v3, or te
3. Describe the changes you are proposing 3. Describe the changes you are proposing
1. If adding an extra validation state the benefits of adding it and the problem is solving 1. If adding an extra validation state the benefits of adding it and the problem is solving
2. Document in the readme, by adding it to the list 2. Document in the readme, by adding it to the list
4. Provide appropiate tests for the code you are submitting: aim to keep the existing coverage percentage. 4. Provide appropriate tests for the code you are submitting: aim to keep the existing coverage percentage.
5. Add your Twitter handle (if you have) so we can thank you there. 5. Add your Twitter handle (if you have) so we can thank you there.
## License ## License

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,7 @@ class EmailLexer extends AbstractLexer
* *
* @var array * @var array
*/ */
protected $charValue = array( protected $charValue = [
'{' => self::S_OPENCURLYBRACES, '{' => self::S_OPENCURLYBRACES,
'}' => self::S_CLOSECURLYBRACES, '}' => self::S_CLOSECURLYBRACES,
'(' => self::S_OPENPARENTHESIS, '(' => self::S_OPENPARENTHESIS,
@ -105,11 +105,29 @@ class EmailLexer extends AbstractLexer
'?' => self::QUESTIONMARK, '?' => self::QUESTIONMARK,
'#' => self::NUMBER_SIGN, '#' => self::NUMBER_SIGN,
'¡' => self::INVERT_EXCLAMATION, '¡' => self::INVERT_EXCLAMATION,
); ];
/** const INVALID_CHARS_REGEX = "/[^\p{S}\p{C}\p{Cc}]+/iu";
* @var bool
*/ const VALID_UTF8_REGEX = '/\p{Cc}+/u';
const CATCHABLE_PATTERNS = [
'[a-zA-Z]+[46]?', //ASCII and domain literal
'[^\x00-\x7F]', //UTF-8
'[0-9]+',
'\r\n',
'::',
'\s+?',
'.',
];
const NON_CATCHABLE_PATTERNS = [
'[\xA0-\xff]+',
];
const MODIFIERS = 'iu';
/** @var bool */
protected $hasInvalidTokens = false; protected $hasInvalidTokens = false;
/** /**
@ -133,27 +151,21 @@ class EmailLexer extends AbstractLexer
/** /**
* The next token in the input. * The next token in the input.
* *
* @var array|null * @var array{position: int, type: int|null|string, value: int|string}|null
*/ */
public $lookahead; public $lookahead;
/** /** @psalm-var array{value:'', type:null, position:0} */
* @psalm-var array{value:'', type:null, position:0}
*/
private static $nullToken = [ private static $nullToken = [
'value' => '', 'value' => '',
'type' => null, 'type' => null,
'position' => 0, 'position' => 0,
]; ];
/** /** @var string */
* @var string
*/
private $accumulator = ''; private $accumulator = '';
/** /** @var bool */
* @var bool
*/
private $hasToRecord = false; private $hasToRecord = false;
public function __construct() public function __construct()
@ -162,24 +174,13 @@ class EmailLexer extends AbstractLexer
$this->lookahead = null; $this->lookahead = null;
} }
/** public function reset() : void
* @return void
*/
public function reset()
{ {
$this->hasInvalidTokens = false; $this->hasInvalidTokens = false;
parent::reset(); parent::reset();
$this->previous = $this->token = self::$nullToken; $this->previous = $this->token = self::$nullToken;
} }
/**
* @return bool
*/
public function hasInvalidTokens()
{
return $this->hasInvalidTokens;
}
/** /**
* @param int $type * @param int $type
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
@ -187,7 +188,7 @@ class EmailLexer extends AbstractLexer
* *
* @psalm-suppress InvalidScalarArgument * @psalm-suppress InvalidScalarArgument
*/ */
public function find($type) public function find($type) : bool
{ {
$search = clone $this; $search = clone $this;
$search->skipUntil($type); $search->skipUntil($type);
@ -198,30 +199,24 @@ class EmailLexer extends AbstractLexer
return true; return true;
} }
/**
* getPrevious
*
* @return array
*/
public function getPrevious()
{
return $this->previous;
}
/** /**
* moveNext * moveNext
* *
* @return boolean * @return boolean
*/ */
public function moveNext() public function moveNext() : bool
{ {
if ($this->hasToRecord && $this->previous === self::$nullToken) { if ($this->hasToRecord && $this->previous === self::$nullToken) {
$this->accumulator .= $this->token['value']; $this->accumulator .= $this->token['value'];
} }
$this->previous = $this->token; $this->previous = $this->token;
if($this->lookahead === null) {
$this->lookahead = self::$nullToken;
}
$hasNext = parent::moveNext(); $hasNext = parent::moveNext();
$this->token = $this->token ?: self::$nullToken;
if ($this->hasToRecord) { if ($this->hasToRecord) {
$this->accumulator .= $this->token['value']; $this->accumulator .= $this->token['value'];
@ -230,36 +225,6 @@ class EmailLexer extends AbstractLexer
return $hasNext; return $hasNext;
} }
/**
* Lexical catchable patterns.
*
* @return string[]
*/
protected function getCatchablePatterns()
{
return array(
'[a-zA-Z]+[46]?', //ASCII and domain literal
'[^\x00-\x7F]', //UTF-8
'[0-9]+',
'\r\n',
'::',
'\s+?',
'.',
);
}
/**
* Lexical non-catchable patterns.
*
* @return string[]
*/
protected function getNonCatchablePatterns()
{
return [
'[\xA0-\xff]+',
];
}
/** /**
* Retrieve token type. Also processes the token value if necessary. * Retrieve token type. Also processes the token value if necessary.
* *
@ -272,7 +237,7 @@ class EmailLexer extends AbstractLexer
$encoded = $value; $encoded = $value;
if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') { if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') {
$encoded = utf8_encode($value); $encoded = mb_convert_encoding($value, 'UTF-8', 'Windows-1252');
} }
if ($this->isValid($encoded)) { if ($this->isValid($encoded)) {
@ -292,51 +257,64 @@ class EmailLexer extends AbstractLexer
return self::GENERIC; return self::GENERIC;
} }
protected function isInvalidChar(string $value) : bool
{
if(preg_match("/[^\p{S}\p{C}\p{Cc}]+/iu", $value) ) {
return false;
}
return true;
}
protected function isValid(string $value) : bool protected function isValid(string $value) : bool
{ {
if (isset($this->charValue[$value])) { return isset($this->charValue[$value]);
return true;
}
return false;
} }
/** protected function isNullType(string $value) : bool
* @param string $value
* @return bool
*/
protected function isNullType($value)
{ {
if ($value === "\0") { return $value === "\0";
return true; }
}
return false; protected function isInvalidChar(string $value) : bool
{
return !preg_match(self::INVALID_CHARS_REGEX, $value);
} }
protected function isUTF8Invalid(string $value) : bool protected function isUTF8Invalid(string $value) : bool
{ {
if (preg_match('/\p{Cc}+/u', $value)) { return preg_match(self::VALID_UTF8_REGEX, $value) !== false;
return true; }
}
return false; public function hasInvalidTokens() : bool
{
return $this->hasInvalidTokens;
} }
/** /**
* @return string * getPrevious
*
* @return array
*/ */
protected function getModifiers() public function getPrevious() : array
{ {
return 'iu'; return $this->previous;
}
/**
* Lexical catchable patterns.
*
* @return string[]
*/
protected function getCatchablePatterns() : array
{
return self::CATCHABLE_PATTERNS;
}
/**
* Lexical non-catchable patterns.
*
* @return string[]
*/
protected function getNonCatchablePatterns() : array
{
return self::NON_CATCHABLE_PATTERNS;
}
protected function getModifiers() : string
{
return self::MODIFIERS;
} }
public function getAccumulatedValues() : string public function getAccumulatedValues() : string

View File

@ -3,7 +3,6 @@
namespace Egulias\EmailValidator; namespace Egulias\EmailValidator;
use Egulias\EmailValidator\Parser; use Egulias\EmailValidator\Parser;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\Result; use Egulias\EmailValidator\Result\Result;
use Egulias\EmailValidator\Parser\IDLeftPart; use Egulias\EmailValidator\Parser\IDLeftPart;
use Egulias\EmailValidator\Parser\IDRightPart; use Egulias\EmailValidator\Parser\IDRightPart;

View File

@ -22,6 +22,15 @@ use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral;
class DomainLiteral extends PartParser class DomainLiteral extends PartParser
{ {
const IPV4_REGEX = '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
const OBSOLETE_WARNINGS = [
EmailLexer::INVALID,
EmailLexer::C_DEL,
EmailLexer::S_LF,
EmailLexer::S_BACKSLASH
];
public function parse() : Result public function parse() : Result
{ {
$this->addTagWarnings(); $this->addTagWarnings();
@ -41,7 +50,7 @@ class DomainLiteral extends PartParser
} }
if ($this->lexer->isNextTokenAny( if ($this->lexer->isNextTokenAny(
array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF) array(EmailLexer::S_HTAB, EmailLexer::S_SP, EmailLexer::CRLF)
)) { )) {
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS(); $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
$this->parseFWS(); $this->parseFWS();
@ -138,11 +147,8 @@ class DomainLiteral extends PartParser
public function convertIPv4ToIPv6(string $addressLiteralIPv4) : string public function convertIPv4ToIPv6(string $addressLiteralIPv4) : string
{ {
$matchesIP = array(); $matchesIP = [];
$IPv4Match = preg_match( $IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteralIPv4, $matchesIP);
'/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
$addressLiteralIPv4,
$matchesIP);
// Extract IPv4 part from the end of the address-literal (if there is one) // Extract IPv4 part from the end of the address-literal (if there is one)
if ($IPv4Match > 0) { if ($IPv4Match > 0) {
@ -164,11 +170,8 @@ class DomainLiteral extends PartParser
*/ */
protected function checkIPV4Tag($addressLiteral) : bool protected function checkIPV4Tag($addressLiteral) : bool
{ {
$matchesIP = array(); $matchesIP = [];
$IPv4Match = preg_match( $IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteral, $matchesIP);
'/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
$addressLiteral,
$matchesIP);
// Extract IPv4 part from the end of the address-literal (if there is one) // Extract IPv4 part from the end of the address-literal (if there is one)
@ -186,11 +189,7 @@ class DomainLiteral extends PartParser
private function addObsoleteWarnings() : void private function addObsoleteWarnings() : void
{ {
if ($this->lexer->token['type'] === EmailLexer::INVALID || if(in_array($this->lexer->token['type'], self::OBSOLETE_WARNINGS)) {
$this->lexer->token['type'] === EmailLexer::C_DEL ||
$this->lexer->token['type'] === EmailLexer::S_LF ||
$this->lexer->token['type'] === EmailLexer::S_BACKSLASH
) {
$this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT(); $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
} }
} }

View File

@ -292,7 +292,7 @@ class DomainPart extends PartParser
private function isLabelTooLong(string $label) : bool private function isLabelTooLong(string $label) : bool
{ {
if (preg_match('/[^\x00-\x7F]/', $label)) { if (preg_match('/[^\x00-\x7F]/', $label)) {
idn_to_ascii(utf8_decode($label), IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo); idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG); return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
} }
return strlen($label) > self::LABEL_MAX_LENGTH; return strlen($label) > self::LABEL_MAX_LENGTH;

View File

@ -19,18 +19,19 @@ class DoubleQuote extends PartParser
$validQuotedString = $this->checkDQUOTE(); $validQuotedString = $this->checkDQUOTE();
if($validQuotedString->isInvalid()) return $validQuotedString; if($validQuotedString->isInvalid()) return $validQuotedString;
$special = array( $special = [
EmailLexer::S_CR => true, EmailLexer::S_CR => true,
EmailLexer::S_HTAB => true, EmailLexer::S_HTAB => true,
EmailLexer::S_LF => true EmailLexer::S_LF => true
); ];
$invalid = array( $invalid = [
EmailLexer::C_NUL => true, EmailLexer::C_NUL => true,
EmailLexer::S_HTAB => true, EmailLexer::S_HTAB => true,
EmailLexer::S_CR => true, EmailLexer::S_CR => true,
EmailLexer::S_LF => true EmailLexer::S_LF => true
); ];
$setSpecialsWarning = true; $setSpecialsWarning = true;
$this->lexer->moveNext(); $this->lexer->moveNext();

View File

@ -15,6 +15,14 @@ use Egulias\EmailValidator\Result\ValidEmail;
class FoldingWhiteSpace extends PartParser class FoldingWhiteSpace extends PartParser
{ {
const FWS_TYPES = [
EmailLexer::S_SP,
EmailLexer::S_HTAB,
EmailLexer::S_CR,
EmailLexer::S_LF,
EmailLexer::CRLF
];
public function parse() : Result public function parse() : Result
{ {
if (!$this->isFWS()) { if (!$this->isFWS()) {
@ -73,10 +81,6 @@ class FoldingWhiteSpace extends PartParser
return false; return false;
} }
return $this->lexer->token['type'] === EmailLexer::S_SP || return in_array($this->lexer->token['type'], self::FWS_TYPES);
$this->lexer->token['type'] === EmailLexer::S_HTAB ||
$this->lexer->token['type'] === EmailLexer::S_CR ||
$this->lexer->token['type'] === EmailLexer::S_LF ||
$this->lexer->token['type'] === EmailLexer::CRLF;
} }
} }

View File

@ -12,14 +12,14 @@ class IDRightPart extends DomainPart
{ {
protected function validateTokens(bool $hasComments) : Result protected function validateTokens(bool $hasComments) : Result
{ {
$invalidDomainTokens = array( $invalidDomainTokens = [
EmailLexer::S_DQUOTE => true, EmailLexer::S_DQUOTE => true,
EmailLexer::S_SQUOTE => true, EmailLexer::S_SQUOTE => true,
EmailLexer::S_BACKTICK => true, EmailLexer::S_BACKTICK => true,
EmailLexer::S_SEMICOLON => true, EmailLexer::S_SEMICOLON => true,
EmailLexer::S_GREATERTHAN => true, EmailLexer::S_GREATERTHAN => true,
EmailLexer::S_LOWERTHAN => true, EmailLexer::S_LOWERTHAN => true,
); ];
if (isset($invalidDomainTokens[$this->lexer->token['type']])) { if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']); return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']);

View File

@ -15,6 +15,17 @@ use Egulias\EmailValidator\Parser\CommentStrategy\LocalComment;
class LocalPart extends PartParser class LocalPart extends PartParser
{ {
const INVALID_TOKENS = [
EmailLexer::S_COMMA => EmailLexer::S_COMMA,
EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
EmailLexer::S_COLON => EmailLexer::S_COLON,
EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
EmailLexer::INVALID => EmailLexer::INVALID
];
/** /**
* @var string * @var string
*/ */
@ -88,17 +99,7 @@ class LocalPart extends PartParser
protected function validateTokens(bool $hasComments) : Result protected function validateTokens(bool $hasComments) : Result
{ {
$invalidTokens = array( if (isset(self::INVALID_TOKENS[$this->lexer->token['type']])) {
EmailLexer::S_COMMA => EmailLexer::S_COMMA,
EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
EmailLexer::S_COLON => EmailLexer::S_COLON,
EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
EmailLexer::INVALID => EmailLexer::INVALID
);
if (isset($invalidTokens[$this->lexer->token['type']])) {
return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']); return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']);
} }
return new ValidEmail(); return new ValidEmail();

View File

@ -11,6 +11,6 @@ class UnOpenedComment implements Reason
public function description(): string public function description(): string
{ {
return 'Missing openning comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2'; return 'Missing opening comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2';
} }
} }

View File

@ -2,6 +2,7 @@
namespace Egulias\EmailValidator\Validation; namespace Egulias\EmailValidator\Validation;
use Egulias\EmailValidator\Validation\DNSGetRecordWrapper;
use Egulias\EmailValidator\EmailLexer; use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\InvalidEmail; use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail; use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail;
@ -17,6 +18,29 @@ class DNSCheckValidation implements EmailValidation
*/ */
protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA; protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA;
/**
* Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2),
* mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G)
*/
const RESERVED_DNS_TOP_LEVEL_NAMES = [
// Reserved Top Level DNS Names
'test',
'example',
'invalid',
'localhost',
// mDNS
'local',
// Private DNS Namespaces
'intranet',
'internal',
'private',
'corp',
'home',
'lan',
];
/** /**
* @var array * @var array
*/ */
@ -32,12 +56,22 @@ class DNSCheckValidation implements EmailValidation
*/ */
private $mxRecords = []; private $mxRecords = [];
/**
* @var DNSGetRecordWrapper
*/
private $dnsGetRecord;
public function __construct() public function __construct(DNSGetRecordWrapper $dnsGetRecord = null)
{ {
if (!function_exists('idn_to_ascii')) { if (!function_exists('idn_to_ascii')) {
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__)); throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
} }
if ($dnsGetRecord == null) {
$dnsGetRecord = new DNSGetRecordWrapper();
}
$this->dnsGetRecord = $dnsGetRecord;
} }
public function isValid(string $email, EmailLexer $emailLexer) : bool public function isValid(string $email, EmailLexer $emailLexer) : bool
@ -53,29 +87,8 @@ class DNSCheckValidation implements EmailValidation
// Get the domain parts // Get the domain parts
$hostParts = explode('.', $host); $hostParts = explode('.', $host);
// Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2),
// mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G)
$reservedTopLevelDnsNames = [
// Reserved Top Level DNS Names
'test',
'example',
'invalid',
'localhost',
// mDNS
'local',
// Private DNS Namespaces
'intranet',
'internal',
'private',
'corp',
'home',
'lan',
];
$isLocalDomain = count($hostParts) <= 1; $isLocalDomain = count($hostParts) <= 1;
$isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], $reservedTopLevelDnsNames, true); $isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], self::RESERVED_DNS_TOP_LEVEL_NAMES, true);
// Exclude reserved top level DNS names // Exclude reserved top level DNS names
if ($isLocalDomain || $isReservedTopLevel) { if ($isLocalDomain || $isReservedTopLevel) {
@ -120,27 +133,17 @@ class DNSCheckValidation implements EmailValidation
*/ */
private function validateDnsRecords($host) : bool private function validateDnsRecords($host) : bool
{ {
// A workaround to fix https://bugs.php.net/bug.php?id=73149 $dnsRecordsResult = $this->dnsGetRecord->getRecords($host, static::DNS_RECORD_TYPES_TO_CHECK);
/** @psalm-suppress InvalidArgument */
set_error_handler(
static function (int $errorLevel, string $errorMessage): ?bool {
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
}
);
try { if ($dnsRecordsResult->withError()) {
// Get all MX, A and AAAA DNS records for host
$dnsRecords = dns_get_record($host, static::DNS_RECORD_TYPES_TO_CHECK);
} catch (\RuntimeException $exception) {
$this->error = new InvalidEmail(new UnableToGetDNSRecord(), ''); $this->error = new InvalidEmail(new UnableToGetDNSRecord(), '');
return false; return false;
} finally {
restore_error_handler();
} }
$dnsRecords = $dnsRecordsResult->getRecords();
// No MX, A or AAAA DNS records // No MX, A or AAAA DNS records
if ($dnsRecords === [] || $dnsRecords === false) { if ($dnsRecords === []) {
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), ''); $this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
return false; return false;
} }
@ -167,6 +170,11 @@ class DNSCheckValidation implements EmailValidation
*/ */
private function validateMxRecord($dnsRecord) : bool private function validateMxRecord($dnsRecord) : bool
{ {
if (!isset($dnsRecord['type'])) {
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
return false;
}
if ($dnsRecord['type'] !== 'MX') { if ($dnsRecord['type'] !== 'MX') {
return true; return true;
} }

View File

@ -0,0 +1,28 @@
<?php
namespace Egulias\EmailValidator\Validation;
class DNSGetRecordWrapper
{
/**
* @param string $host
* @param int $type
*/
public function getRecords(string $host, int $type) : DNSRecords
{
// A workaround to fix https://bugs.php.net/bug.php?id=73149
/** @psalm-suppress InvalidArgument */
set_error_handler(
static function (int $errorLevel, string $errorMessage): ?bool {
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
}
);
try {
// Get all MX, A and AAAA DNS records for host
return new DNSRecords(dns_get_record($host, $type));
} catch (\RuntimeException $exception) {
return new DNSRecords([], true);
} finally {
restore_error_handler();
}
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Egulias\EmailValidator\Validation;
class DNSRecords
{
/**
* @var array $records
*/
private $records = [];
/**
* @var bool $error
*/
private $error = false;
public function __construct(array $records, bool $error = false)
{
$this->records = $records;
$this->error = $error;
}
public function getRecords() : array
{
return $this->records;
}
public function withError() : bool
{
return $this->error;
}
}

View File

@ -10,7 +10,7 @@ use Egulias\EmailValidator\Result\MultipleErrors;
class MultipleValidationWithAnd implements EmailValidation class MultipleValidationWithAnd implements EmailValidation
{ {
/** /**
* If one of validations fails, the remaining validations will be skept. * If one of validations fails, the remaining validations will be skipped.
* This means MultipleErrors will only contain a single error, the first found. * This means MultipleErrors will only contain a single error, the first found.
*/ */
const STOP_ON_ERROR = 0; const STOP_ON_ERROR = 0;

View File

@ -42,6 +42,6 @@ abstract class Warning
public function __toString() public function __toString()
{ {
return $this->message() . " rfc: " . $this->rfcNumber . "interal code: " . static::CODE; return $this->message() . " rfc: " . $this->rfcNumber . "internal code: " . static::CODE;
} }
} }

View File

@ -0,0 +1,3 @@
composer.lock
composer.phar
/vendor/

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013-2016 container-interop
Copyright (c) 2016 PHP Framework Interoperability Group
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,13 @@
Container interface
==============
This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url].
Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container.
The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
[psr-url]: https://www.php-fig.org/psr/psr-11/
[package-url]: https://packagist.org/packages/psr/container
[implementation-url]: https://packagist.org/providers/psr/container-implementation

View File

@ -0,0 +1,22 @@
{
"name": "psr/container",
"type": "library",
"description": "Common Container Interface (PHP FIG PSR-11)",
"keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"],
"homepage": "https://github.com/php-fig/container",
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"require": {
"php": ">=7.2.0"
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Psr\Container;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface
{
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Psr\Container;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get(string $id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has(string $id);
}

View File

@ -0,0 +1,10 @@
<?php
namespace Psr\Container;
/**
* No entry was found in the container.
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}

View File

@ -0,0 +1,15 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[Makefile]
indent_style = tab

View File

@ -0,0 +1,2 @@
/vendor/
composer.lock

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 PHP-FIG
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,6 @@
PSR Event Dispatcher
====================
This repository holds the interfaces related to [PSR-14](http://www.php-fig.org/psr/psr-14/).
Note that this is not an Event Dispatcher implementation of its own. It is merely interfaces that describe the components of an Event Dispatcher. See the specification for more details.

View File

@ -0,0 +1,26 @@
{
"name": "psr/event-dispatcher",
"description": "Standard interfaces for event handling.",
"type": "library",
"keywords": ["psr", "psr-14", "events"],
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"require": {
"php": ">=7.2.0"
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* Defines a dispatcher for events.
*/
interface EventDispatcherInterface
{
/**
* Provide all relevant listeners with an event to process.
*
* @param object $event
* The object to process.
*
* @return object
* The Event that was passed, now modified by listeners.
*/
public function dispatch(object $event);
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* Mapper from an event to the listeners that are applicable to that event.
*/
interface ListenerProviderInterface
{
/**
* @param object $event
* An event for which to return the relevant listeners.
* @return iterable[callable]
* An iterable (array, iterator, or generator) of callables. Each
* callable MUST be type-compatible with $event.
*/
public function getListenersForEvent(object $event) : iterable;
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Psr\EventDispatcher;
/**
* An Event whose processing may be interrupted when the event has been handled.
*
* A Dispatcher implementation MUST check to determine if an Event
* is marked as stopped after each listener is called. If it is then it should
* return immediately without calling any further Listeners.
*/
interface StoppableEventInterface
{
/**
* Is propagation stopped?
*
* This will typically only be used by the Dispatcher to determine if the
* previous listener halted propagation.
*
* @return bool
* True if the Event is complete and no further listeners should be called.
* False to continue calling listeners.
*/
public function isPropagationStopped() : bool;
}

View File

@ -1,19 +0,0 @@
<!-- Please fill in this template according to your issue. -->
| Q | A
| ------------------- | -----
| Bug report? | yes/no
| Feature request? | yes/no
| RFC? | yes/no
| How used? | Standalone/Symfony/3party
| Swiftmailer version | x.y.z
| PHP version | x.y.z
### Observed behaviour
<!-- What does the code do? -->
### Expected behaviour
<!-- What should the code do? -->
### Example
<!-- Example to reproduce the issue. -->

View File

@ -1,14 +0,0 @@
<!-- Please fill in this template according to the PR you're about to submit. -->
| Q | A
| ------------- | ---
| Bug fix? | yes/no
| New feature? | yes/no
| Doc update? | yes/no
| BC breaks? | yes/no
| Deprecations? | yes/no
| Fixed tickets | #... <!-- #-prefixed issue number(s), if any -->
| License | MIT
<!-- Replace this comment by the description of your issue. -->

View File

@ -1,21 +0,0 @@
<?php
return PhpCsFixer\Config::create()
->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'@PHPUnit75Migration:risky' => true,
'php_unit_dedicate_assert' => ['target' => '5.6'],
'array_syntax' => ['syntax' => 'short'],
'php_unit_fqcn_annotation' => true,
'no_unreachable_default_argument_value' => false,
'braces' => ['allow_single_line_closure' => true],
'heredoc_to_nowdoc' => false,
'ordered_imports' => true,
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'],
'fopen_flags' => false,
])
->setRiskyAllowed(true)
->setFinder(PhpCsFixer\Finder::create()->in(__DIR__))
;

View File

@ -1,391 +0,0 @@
Changelog
=========
**Swiftmailer will stop being maintained at the end of November 2021.**
Please, move to Symfony Mailer at your earliest convenience.
Symfony Mailer is the next evolution of Swiftmailer.
It provides the same features with support for modern PHP code and support for third-party providers.
See https://symfony.com/doc/current/mailer.html for more information.
6.3.0 (2021-10-18)
------------------
* Fix support for PHP 8.1
6.2.7 (2021-03-09)
------------------
* Allow egulias/email-validator 3.1+
6.2.6 (2021-03-05)
------------------
* Fix Bcc support
6.2.5 (2021-01-12)
------------------
* Don't trust properties at destruct time
* Remove invalid PHPDocs param in EventDispatcher interface
* Bump license year
* Removes PHP version from README
6.2.4 (2020-12-08)
------------------
* Prevent flushing of the bubble queue when event handler raises another event
* Add support for PHP 8
* Code cleanups
6.2.3 (2019-11-12)
------------------
* no changes
6.2.2 (2019-11-12)
------------------
* fixed compat with PHP 7.4
* fixed error message when connecting to a stream raises an error before connect()
6.2.1 (2019-04-21)
------------------
* reverted "deprecated Swift_CharacterStream_ArrayCharacterStream and Swift_CharacterStream_NgCharacterStream in favor of Swift_CharacterStream_CharacterStream"
6.2.0 (2019-03-10)
------------------
* added support for symfony/polyfill-intl-dn
* deprecated Swift_CharacterStream_ArrayCharacterStream and Swift_CharacterStream_NgCharacterStream in favor of Swift_CharacterStream_CharacterStream
6.1.3 (2018-09-11)
------------------
* added auto-start to the SMTP transport when sending a message
* tweaked error message when the response from an SMTP server is empty
* fixed missing property in Swift_Mime_IdGenerator
* exposed original body content type with Swift_Mime_SimpleMimeEntity::getBodyContentType()
* fixed typo in variable name in Swift_AddressEncoder_IdnAddressEncoder
* fixed return type in MessageLogger
* fixed missing property addressEncoder in SimpleHeaderFactory class
6.1.2 (2018-07-13)
------------------
* handled recipient errors when pipelining
6.1.1 (2018-07-04)
------------------
* removed hard dependency on an IDN encoder
6.1.0 (2018-07-02)
------------------
* added address encoder exceptions during send
* added support for bubbling up authenticator error messages
* added support for non-ASCII email addresses
* introduced new dependencies: transport.smtphandlers and transport.authhandlers
* deprecated Swift_Signers_OpenDKIMSigner; use Swift_Signers_DKIMSigner instead
* added support for SMTP pipelining
* added Swift_Transport_Esmtp_EightBitMimeHandler
* fixed startTLS only allowed tls1.0, now allowed: tls1.0, tls1.1, tls1.2
6.0.2 (2017-09-30)
------------------
* fixed DecoratorPlugin
* removed usage of getmypid()
6.0.1 (2017-05-20)
------------------
* fixed BC break that can be avoided easily
6.0.0 (2017-05-19)
------------------
* added Swift_Transport::ping()
* removed Swift_Mime_HeaderFactory, Swift_Mime_HeaderSet, Swift_Mime_Message, Swift_Mime_MimeEntity,
and Swift_Mime_ParameterizedHeader interfaces
* removed Swift_MailTransport and Swift_Transport_MailTransport
* removed Swift_Encoding
* removed the Swift_Transport_MailInvoker interface and Swift_Transport_SimpleMailInvoker class
* removed the Swift_SignedMessage class
* removed newInstance() methods everywhere
* methods operating on Date header now use DateTimeImmutable object instead of Unix timestamp;
Swift_Mime_Headers_DateHeader::getTimestamp()/setTimestamp() renamed to getDateTime()/setDateTime()
* bumped minimum version to PHP 7.0
* removed Swift_Validate and replaced by egulias/email-validator
5.4.9 (2018-01-23)
------------------
* no changes, last version of the 5.x series
5.4.8 (2017-05-01)
------------------
* fixed encoding inheritance in addPart()
* fixed sorting MIME children when their types are equal
5.4.7 (2017-04-20)
------------------
* fixed NTLMAuthenticator clobbering bcmath scale
5.4.6 (2017-02-13)
------------------
* removed exceptions thrown in destructors as they lead to fatal errors
* switched to use sha256 by default in DKIM as per the RFC
* fixed an 'Undefined variable: pipes' PHP notice
* fixed long To headers when using the mail transport
* fixed NTLMAuthenticator when no domain is passed with the username
* prevented fatal error during unserialization of a message
* fixed a PHP warning when sending a message that has a length of a multiple of 8192
5.4.5 (2016-12-29)
------------------
* SECURITY FIX: fixed CVE-2016-10074 by disallowing potentially unsafe shell characters
Prior to 5.4.5, the mail transport (Swift_Transport_MailTransport) was vulnerable to passing
arbitrary shell arguments if the "From", "ReturnPath" or "Sender" header came
from a non-trusted source, potentially allowing Remote Code Execution
* deprecated the mail transport
5.4.4 (2016-11-23)
------------------
* reverted escaping command-line args to mail (PHP mail() function already does it)
5.4.3 (2016-07-08)
------------------
* fixed SimpleHeaderSet::has()/get() when the 0 index is removed
* removed the need to have mcrypt installed
* fixed broken MIME header encoding with quotes/colons and non-ascii chars
* allowed mail transport send for messages without To header
* fixed PHP 7 support
5.4.2 (2016-05-01)
------------------
* fixed support for IPv6 sockets
* added auto-retry when sending messages from the memory spool
* fixed consecutive read calls in Swift_ByteStream_FileByteStream
* added support for iso-8859-15 encoding
* fixed PHP mail extra params on missing reversePath
* added methods to set custom stream context options
* fixed charset changes in QpContentEncoderProxy
* added return-path header to the ignoredHeaders list of DKIMSigner
* fixed crlf for subject using mail
* fixed add soft line break only when necessary
* fixed escaping command-line args to mail
5.4.1 (2015-06-06)
------------------
* made Swiftmailer exceptions confirm to PHP base exception constructor signature
* fixed MAIL FROM & RCPT TO headers to be RFC compliant
5.4.0 (2015-03-14)
------------------
* added the possibility to add extra certs to PKCS#7 signature
* fix base64 encoding with streams
* added a new RESULT_SPOOLED status for SpoolTransport
* fixed getBody() on attachments when called more than once
* removed dots from generated filenames in filespool
5.3.1 (2014-12-05)
------------------
* fixed cloning of messages with attachments
5.3.0 (2014-10-04)
------------------
* fixed cloning when using signers
* reverted removal of Swift_Encoding
* drop support for PHP 5.2.x
5.2.2 (2014-09-20)
------------------
* fixed Japanese support
* fixed the memory spool when the message changes when in the pool
* added support for cloning messages
* fixed PHP warning in the redirect plugin
* changed the way to and cc-ed email are sent to only use one transaction
5.2.1 (2014-06-13)
------------------
* SECURITY FIX: fixed CLI escaping when using sendmail as a transport
Prior to 5.2.1, the sendmail transport (Swift_Transport_SendmailTransport)
was vulnerable to an arbitrary shell execution if the "From" header came
from a non-trusted source and no "Return-Path" is configured.
* fixed parameter in DKIMSigner
* fixed compatibility with PHP < 5.4
5.2.0 (2014-05-08)
------------------
* fixed Swift_ByteStream_FileByteStream::read() to match to the specification
* fixed from-charset and to-charset arguments in mbstring_convert_encoding() usages
* fixed infinite loop in StreamBuffer
* fixed NullTransport to return the number of ignored emails instead of 0
* Use phpunit and mockery for unit testing (realityking)
5.1.0 (2014-03-18)
------------------
* fixed data writing to stream when sending large messages
* added support for libopendkim (https://github.com/xdecock/php-opendkim)
* merged SignedMessage and Message
* added Gmail XOAuth2 authentication
* updated the list of known mime types
* added NTLM authentication
5.0.3 (2013-12-03)
------------------
* fixed double-dot bug
* fixed DKIM signer
5.0.2 (2013-08-30)
------------------
* handled correct exception type while reading IoBuffer output
5.0.1 (2013-06-17)
------------------
* changed the spool to only start the transport when a mail has to be sent
* fixed compatibility with PHP 5.2
* fixed LICENSE file
5.0.0 (2013-04-30)
------------------
* changed the license from LGPL to MIT
4.3.1 (2013-04-11)
------------------
* removed usage of the native QP encoder when the charset is not UTF-8
* fixed usage of uniqid to avoid collisions
* made a performance improvement when tokenizing large headers
* fixed usage of the PHP native QP encoder on PHP 5.4.7+
4.3.0 (2013-01-08)
------------------
* made the temporary directory configurable via the TMPDIR env variable
* added S/MIME signer and encryption support
4.2.2 (2012-10-25)
------------------
* added the possibility to throttle messages per second in ThrottlerPlugin (mostly for Amazon SES)
* switched mime.qpcontentencoder to automatically use the PHP native encoder on PHP 5.4.7+
* allowed specifying a whitelist with regular expressions in RedirectingPlugin
4.2.1 (2012-07-13)
------------------
* changed the coding standards to PSR-1/2
* fixed issue with autoloading
* added NativeQpContentEncoder to enhance performance (for PHP 5.3+)
4.2.0 (2012-06-29)
------------------
* added documentation about how to use the Japanese support introduced in 4.1.8
* added a way to override the default configuration in a lazy way
* changed the PEAR init script to lazy-load the initialization
* fixed a bug when calling Swift_Preferences before anything else (regression introduced in 4.1.8)
4.1.8 (2012-06-17)
------------------
* added Japanese iso-2022-jp support
* changed the init script to lazy-load the initialization
* fixed docblocks (@id) which caused some problems with libraries parsing the dobclocks
* fixed Swift_Mime_Headers_IdentificationHeader::setId() when passed an array of ids
* fixed encoding of email addresses in headers
* added replacements setter to the Decorator plugin
4.1.7 (2012-04-26)
------------------
* fixed QpEncoder safeMapShareId property
4.1.6 (2012-03-23)
------------------
* reduced the size of serialized Messages
4.1.5 (2012-01-04)
------------------
* enforced Swift_Spool::queueMessage() to return a Boolean
* made an optimization to the memory spool: start the transport only when required
* prevented stream_socket_client() from generating an error and throw a Swift_TransportException instead
* fixed a PHP warning when calling to mail() when safe_mode is off
* many doc tweaks
4.1.4 (2011-12-16)
------------------
* added a memory spool (Swift_MemorySpool)
* fixed too many opened files when sending emails with attachments
4.1.3 (2011-10-27)
------------------
* added STARTTLS support
* added missing @return tags on fluent methods
* added a MessageLogger plugin that logs all sent messages
* added composer.json
4.1.2 (2011-09-13)
------------------
* fixed wrong detection of magic_quotes_runtime
* fixed fatal errors when no To or Subject header has been set
* fixed charset on parameter header continuations
* added documentation about how to install Swiftmailer from the PEAR channel
* fixed various typos and markup problem in the documentation
* fixed warning when cache directory does not exist
* fixed "slashes are escaped" bug
* changed require_once() to require() in autoload
4.1.1 (2011-07-04)
------------------
* added missing file in PEAR package
4.1.0 (2011-06-30)
------------------
* documentation has been converted to ReST
4.1.0 RC1 (2011-06-17)
----------------------
New features:
* changed the Decorator Plugin to allow replacements in all headers
* added Swift_Mime_Grammar and Swift_Validate to validate an email address
* modified the autoloader to lazy-initialize Swiftmailer
* removed Swift_Mailer::batchSend()
* added NullTransport
* added new plugins: RedirectingPlugin and ImpersonatePlugin
* added a way to send messages asynchronously (Spool)

View File

@ -1,25 +0,0 @@
Swift Mailer
------------
**Swiftmailer will stop being maintained at the end of November 2021.**
Please, move to [Symfony Mailer](https://symfony.com/doc/current/mailer.html) at your earliest convenience.
[Symfony Mailer](https://symfony.com/doc/current/mailer.html) is the next evolution of Swiftmailer.
It provides the same features with support for modern PHP code and support for third-party providers.
Swift Mailer is a component based mailing solution for PHP.
It is released under the MIT license.
Swift Mailer is highly object-oriented by design and lends itself
to use in complex web application with a great deal of flexibility.
For full details on usage, read the [documentation](https://swiftmailer.symfony.com/docs/introduction.html).
Sponsors
--------
<div>
<a href="https://blackfire.io/docs/introduction?utm_source=swiftmailer&utm_medium=github_readme&utm_campaign=logo">
<img src="https://static.blackfire.io/assets/intemporals/logo/png/blackfire-io_secondary_horizontal_transparent.png?1" width="255px" alt="Blackfire.io">
</a>
</div>

View File

@ -1,78 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* General utility class in Swift Mailer, not to be instantiated.
*
* @author Chris Corbyn
*/
abstract class Swift
{
const VERSION = '6.3.0';
public static $initialized = false;
public static $inits = [];
/**
* Registers an initializer callable that will be called the first time
* a SwiftMailer class is autoloaded.
*
* This enables you to tweak the default configuration in a lazy way.
*
* @param mixed $callable A valid PHP callable that will be called when autoloading the first Swift class
*/
public static function init($callable)
{
self::$inits[] = $callable;
}
/**
* Internal autoloader for spl_autoload_register().
*
* @param string $class
*/
public static function autoload($class)
{
// Don't interfere with other autoloaders
if (0 !== strpos($class, 'Swift_')) {
return;
}
$path = __DIR__.'/'.str_replace('_', '/', $class).'.php';
if (!file_exists($path)) {
return;
}
require $path;
if (self::$inits && !self::$initialized) {
self::$initialized = true;
foreach (self::$inits as $init) {
\call_user_func($init);
}
}
}
/**
* Configure autoloading using Swift Mailer.
*
* This is designed to play nicely with other autoloaders.
*
* @param mixed $callable A valid PHP callable that will be called when autoloading the first Swift class
*/
public static function registerAutoload($callable = null)
{
if (null !== $callable) {
self::$inits[] = $callable;
}
spl_autoload_register(['Swift', 'autoload']);
}
}

View File

@ -1,25 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Email address encoder.
*
* @author Christian Schmidt
*/
interface Swift_AddressEncoder
{
/**
* Encodes an email address.
*
* @throws Swift_AddressEncoderException if the email cannot be represented in
* the encoding implemented by this class
*/
public function encodeString(string $address): string;
}

View File

@ -1,36 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A UTF-8 email address encoder.
*
* Returns the email address verbatimly in UTF-8 as permitted by RFC 6531 and
* RFC 6532. It supports addresses containing non-ASCII characters in both
* local-part and domain (i.e. on both sides of @).
*
* This encoder must be used together with Swift_Transport_Esmtp_SmtpUtf8Handler
* and requires that the outbound SMTP server supports the SMTPUTF8 extension.
*
* If your outbound SMTP server does not support SMTPUTF8, use
* Swift_AddressEncoder_IdnAddressEncoder instead. This allows sending to email
* addresses with non-ASCII characters in the domain, but not in local-part.
*
* @author Christian Schmidt
*/
class Swift_AddressEncoder_Utf8AddressEncoder implements Swift_AddressEncoder
{
/**
* Returns the address verbatimly.
*/
public function encodeString(string $address): string
{
return $address;
}
}

View File

@ -1,32 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* AddressEncoderException when the specified email address is in a format that
* cannot be encoded by a given address encoder.
*
* @author Christian Schmidt
*/
class Swift_AddressEncoderException extends Swift_RfcComplianceException
{
protected $address;
public function __construct(string $message, string $address)
{
parent::__construct($message);
$this->address = $address;
}
public function getAddress(): string
{
return $this->address;
}
}

View File

@ -1,54 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Attachment class for attaching files to a {@link Swift_Mime_SimpleMessage}.
*
* @author Chris Corbyn
*/
class Swift_Attachment extends Swift_Mime_Attachment
{
/**
* Create a new Attachment.
*
* Details may be optionally provided to the constructor.
*
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
*/
public function __construct($data = null, $filename = null, $contentType = null)
{
\call_user_func_array(
[$this, 'Swift_Mime_Attachment::__construct'],
Swift_DependencyContainer::getInstance()
->createDependenciesFor('mime.attachment')
);
$this->setBody($data, $contentType);
$this->setFilename($filename);
}
/**
* Create a new Attachment from a filesystem path.
*
* @param string $path
* @param string $contentType optional
*
* @return self
*/
public static function fromPath($path, $contentType = null)
{
return (new self())->setFile(
new Swift_ByteStream_FileByteStream($path),
$contentType
);
}
}

View File

@ -1,176 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Provides the base functionality for an InputStream supporting filters.
*
* @author Chris Corbyn
*/
abstract class Swift_ByteStream_AbstractFilterableInputStream implements Swift_InputByteStream, Swift_Filterable
{
/**
* Write sequence.
*/
protected $sequence = 0;
/**
* StreamFilters.
*
* @var Swift_StreamFilter[]
*/
private $filters = [];
/**
* A buffer for writing.
*/
private $writeBuffer = '';
/**
* Bound streams.
*
* @var Swift_InputByteStream[]
*/
private $mirrors = [];
/**
* Commit the given bytes to the storage medium immediately.
*
* @param string $bytes
*/
abstract protected function doCommit($bytes);
/**
* Flush any buffers/content with immediate effect.
*/
abstract protected function flush();
/**
* Add a StreamFilter to this InputByteStream.
*
* @param string $key
*/
public function addFilter(Swift_StreamFilter $filter, $key)
{
$this->filters[$key] = $filter;
}
/**
* Remove an already present StreamFilter based on its $key.
*
* @param string $key
*/
public function removeFilter($key)
{
unset($this->filters[$key]);
}
/**
* Writes $bytes to the end of the stream.
*
* @param string $bytes
*
* @throws Swift_IoException
*
* @return int
*/
public function write($bytes)
{
$this->writeBuffer .= $bytes;
foreach ($this->filters as $filter) {
if ($filter->shouldBuffer($this->writeBuffer)) {
return;
}
}
$this->doWrite($this->writeBuffer);
return ++$this->sequence;
}
/**
* For any bytes that are currently buffered inside the stream, force them
* off the buffer.
*
* @throws Swift_IoException
*/
public function commit()
{
$this->doWrite($this->writeBuffer);
}
/**
* Attach $is to this stream.
*
* The stream acts as an observer, receiving all data that is written.
* All {@link write()} and {@link flushBuffers()} operations will be mirrored.
*/
public function bind(Swift_InputByteStream $is)
{
$this->mirrors[] = $is;
}
/**
* Remove an already bound stream.
*
* If $is is not bound, no errors will be raised.
* If the stream currently has any buffered data it will be written to $is
* before unbinding occurs.
*/
public function unbind(Swift_InputByteStream $is)
{
foreach ($this->mirrors as $k => $stream) {
if ($is === $stream) {
if ('' !== $this->writeBuffer) {
$stream->write($this->writeBuffer);
}
unset($this->mirrors[$k]);
}
}
}
/**
* Flush the contents of the stream (empty it) and set the internal pointer
* to the beginning.
*
* @throws Swift_IoException
*/
public function flushBuffers()
{
if ('' !== $this->writeBuffer) {
$this->doWrite($this->writeBuffer);
}
$this->flush();
foreach ($this->mirrors as $stream) {
$stream->flushBuffers();
}
}
/** Run $bytes through all filters */
private function filter($bytes)
{
foreach ($this->filters as $filter) {
$bytes = $filter->filter($bytes);
}
return $bytes;
}
/** Just write the bytes to the stream */
private function doWrite($bytes)
{
$this->doCommit($this->filter($bytes));
foreach ($this->mirrors as $stream) {
$stream->write($bytes);
}
$this->writeBuffer = '';
}
}

View File

@ -1,178 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Allows reading and writing of bytes to and from an array.
*
* @author Chris Corbyn
*/
class Swift_ByteStream_ArrayByteStream implements Swift_InputByteStream, Swift_OutputByteStream
{
/**
* The internal stack of bytes.
*
* @var string[]
*/
private $array = [];
/**
* The size of the stack.
*
* @var int
*/
private $arraySize = 0;
/**
* The internal pointer offset.
*
* @var int
*/
private $offset = 0;
/**
* Bound streams.
*
* @var Swift_InputByteStream[]
*/
private $mirrors = [];
/**
* Create a new ArrayByteStream.
*
* If $stack is given the stream will be populated with the bytes it contains.
*
* @param mixed $stack of bytes in string or array form, optional
*/
public function __construct($stack = null)
{
if (\is_array($stack)) {
$this->array = $stack;
$this->arraySize = \count($stack);
} elseif (\is_string($stack)) {
$this->write($stack);
} else {
$this->array = [];
}
}
/**
* Reads $length bytes from the stream into a string and moves the pointer
* through the stream by $length.
*
* If less bytes exist than are requested the
* remaining bytes are given instead. If no bytes are remaining at all, boolean
* false is returned.
*
* @param int $length
*
* @return string
*/
public function read($length)
{
if ($this->offset == $this->arraySize) {
return false;
}
// Don't use array slice
$end = $length + $this->offset;
$end = $this->arraySize < $end ? $this->arraySize : $end;
$ret = '';
for (; $this->offset < $end; ++$this->offset) {
$ret .= $this->array[$this->offset];
}
return $ret;
}
/**
* Writes $bytes to the end of the stream.
*
* @param string $bytes
*/
public function write($bytes)
{
$to_add = str_split($bytes);
foreach ($to_add as $value) {
$this->array[] = $value;
}
$this->arraySize = \count($this->array);
foreach ($this->mirrors as $stream) {
$stream->write($bytes);
}
}
/**
* Not used.
*/
public function commit()
{
}
/**
* Attach $is to this stream.
*
* The stream acts as an observer, receiving all data that is written.
* All {@link write()} and {@link flushBuffers()} operations will be mirrored.
*/
public function bind(Swift_InputByteStream $is)
{
$this->mirrors[] = $is;
}
/**
* Remove an already bound stream.
*
* If $is is not bound, no errors will be raised.
* If the stream currently has any buffered data it will be written to $is
* before unbinding occurs.
*/
public function unbind(Swift_InputByteStream $is)
{
foreach ($this->mirrors as $k => $stream) {
if ($is === $stream) {
unset($this->mirrors[$k]);
}
}
}
/**
* Move the internal read pointer to $byteOffset in the stream.
*
* @param int $byteOffset
*
* @return bool
*/
public function setReadPointer($byteOffset)
{
if ($byteOffset > $this->arraySize) {
$byteOffset = $this->arraySize;
} elseif ($byteOffset < 0) {
$byteOffset = 0;
}
$this->offset = $byteOffset;
}
/**
* Flush the contents of the stream (empty it) and set the internal pointer
* to the beginning.
*/
public function flushBuffers()
{
$this->offset = 0;
$this->array = [];
$this->arraySize = 0;
foreach ($this->mirrors as $stream) {
$stream->flushBuffers();
}
}
}

View File

@ -1,214 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Allows reading and writing of bytes to and from a file.
*
* @author Chris Corbyn
*/
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream implements Swift_FileStream
{
/** The internal pointer offset */
private $offset = 0;
/** The path to the file */
private $path;
/** The mode this file is opened in for writing */
private $mode;
/** A lazy-loaded resource handle for reading the file */
private $reader;
/** A lazy-loaded resource handle for writing the file */
private $writer;
/** If stream is seekable true/false, or null if not known */
private $seekable = null;
/**
* Create a new FileByteStream for $path.
*
* @param string $path
* @param bool $writable if true
*/
public function __construct($path, $writable = false)
{
if (empty($path)) {
throw new Swift_IoException('The path cannot be empty');
}
$this->path = $path;
$this->mode = $writable ? 'w+b' : 'rb';
}
/**
* Get the complete path to the file.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Reads $length bytes from the stream into a string and moves the pointer
* through the stream by $length.
*
* If less bytes exist than are requested the
* remaining bytes are given instead. If no bytes are remaining at all, boolean
* false is returned.
*
* @param int $length
*
* @return string|bool
*
* @throws Swift_IoException
*/
public function read($length)
{
$fp = $this->getReadHandle();
if (!feof($fp)) {
$bytes = fread($fp, $length);
$this->offset = ftell($fp);
// If we read one byte after reaching the end of the file
// feof() will return false and an empty string is returned
if ((false === $bytes || '' === $bytes) && feof($fp)) {
$this->resetReadHandle();
return false;
}
return $bytes;
}
$this->resetReadHandle();
return false;
}
/**
* Move the internal read pointer to $byteOffset in the stream.
*
* @param int $byteOffset
*
* @return bool
*/
public function setReadPointer($byteOffset)
{
if (isset($this->reader)) {
$this->seekReadStreamToPosition($byteOffset);
}
$this->offset = $byteOffset;
}
/** Just write the bytes to the file */
protected function doCommit($bytes)
{
fwrite($this->getWriteHandle(), $bytes);
$this->resetReadHandle();
}
/** Not used */
protected function flush()
{
}
/** Get the resource for reading */
private function getReadHandle()
{
if (!isset($this->reader)) {
$pointer = @fopen($this->path, 'rb');
if (!$pointer) {
throw new Swift_IoException('Unable to open file for reading ['.$this->path.']');
}
$this->reader = $pointer;
if (0 != $this->offset) {
$this->getReadStreamSeekableStatus();
$this->seekReadStreamToPosition($this->offset);
}
}
return $this->reader;
}
/** Get the resource for writing */
private function getWriteHandle()
{
if (!isset($this->writer)) {
if (!$this->writer = fopen($this->path, $this->mode)) {
throw new Swift_IoException('Unable to open file for writing ['.$this->path.']');
}
}
return $this->writer;
}
/** Force a reload of the resource for reading */
private function resetReadHandle()
{
if (isset($this->reader)) {
fclose($this->reader);
$this->reader = null;
}
}
/** Check if ReadOnly Stream is seekable */
private function getReadStreamSeekableStatus()
{
$metas = stream_get_meta_data($this->reader);
$this->seekable = $metas['seekable'];
}
/** Streams in a readOnly stream ensuring copy if needed */
private function seekReadStreamToPosition($offset)
{
if (null === $this->seekable) {
$this->getReadStreamSeekableStatus();
}
if (false === $this->seekable) {
$currentPos = ftell($this->reader);
if ($currentPos < $offset) {
$toDiscard = $offset - $currentPos;
fread($this->reader, $toDiscard);
return;
}
$this->copyReadStream();
}
fseek($this->reader, $offset, SEEK_SET);
}
/** Copy a readOnly Stream to ensure seekability */
private function copyReadStream()
{
if ($tmpFile = fopen('php://temp/maxmemory:4096', 'w+b')) {
/* We have opened a php:// Stream Should work without problem */
} elseif (\function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir()) && ($tmpFile = tmpfile())) {
/* We have opened a tmpfile */
} else {
throw new Swift_IoException('Unable to copy the file to make it seekable, sys_temp_dir is not writable, php://memory not available');
}
$currentPos = ftell($this->reader);
fclose($this->reader);
$source = fopen($this->path, 'rb');
if (!$source) {
throw new Swift_IoException('Unable to open file for copying ['.$this->path.']');
}
fseek($tmpFile, 0, SEEK_SET);
while (!feof($source)) {
fwrite($tmpFile, fread($source, 4096));
}
fseek($tmpFile, $currentPos, SEEK_SET);
fclose($source);
$this->reader = $tmpFile;
}
}

View File

@ -1,52 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @author Romain-Geissler
*/
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream
{
public function __construct()
{
$filePath = tempnam(sys_get_temp_dir(), 'FileByteStream');
if (false === $filePath) {
throw new Swift_IoException('Failed to retrieve temporary file name.');
}
parent::__construct($filePath, true);
}
public function getContent()
{
if (false === ($content = file_get_contents($this->getPath()))) {
throw new Swift_IoException('Failed to get temporary file content.');
}
return $content;
}
public function __destruct()
{
if (file_exists($this->getPath())) {
@unlink($this->getPath());
}
}
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
}

View File

@ -1,67 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Analyzes characters for a specific character set.
*
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
interface Swift_CharacterReader
{
const MAP_TYPE_INVALID = 0x01;
const MAP_TYPE_FIXED_LEN = 0x02;
const MAP_TYPE_POSITIONS = 0x03;
/**
* Returns the complete character map.
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
*
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars);
/**
* Returns the mapType, see constants.
*
* @return int
*/
public function getMapType();
/**
* Returns an integer which specifies how many more bytes to read.
*
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
*
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
*
* @param int[] $bytes
* @param int $size
*
* @return int
*/
public function validateByteSequence($bytes, $size);
/**
* Returns the number of bytes which should be read to start each character.
*
* For fixed width character sets this should be the number of octets-per-character.
* For multibyte character sets this will probably be 1.
*
* @return int
*/
public function getInitialByteSize();
}

View File

@ -1,97 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Provides fixed-width byte sizes for reading fixed-width character sets.
*
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
class Swift_CharacterReader_GenericFixedWidthReader implements Swift_CharacterReader
{
/**
* The number of bytes in a single character.
*
* @var int
*/
private $width;
/**
* Creates a new GenericFixedWidthReader using $width bytes per character.
*
* @param int $width
*/
public function __construct($width)
{
$this->width = $width;
}
/**
* Returns the complete character map.
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
*
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
$strlen = \strlen($string);
// % and / are CPU intensive, so, maybe find a better way
$ignored = $strlen % $this->width;
$ignoredChars = $ignored ? substr($string, -$ignored) : '';
$currentMap = $this->width;
return ($strlen - $ignored) / $this->width;
}
/**
* Returns the mapType.
*
* @return int
*/
public function getMapType()
{
return self::MAP_TYPE_FIXED_LEN;
}
/**
* Returns an integer which specifies how many more bytes to read.
*
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
*
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
*
* @param string $bytes
* @param int $size
*
* @return int
*/
public function validateByteSequence($bytes, $size)
{
$needed = $this->width - $size;
return $needed > -1 ? $needed : -1;
}
/**
* Returns the number of bytes which should be read to start each character.
*
* @return int
*/
public function getInitialByteSize()
{
return $this->width;
}
}

View File

@ -1,84 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Analyzes US-ASCII characters.
*
* @author Chris Corbyn
*/
class Swift_CharacterReader_UsAsciiReader implements Swift_CharacterReader
{
/**
* Returns the complete character map.
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param string $ignoredChars
*
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
$strlen = \strlen($string);
$ignoredChars = '';
for ($i = 0; $i < $strlen; ++$i) {
if ($string[$i] > "\x07F") {
// Invalid char
$currentMap[$i + $startOffset] = $string[$i];
}
}
return $strlen;
}
/**
* Returns mapType.
*
* @return int mapType
*/
public function getMapType()
{
return self::MAP_TYPE_INVALID;
}
/**
* Returns an integer which specifies how many more bytes to read.
*
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
*
* @param string $bytes
* @param int $size
*
* @return int
*/
public function validateByteSequence($bytes, $size)
{
$byte = reset($bytes);
if (1 == \count($bytes) && $byte >= 0x00 && $byte <= 0x7F) {
return 0;
}
return -1;
}
/**
* Returns the number of bytes which should be read to start each character.
*
* @return int
*/
public function getInitialByteSize()
{
return 1;
}
}

View File

@ -1,176 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Analyzes UTF-8 characters.
*
* @author Chris Corbyn
* @author Xavier De Cock <xdecock@gmail.com>
*/
class Swift_CharacterReader_Utf8Reader implements Swift_CharacterReader
{
/** Pre-computed for optimization */
private static $length_map = [
// N=0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x0N
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x1N
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x2N
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x3N
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x4N
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x5N
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x6N
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x7N
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x8N
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x9N
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xAN
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xBN
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xCN
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xDN
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xEN
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // 0xFN
];
private static $s_length_map = [
"\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1,
"\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1,
"\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1,
"\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1,
"\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1,
"\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1,
"\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1,
"\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1,
"\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1,
"\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1,
"\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1,
"\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1,
"\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1,
"\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1,
"\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1,
"\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1,
"\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0,
"\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0,
"\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0,
"\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0,
"\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0,
"\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0,
"\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0,
"\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0,
"\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2,
"\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2,
"\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2,
"\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2,
"\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3,
"\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3,
"\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4,
"\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0,
];
/**
* Returns the complete character map.
*
* @param string $string
* @param int $startOffset
* @param array $currentMap
* @param mixed $ignoredChars
*
* @return int
*/
public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
{
if (!isset($currentMap['i']) || !isset($currentMap['p'])) {
$currentMap['p'] = $currentMap['i'] = [];
}
$strlen = \strlen($string);
$charPos = \count($currentMap['p']);
$foundChars = 0;
$invalid = false;
for ($i = 0; $i < $strlen; ++$i) {
$char = $string[$i];
$size = self::$s_length_map[$char];
if (0 == $size) {
/* char is invalid, we must wait for a resync */
$invalid = true;
continue;
} else {
if (true === $invalid) {
/* We mark the chars as invalid and start a new char */
$currentMap['p'][$charPos + $foundChars] = $startOffset + $i;
$currentMap['i'][$charPos + $foundChars] = true;
++$foundChars;
$invalid = false;
}
if (($i + $size) > $strlen) {
$ignoredChars = substr($string, $i);
break;
}
for ($j = 1; $j < $size; ++$j) {
$char = $string[$i + $j];
if ($char > "\x7F" && $char < "\xC0") {
// Valid - continue parsing
} else {
/* char is invalid, we must wait for a resync */
$invalid = true;
continue 2;
}
}
/* Ok we got a complete char here */
$currentMap['p'][$charPos + $foundChars] = $startOffset + $i + $size;
$i += $j - 1;
++$foundChars;
}
}
return $foundChars;
}
/**
* Returns mapType.
*
* @return int mapType
*/
public function getMapType()
{
return self::MAP_TYPE_POSITIONS;
}
/**
* Returns an integer which specifies how many more bytes to read.
*
* A positive integer indicates the number of more bytes to fetch before invoking
* this method again.
* A value of zero means this is already a valid character.
* A value of -1 means this cannot possibly be a valid character.
*
* @param string $bytes
* @param int $size
*
* @return int
*/
public function validateByteSequence($bytes, $size)
{
if ($size < 1) {
return -1;
}
$needed = self::$length_map[$bytes[0]] - $size;
return $needed > -1 ? $needed : -1;
}
/**
* Returns the number of bytes which should be read to start each character.
*
* @return int
*/
public function getInitialByteSize()
{
return 1;
}
}

View File

@ -1,26 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A factory for creating CharacterReaders.
*
* @author Chris Corbyn
*/
interface Swift_CharacterReaderFactory
{
/**
* Returns a CharacterReader suitable for the charset applied.
*
* @param string $charset
*
* @return Swift_CharacterReader
*/
public function getReaderFor($charset);
}

View File

@ -1,124 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Standard factory for creating CharacterReaders.
*
* @author Chris Corbyn
*/
class Swift_CharacterReaderFactory_SimpleCharacterReaderFactory implements Swift_CharacterReaderFactory
{
/**
* A map of charset patterns to their implementation classes.
*
* @var array
*/
private static $map = [];
/**
* Factories which have already been loaded.
*
* @var Swift_CharacterReaderFactory[]
*/
private static $loaded = [];
/**
* Creates a new CharacterReaderFactory.
*/
public function __construct()
{
$this->init();
}
public function __wakeup()
{
$this->init();
}
public function init()
{
if (\count(self::$map) > 0) {
return;
}
$prefix = 'Swift_CharacterReader_';
$singleByte = [
'class' => $prefix.'GenericFixedWidthReader',
'constructor' => [1],
];
$doubleByte = [
'class' => $prefix.'GenericFixedWidthReader',
'constructor' => [2],
];
$fourBytes = [
'class' => $prefix.'GenericFixedWidthReader',
'constructor' => [4],
];
// Utf-8
self::$map['utf-?8'] = [
'class' => $prefix.'Utf8Reader',
'constructor' => [],
];
//7-8 bit charsets
self::$map['(us-)?ascii'] = $singleByte;
self::$map['(iso|iec)-?8859-?[0-9]+'] = $singleByte;
self::$map['windows-?125[0-9]'] = $singleByte;
self::$map['cp-?[0-9]+'] = $singleByte;
self::$map['ansi'] = $singleByte;
self::$map['macintosh'] = $singleByte;
self::$map['koi-?7'] = $singleByte;
self::$map['koi-?8-?.+'] = $singleByte;
self::$map['mik'] = $singleByte;
self::$map['(cork|t1)'] = $singleByte;
self::$map['v?iscii'] = $singleByte;
//16 bits
self::$map['(ucs-?2|utf-?16)'] = $doubleByte;
//32 bits
self::$map['(ucs-?4|utf-?32)'] = $fourBytes;
// Fallback
self::$map['.*'] = $singleByte;
}
/**
* Returns a CharacterReader suitable for the charset applied.
*
* @param string $charset
*
* @return Swift_CharacterReader
*/
public function getReaderFor($charset)
{
$charset = strtolower(trim($charset ?? ''));
foreach (self::$map as $pattern => $spec) {
$re = '/^'.$pattern.'$/D';
if (preg_match($re, $charset)) {
if (!\array_key_exists($pattern, self::$loaded)) {
$reflector = new ReflectionClass($spec['class']);
if ($reflector->getConstructor()) {
$reader = $reflector->newInstanceArgs($spec['constructor']);
} else {
$reader = $reflector->newInstance();
}
self::$loaded[$pattern] = $reader;
}
return self::$loaded[$pattern];
}
}
}
}

View File

@ -1,87 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An abstract means of reading and writing data in terms of characters as opposed
* to bytes.
*
* Classes implementing this interface may use a subsystem which requires less
* memory than working with large strings of data.
*
* @author Chris Corbyn
*/
interface Swift_CharacterStream
{
/**
* Set the character set used in this CharacterStream.
*
* @param string $charset
*/
public function setCharacterSet($charset);
/**
* Set the CharacterReaderFactory for multi charset support.
*/
public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory);
/**
* Overwrite this character stream using the byte sequence in the byte stream.
*
* @param Swift_OutputByteStream $os output stream to read from
*/
public function importByteStream(Swift_OutputByteStream $os);
/**
* Import a string a bytes into this CharacterStream, overwriting any existing
* data in the stream.
*
* @param string $string
*/
public function importString($string);
/**
* Read $length characters from the stream and move the internal pointer
* $length further into the stream.
*
* @param int $length
*
* @return string
*/
public function read($length);
/**
* Read $length characters from the stream and return a 1-dimensional array
* containing there octet values.
*
* @param int $length
*
* @return int[]
*/
public function readBytes($length);
/**
* Write $chars to the end of the stream.
*
* @param string $chars
*/
public function write($chars);
/**
* Move the internal pointer to $charOffset in the stream.
*
* @param int $charOffset
*/
public function setPointer($charOffset);
/**
* Empty the stream and reset the internal pointer.
*/
public function flushContents();
}

View File

@ -1,291 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A CharacterStream implementation which stores characters in an internal array.
*
* @author Chris Corbyn
*/
class Swift_CharacterStream_ArrayCharacterStream implements Swift_CharacterStream
{
/** A map of byte values and their respective characters */
private static $charMap;
/** A map of characters and their derivative byte values */
private static $byteMap;
/** The char reader (lazy-loaded) for the current charset */
private $charReader;
/** A factory for creating CharacterReader instances */
private $charReaderFactory;
/** The character set this stream is using */
private $charset;
/** Array of characters */
private $array = [];
/** Size of the array of character */
private $array_size = [];
/** The current character offset in the stream */
private $offset = 0;
/**
* Create a new CharacterStream with the given $chars, if set.
*
* @param Swift_CharacterReaderFactory $factory for loading validators
* @param string $charset used in the stream
*/
public function __construct(Swift_CharacterReaderFactory $factory, $charset)
{
self::initializeMaps();
$this->setCharacterReaderFactory($factory);
$this->setCharacterSet($charset);
}
/**
* Set the character set used in this CharacterStream.
*
* @param string $charset
*/
public function setCharacterSet($charset)
{
$this->charset = $charset;
$this->charReader = null;
}
/**
* Set the CharacterReaderFactory for multi charset support.
*/
public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
{
$this->charReaderFactory = $factory;
}
/**
* Overwrite this character stream using the byte sequence in the byte stream.
*
* @param Swift_OutputByteStream $os output stream to read from
*/
public function importByteStream(Swift_OutputByteStream $os)
{
if (!isset($this->charReader)) {
$this->charReader = $this->charReaderFactory
->getReaderFor($this->charset);
}
$startLength = $this->charReader->getInitialByteSize();
while (false !== $bytes = $os->read($startLength)) {
$c = [];
for ($i = 0, $len = \strlen($bytes); $i < $len; ++$i) {
$c[] = self::$byteMap[$bytes[$i]];
}
$size = \count($c);
$need = $this->charReader
->validateByteSequence($c, $size);
if ($need > 0 &&
false !== $bytes = $os->read($need)) {
for ($i = 0, $len = \strlen($bytes); $i < $len; ++$i) {
$c[] = self::$byteMap[$bytes[$i]];
}
}
$this->array[] = $c;
++$this->array_size;
}
}
/**
* Import a string a bytes into this CharacterStream, overwriting any existing
* data in the stream.
*
* @param string $string
*/
public function importString($string)
{
$this->flushContents();
$this->write($string);
}
/**
* Read $length characters from the stream and move the internal pointer
* $length further into the stream.
*
* @param int $length
*
* @return string
*/
public function read($length)
{
if ($this->offset == $this->array_size) {
return false;
}
// Don't use array slice
$arrays = [];
$end = $length + $this->offset;
for ($i = $this->offset; $i < $end; ++$i) {
if (!isset($this->array[$i])) {
break;
}
$arrays[] = $this->array[$i];
}
$this->offset += $i - $this->offset; // Limit function calls
$chars = false;
foreach ($arrays as $array) {
$chars .= implode('', array_map('chr', $array));
}
return $chars;
}
/**
* Read $length characters from the stream and return a 1-dimensional array
* containing there octet values.
*
* @param int $length
*
* @return int[]
*/
public function readBytes($length)
{
if ($this->offset == $this->array_size) {
return false;
}
$arrays = [];
$end = $length + $this->offset;
for ($i = $this->offset; $i < $end; ++$i) {
if (!isset($this->array[$i])) {
break;
}
$arrays[] = $this->array[$i];
}
$this->offset += ($i - $this->offset); // Limit function calls
return array_merge(...$arrays);
}
/**
* Write $chars to the end of the stream.
*
* @param string $chars
*/
public function write($chars)
{
if (!isset($this->charReader)) {
$this->charReader = $this->charReaderFactory->getReaderFor(
$this->charset);
}
$startLength = $this->charReader->getInitialByteSize();
$fp = fopen('php://memory', 'w+b');
fwrite($fp, $chars);
unset($chars);
fseek($fp, 0, SEEK_SET);
$buffer = [0];
$buf_pos = 1;
$buf_len = 1;
$has_datas = true;
do {
$bytes = [];
// Buffer Filing
if ($buf_len - $buf_pos < $startLength) {
$buf = array_splice($buffer, $buf_pos);
$new = $this->reloadBuffer($fp, 100);
if ($new) {
$buffer = array_merge($buf, $new);
$buf_len = \count($buffer);
$buf_pos = 0;
} else {
$has_datas = false;
}
}
if ($buf_len - $buf_pos > 0) {
$size = 0;
for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i) {
++$size;
$bytes[] = $buffer[$buf_pos++];
}
$need = $this->charReader->validateByteSequence(
$bytes, $size);
if ($need > 0) {
if ($buf_len - $buf_pos < $need) {
$new = $this->reloadBuffer($fp, $need);
if ($new) {
$buffer = array_merge($buffer, $new);
$buf_len = \count($buffer);
}
}
for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i) {
$bytes[] = $buffer[$buf_pos++];
}
}
$this->array[] = $bytes;
++$this->array_size;
}
} while ($has_datas);
fclose($fp);
}
/**
* Move the internal pointer to $charOffset in the stream.
*
* @param int $charOffset
*/
public function setPointer($charOffset)
{
if ($charOffset > $this->array_size) {
$charOffset = $this->array_size;
} elseif ($charOffset < 0) {
$charOffset = 0;
}
$this->offset = $charOffset;
}
/**
* Empty the stream and reset the internal pointer.
*/
public function flushContents()
{
$this->offset = 0;
$this->array = [];
$this->array_size = 0;
}
private function reloadBuffer($fp, $len)
{
if (!feof($fp) && false !== ($bytes = fread($fp, $len))) {
$buf = [];
for ($i = 0, $len = \strlen($bytes); $i < $len; ++$i) {
$buf[] = self::$byteMap[$bytes[$i]];
}
return $buf;
}
return false;
}
private static function initializeMaps()
{
if (!isset(self::$charMap)) {
self::$charMap = [];
for ($byte = 0; $byte < 256; ++$byte) {
self::$charMap[$byte] = \chr($byte);
}
self::$byteMap = array_flip(self::$charMap);
}
}
}

View File

@ -1,262 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A CharacterStream implementation which stores characters in an internal array.
*
* @author Xavier De Cock <xdecock@gmail.com>
*/
class Swift_CharacterStream_NgCharacterStream implements Swift_CharacterStream
{
/**
* The char reader (lazy-loaded) for the current charset.
*
* @var Swift_CharacterReader
*/
private $charReader;
/**
* A factory for creating CharacterReader instances.
*
* @var Swift_CharacterReaderFactory
*/
private $charReaderFactory;
/**
* The character set this stream is using.
*
* @var string
*/
private $charset;
/**
* The data's stored as-is.
*
* @var string
*/
private $datas = '';
/**
* Number of bytes in the stream.
*
* @var int
*/
private $datasSize = 0;
/**
* Map.
*
* @var mixed
*/
private $map;
/**
* Map Type.
*
* @var int
*/
private $mapType = 0;
/**
* Number of characters in the stream.
*
* @var int
*/
private $charCount = 0;
/**
* Position in the stream.
*
* @var int
*/
private $currentPos = 0;
/**
* Constructor.
*
* @param string $charset
*/
public function __construct(Swift_CharacterReaderFactory $factory, $charset)
{
$this->setCharacterReaderFactory($factory);
$this->setCharacterSet($charset);
}
/* -- Changing parameters of the stream -- */
/**
* Set the character set used in this CharacterStream.
*
* @param string $charset
*/
public function setCharacterSet($charset)
{
$this->charset = $charset;
$this->charReader = null;
$this->mapType = 0;
}
/**
* Set the CharacterReaderFactory for multi charset support.
*/
public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
{
$this->charReaderFactory = $factory;
}
/**
* @see Swift_CharacterStream::flushContents()
*/
public function flushContents()
{
$this->datas = null;
$this->map = null;
$this->charCount = 0;
$this->currentPos = 0;
$this->datasSize = 0;
}
/**
* @see Swift_CharacterStream::importByteStream()
*/
public function importByteStream(Swift_OutputByteStream $os)
{
$this->flushContents();
$blocks = 512;
$os->setReadPointer(0);
while (false !== ($read = $os->read($blocks))) {
$this->write($read);
}
}
/**
* @see Swift_CharacterStream::importString()
*
* @param string $string
*/
public function importString($string)
{
$this->flushContents();
$this->write($string);
}
/**
* @see Swift_CharacterStream::read()
*
* @param int $length
*
* @return string
*/
public function read($length)
{
if ($this->currentPos >= $this->charCount) {
return false;
}
$ret = false;
$length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length;
switch ($this->mapType) {
case Swift_CharacterReader::MAP_TYPE_FIXED_LEN:
$len = $length * $this->map;
$ret = substr($this->datas,
$this->currentPos * $this->map,
$len);
$this->currentPos += $length;
break;
case Swift_CharacterReader::MAP_TYPE_INVALID:
$ret = '';
for (; $this->currentPos < $length; ++$this->currentPos) {
if (isset($this->map[$this->currentPos])) {
$ret .= '?';
} else {
$ret .= $this->datas[$this->currentPos];
}
}
break;
case Swift_CharacterReader::MAP_TYPE_POSITIONS:
$end = $this->currentPos + $length;
$end = $end > $this->charCount ? $this->charCount : $end;
$ret = '';
$start = 0;
if ($this->currentPos > 0) {
$start = $this->map['p'][$this->currentPos - 1];
}
$to = $start;
for (; $this->currentPos < $end; ++$this->currentPos) {
if (isset($this->map['i'][$this->currentPos])) {
$ret .= substr($this->datas, $start, $to - $start).'?';
$start = $this->map['p'][$this->currentPos];
} else {
$to = $this->map['p'][$this->currentPos];
}
}
$ret .= substr($this->datas, $start, $to - $start);
break;
}
return $ret;
}
/**
* @see Swift_CharacterStream::readBytes()
*
* @param int $length
*
* @return int[]
*/
public function readBytes($length)
{
$read = $this->read($length);
if (false !== $read) {
$ret = array_map('ord', str_split($read, 1));
return $ret;
}
return false;
}
/**
* @see Swift_CharacterStream::setPointer()
*
* @param int $charOffset
*/
public function setPointer($charOffset)
{
if ($this->charCount < $charOffset) {
$charOffset = $this->charCount;
}
$this->currentPos = $charOffset;
}
/**
* @see Swift_CharacterStream::write()
*
* @param string $chars
*/
public function write($chars)
{
if (!isset($this->charReader)) {
$this->charReader = $this->charReaderFactory->getReaderFor(
$this->charset);
$this->map = [];
$this->mapType = $this->charReader->getMapType();
}
$ignored = '';
$this->datas .= $chars;
$this->charCount += $this->charReader->getCharPositions(substr($this->datas, $this->datasSize), $this->datasSize, $this->map, $ignored);
if (false !== $ignored) {
$this->datasSize = \strlen($this->datas) - \strlen($ignored);
} else {
$this->datasSize = \strlen($this->datas);
}
}
}

View File

@ -1,63 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Base class for Spools (implements time and message limits).
*
* @author Fabien Potencier
*/
abstract class Swift_ConfigurableSpool implements Swift_Spool
{
/** The maximum number of messages to send per flush */
private $message_limit;
/** The time limit per flush */
private $time_limit;
/**
* Sets the maximum number of messages to send per flush.
*
* @param int $limit
*/
public function setMessageLimit($limit)
{
$this->message_limit = (int) $limit;
}
/**
* Gets the maximum number of messages to send per flush.
*
* @return int The limit
*/
public function getMessageLimit()
{
return $this->message_limit;
}
/**
* Sets the time limit (in seconds) per flush.
*
* @param int $limit The limit
*/
public function setTimeLimit($limit)
{
$this->time_limit = (int) $limit;
}
/**
* Gets the time limit (in seconds) per flush.
*
* @return int The limit
*/
public function getTimeLimit()
{
return $this->time_limit;
}
}

View File

@ -1,387 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Dependency Injection container.
*
* @author Chris Corbyn
*/
class Swift_DependencyContainer
{
/** Constant for literal value types */
const TYPE_VALUE = 0x00001;
/** Constant for new instance types */
const TYPE_INSTANCE = 0x00010;
/** Constant for shared instance types */
const TYPE_SHARED = 0x00100;
/** Constant for aliases */
const TYPE_ALIAS = 0x01000;
/** Constant for arrays */
const TYPE_ARRAY = 0x10000;
/** Singleton instance */
private static $instance = null;
/** The data container */
private $store = [];
/** The current endpoint in the data container */
private $endPoint;
/**
* Constructor should not be used.
*
* Use {@link getInstance()} instead.
*/
public function __construct()
{
}
/**
* Returns a singleton of the DependencyContainer.
*
* @return self
*/
public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* List the names of all items stored in the Container.
*
* @return array
*/
public function listItems()
{
return array_keys($this->store);
}
/**
* Test if an item is registered in this container with the given name.
*
* @see register()
*
* @param string $itemName
*
* @return bool
*/
public function has($itemName)
{
return \array_key_exists($itemName, $this->store)
&& isset($this->store[$itemName]['lookupType']);
}
/**
* Lookup the item with the given $itemName.
*
* @see register()
*
* @param string $itemName
*
* @return mixed
*
* @throws Swift_DependencyException If the dependency is not found
*/
public function lookup($itemName)
{
if (!$this->has($itemName)) {
throw new Swift_DependencyException('Cannot lookup dependency "'.$itemName.'" since it is not registered.');
}
switch ($this->store[$itemName]['lookupType']) {
case self::TYPE_ALIAS:
return $this->createAlias($itemName);
case self::TYPE_VALUE:
return $this->getValue($itemName);
case self::TYPE_INSTANCE:
return $this->createNewInstance($itemName);
case self::TYPE_SHARED:
return $this->createSharedInstance($itemName);
case self::TYPE_ARRAY:
return $this->createDependenciesFor($itemName);
}
}
/**
* Create an array of arguments passed to the constructor of $itemName.
*
* @param string $itemName
*
* @return array
*/
public function createDependenciesFor($itemName)
{
$args = [];
if (isset($this->store[$itemName]['args'])) {
$args = $this->resolveArgs($this->store[$itemName]['args']);
}
return $args;
}
/**
* Register a new dependency with $itemName.
*
* This method returns the current DependencyContainer instance because it
* requires the use of the fluid interface to set the specific details for the
* dependency.
*
* @see asNewInstanceOf(), asSharedInstanceOf(), asValue()
*
* @param string $itemName
*
* @return $this
*/
public function register($itemName)
{
$this->store[$itemName] = [];
$this->endPoint = &$this->store[$itemName];
return $this;
}
/**
* Specify the previously registered item as a literal value.
*
* {@link register()} must be called before this will work.
*
* @param mixed $value
*
* @return $this
*/
public function asValue($value)
{
$endPoint = &$this->getEndPoint();
$endPoint['lookupType'] = self::TYPE_VALUE;
$endPoint['value'] = $value;
return $this;
}
/**
* Specify the previously registered item as an alias of another item.
*
* @param string $lookup
*
* @return $this
*/
public function asAliasOf($lookup)
{
$endPoint = &$this->getEndPoint();
$endPoint['lookupType'] = self::TYPE_ALIAS;
$endPoint['ref'] = $lookup;
return $this;
}
/**
* Specify the previously registered item as a new instance of $className.
*
* {@link register()} must be called before this will work.
* Any arguments can be set with {@link withDependencies()},
* {@link addConstructorValue()} or {@link addConstructorLookup()}.
*
* @see withDependencies(), addConstructorValue(), addConstructorLookup()
*
* @param string $className
*
* @return $this
*/
public function asNewInstanceOf($className)
{
$endPoint = &$this->getEndPoint();
$endPoint['lookupType'] = self::TYPE_INSTANCE;
$endPoint['className'] = $className;
return $this;
}
/**
* Specify the previously registered item as a shared instance of $className.
*
* {@link register()} must be called before this will work.
*
* @param string $className
*
* @return $this
*/
public function asSharedInstanceOf($className)
{
$endPoint = &$this->getEndPoint();
$endPoint['lookupType'] = self::TYPE_SHARED;
$endPoint['className'] = $className;
return $this;
}
/**
* Specify the previously registered item as array of dependencies.
*
* {@link register()} must be called before this will work.
*
* @return $this
*/
public function asArray()
{
$endPoint = &$this->getEndPoint();
$endPoint['lookupType'] = self::TYPE_ARRAY;
return $this;
}
/**
* Specify a list of injected dependencies for the previously registered item.
*
* This method takes an array of lookup names.
*
* @see addConstructorValue(), addConstructorLookup()
*
* @return $this
*/
public function withDependencies(array $lookups)
{
$endPoint = &$this->getEndPoint();
$endPoint['args'] = [];
foreach ($lookups as $lookup) {
$this->addConstructorLookup($lookup);
}
return $this;
}
/**
* Specify a literal (non looked up) value for the constructor of the
* previously registered item.
*
* @see withDependencies(), addConstructorLookup()
*
* @param mixed $value
*
* @return $this
*/
public function addConstructorValue($value)
{
$endPoint = &$this->getEndPoint();
if (!isset($endPoint['args'])) {
$endPoint['args'] = [];
}
$endPoint['args'][] = ['type' => 'value', 'item' => $value];
return $this;
}
/**
* Specify a dependency lookup for the constructor of the previously
* registered item.
*
* @see withDependencies(), addConstructorValue()
*
* @param string $lookup
*
* @return $this
*/
public function addConstructorLookup($lookup)
{
$endPoint = &$this->getEndPoint();
if (!isset($this->endPoint['args'])) {
$endPoint['args'] = [];
}
$endPoint['args'][] = ['type' => 'lookup', 'item' => $lookup];
return $this;
}
/** Get the literal value with $itemName */
private function getValue($itemName)
{
return $this->store[$itemName]['value'];
}
/** Resolve an alias to another item */
private function createAlias($itemName)
{
return $this->lookup($this->store[$itemName]['ref']);
}
/** Create a fresh instance of $itemName */
private function createNewInstance($itemName)
{
$reflector = new ReflectionClass($this->store[$itemName]['className']);
if ($reflector->getConstructor()) {
return $reflector->newInstanceArgs(
$this->createDependenciesFor($itemName)
);
}
return $reflector->newInstance();
}
/** Create and register a shared instance of $itemName */
private function createSharedInstance($itemName)
{
if (!isset($this->store[$itemName]['instance'])) {
$this->store[$itemName]['instance'] = $this->createNewInstance($itemName);
}
return $this->store[$itemName]['instance'];
}
/** Get the current endpoint in the store */
private function &getEndPoint()
{
if (!isset($this->endPoint)) {
throw new BadMethodCallException('Component must first be registered by calling register()');
}
return $this->endPoint;
}
/** Get an argument list with dependencies resolved */
private function resolveArgs(array $args)
{
$resolved = [];
foreach ($args as $argDefinition) {
switch ($argDefinition['type']) {
case 'lookup':
$resolved[] = $this->lookupRecursive($argDefinition['item']);
break;
case 'value':
$resolved[] = $argDefinition['item'];
break;
}
}
return $resolved;
}
/** Resolve a single dependency with an collections */
private function lookupRecursive($item)
{
if (\is_array($item)) {
$collection = [];
foreach ($item as $k => $v) {
$collection[$k] = $this->lookupRecursive($v);
}
return $collection;
}
return $this->lookup($item);
}
}

View File

@ -1,27 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* DependencyException gets thrown when a requested dependency is missing.
*
* @author Chris Corbyn
*/
class Swift_DependencyException extends Swift_SwiftException
{
/**
* Create a new DependencyException with $message.
*
* @param string $message
*/
public function __construct($message)
{
parent::__construct($message);
}
}

View File

@ -1,53 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An embedded file, in a multipart message.
*
* @author Chris Corbyn
*/
class Swift_EmbeddedFile extends Swift_Mime_EmbeddedFile
{
/**
* Create a new EmbeddedFile.
*
* Details may be optionally provided to the constructor.
*
* @param string|Swift_OutputByteStream $data
* @param string $filename
* @param string $contentType
*/
public function __construct($data = null, $filename = null, $contentType = null)
{
\call_user_func_array(
[$this, 'Swift_Mime_EmbeddedFile::__construct'],
Swift_DependencyContainer::getInstance()
->createDependenciesFor('mime.embeddedfile')
);
$this->setBody($data);
$this->setFilename($filename);
if ($contentType) {
$this->setContentType($contentType);
}
}
/**
* Create a new EmbeddedFile from a filesystem path.
*
* @param string $path
*
* @return Swift_Mime_EmbeddedFile
*/
public static function fromPath($path)
{
return (new self())->setFile(new Swift_ByteStream_FileByteStream($path));
}
}

View File

@ -1,28 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Interface for all Encoder schemes.
*
* @author Chris Corbyn
*/
interface Swift_Encoder extends Swift_Mime_CharsetObserver
{
/**
* Encode a given string to produce an encoded string.
*
* @param string $string
* @param int $firstLineOffset if first line needs to be shorter
* @param int $maxLineLength - 0 indicates the default length for this encoding
*
* @return string
*/
public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0);
}

View File

@ -1,58 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles Base 64 Encoding in Swift Mailer.
*
* @author Chris Corbyn
*/
class Swift_Encoder_Base64Encoder implements Swift_Encoder
{
/**
* Takes an unencoded string and produces a Base64 encoded string from it.
*
* Base64 encoded strings have a maximum line length of 76 characters.
* If the first line needs to be shorter, indicate the difference with
* $firstLineOffset.
*
* @param string $string to encode
* @param int $firstLineOffset
* @param int $maxLineLength optional, 0 indicates the default of 76 bytes
*
* @return string
*/
public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
{
if (0 >= $maxLineLength || 76 < $maxLineLength) {
$maxLineLength = 76;
}
$encodedString = base64_encode($string ?? '');
$firstLine = '';
if (0 != $firstLineOffset) {
$firstLine = substr(
$encodedString, 0, $maxLineLength - $firstLineOffset
)."\r\n";
$encodedString = substr(
$encodedString, $maxLineLength - $firstLineOffset
);
}
return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n"));
}
/**
* Does nothing.
*/
public function charsetChanged($charset)
{
}
}

View File

@ -1,90 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles RFC 2231 specified Encoding in Swift Mailer.
*
* @author Chris Corbyn
*/
class Swift_Encoder_Rfc2231Encoder implements Swift_Encoder
{
/**
* A character stream to use when reading a string as characters instead of bytes.
*
* @var Swift_CharacterStream
*/
private $charStream;
/**
* Creates a new Rfc2231Encoder using the given character stream instance.
*/
public function __construct(Swift_CharacterStream $charStream)
{
$this->charStream = $charStream;
}
/**
* Takes an unencoded string and produces a string encoded according to
* RFC 2231 from it.
*
* @param string $string
* @param int $firstLineOffset
* @param int $maxLineLength optional, 0 indicates the default of 75 bytes
*
* @return string
*/
public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
{
$lines = [];
$lineCount = 0;
$lines[] = '';
$currentLine = &$lines[$lineCount++];
if (0 >= $maxLineLength) {
$maxLineLength = 75;
}
$this->charStream->flushContents();
$this->charStream->importString($string);
$thisLineLength = $maxLineLength - $firstLineOffset;
while (false !== $char = $this->charStream->read(4)) {
$encodedChar = rawurlencode($char);
if (0 != \strlen($currentLine)
&& \strlen($currentLine.$encodedChar) > $thisLineLength) {
$lines[] = '';
$currentLine = &$lines[$lineCount++];
$thisLineLength = $maxLineLength;
}
$currentLine .= $encodedChar;
}
return implode("\r\n", $lines);
}
/**
* Updates the charset used.
*
* @param string $charset
*/
public function charsetChanged($charset)
{
$this->charStream->setCharacterSet($charset);
}
/**
* Make a deep copy of object.
*/
public function __clone()
{
$this->charStream = clone $this->charStream;
}
}

View File

@ -1,64 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Generated when a command is sent over an SMTP connection.
*
* @author Chris Corbyn
*/
class Swift_Events_CommandEvent extends Swift_Events_EventObject
{
/**
* The command sent to the server.
*
* @var string
*/
private $command;
/**
* An array of codes which a successful response will contain.
*
* @var int[]
*/
private $successCodes = [];
/**
* Create a new CommandEvent for $source with $command.
*
* @param string $command
* @param array $successCodes
*/
public function __construct(Swift_Transport $source, $command, $successCodes = [])
{
parent::__construct($source);
$this->command = $command;
$this->successCodes = $successCodes;
}
/**
* Get the command which was sent to the server.
*
* @return string
*/
public function getCommand()
{
return $this->command;
}
/**
* Get the numeric response codes which indicate success for this command.
*
* @return int[]
*/
public function getSuccessCodes()
{
return $this->successCodes;
}
}

View File

@ -1,22 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Listens for Transports to send commands to the server.
*
* @author Chris Corbyn
*/
interface Swift_Events_CommandListener extends Swift_Events_EventListener
{
/**
* Invoked immediately following a command being sent.
*/
public function commandSent(Swift_Events_CommandEvent $evt);
}

View File

@ -1,38 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* The minimum interface for an Event.
*
* @author Chris Corbyn
*/
interface Swift_Events_Event
{
/**
* Get the source object of this event.
*
* @return object
*/
public function getSource();
/**
* Prevent this Event from bubbling any further up the stack.
*
* @param bool $cancel, optional
*/
public function cancelBubble($cancel = true);
/**
* Returns true if this Event will not bubble any further up the stack.
*
* @return bool
*/
public function bubbleCancelled();
}

View File

@ -1,70 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Interface for the EventDispatcher which handles the event dispatching layer.
*
* @author Chris Corbyn
*/
interface Swift_Events_EventDispatcher
{
/**
* Create a new SendEvent for $source and $message.
*
* @return Swift_Events_SendEvent
*/
public function createSendEvent(Swift_Transport $source, Swift_Mime_SimpleMessage $message);
/**
* Create a new CommandEvent for $source and $command.
*
* @param string $command That will be executed
* @param array $successCodes That are needed
*
* @return Swift_Events_CommandEvent
*/
public function createCommandEvent(Swift_Transport $source, $command, $successCodes = []);
/**
* Create a new ResponseEvent for $source and $response.
*
* @param string $response
* @param bool $valid If the response is valid
*
* @return Swift_Events_ResponseEvent
*/
public function createResponseEvent(Swift_Transport $source, $response, $valid);
/**
* Create a new TransportChangeEvent for $source.
*
* @return Swift_Events_TransportChangeEvent
*/
public function createTransportChangeEvent(Swift_Transport $source);
/**
* Create a new TransportExceptionEvent for $source.
*
* @return Swift_Events_TransportExceptionEvent
*/
public function createTransportExceptionEvent(Swift_Transport $source, Swift_TransportException $ex);
/**
* Bind an event listener to this dispatcher.
*/
public function bindEventListener(Swift_Events_EventListener $listener);
/**
* Dispatch the given Event to all suitable listeners.
*
* @param string $target method
*/
public function dispatchEvent(Swift_Events_EventObject $evt, $target);
}

View File

@ -1,18 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An identity interface which all EventListeners must extend.
*
* @author Chris Corbyn
*/
interface Swift_Events_EventListener
{
}

View File

@ -1,61 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A base Event which all Event classes inherit from.
*
* @author Chris Corbyn
*/
class Swift_Events_EventObject implements Swift_Events_Event
{
/** The source of this Event */
private $source;
/** The state of this Event (should it bubble up the stack?) */
private $bubbleCancelled = false;
/**
* Create a new EventObject originating at $source.
*
* @param object $source
*/
public function __construct($source)
{
$this->source = $source;
}
/**
* Get the source object of this event.
*
* @return object
*/
public function getSource()
{
return $this->source;
}
/**
* Prevent this Event from bubbling any further up the stack.
*/
public function cancelBubble($cancel = true)
{
$this->bubbleCancelled = $cancel;
}
/**
* Returns true if this Event will not bubble any further up the stack.
*
* @return bool
*/
public function bubbleCancelled()
{
return $this->bubbleCancelled;
}
}

View File

@ -1,64 +0,0 @@
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Generated when a response is received on a SMTP connection.
*
* @author Chris Corbyn
*/
class Swift_Events_ResponseEvent extends Swift_Events_EventObject
{
/**
* The overall result.
*
* @var bool
*/
private $valid;
/**
* The response received from the server.
*
* @var string
*/
private $response;
/**
* Create a new ResponseEvent for $source and $response.
*
* @param string $response
* @param bool $valid
*/
public function __construct(Swift_Transport $source, $response, $valid = false)
{
parent::__construct($source);
$this->response = $response;
$this->valid = $valid;
}
/**
* Get the response which was received from the server.
*
* @return string
*/
public function getResponse()
{
return $this->response;
}
/**
* Get the success status of this Event.
*
* @return bool
*/
public function isValid()
{
return $this->valid;
}
}

Some files were not shown because too many files have changed in this diff Show More