first commit
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
# v3.0.9
|
||||
## 06/08/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Disable password autocomplete in password field
|
||||
* Don't save empty string in password field [#134](https://github.com/getgrav/grav-plugin-email/issues/134)
|
||||
|
||||
# v3.0.8
|
||||
## 04/27/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Updated vendor library files
|
||||
* Use Grav's Parsedown class
|
||||
|
||||
# v3.0.7
|
||||
## 03/05/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Updated email validator library
|
||||
1. [](#bugfix)
|
||||
* Fixed `Invalid resource theme://` on CLI command `test-email` on Grav 1.6.21 and later versions [#128](https://github.com/getgrav/grav-plugin-email/issues/128)
|
||||
|
||||
# v3.0.6
|
||||
## 02/11/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Updated email validator library
|
||||
|
||||
# v3.0.5
|
||||
## 02/03/2020
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed a date in changelog (no other changes)
|
||||
|
||||
# v3.0.4
|
||||
## 01/17/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Added ZOHO configuration example
|
||||
* Updated SwiftMailer library for PHP 7.4 support
|
||||
|
||||
# v3.0.3
|
||||
## 08/16/2019
|
||||
|
||||
1. [](#new)
|
||||
* Support an array of multiple emails in `email:` form process
|
||||
* Allow form values in email templates
|
||||
1. [](#improved)
|
||||
* Added Twig blocks for `content` and `footer` in `email/base.html.twig` template
|
||||
* Updated `README.md` to reflect working setup for GMail
|
||||
|
||||
# v3.0.2
|
||||
## 05/09/2019
|
||||
|
||||
1. [](#new)
|
||||
* Requires Form Plugin v3.0.3
|
||||
* Added Russian translation [#113](https://github.com/getgrav/grav-plugin-email/pull/113)
|
||||
1. [](#bugfix)
|
||||
* Better fix for missing attachments when sending an email using a form [form#333](https://github.com/getgrav/grav-plugin-form/issues/333)
|
||||
|
||||
# v3.0.1
|
||||
## 04/15/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Put a `try/catch` around email attachments and log any errors rather than hard fail
|
||||
1. [](#bugfix)
|
||||
* Fixed missing attachments when sending an email using a form [form#333](https://github.com/getgrav/grav-plugin-form/issues/333)
|
||||
|
||||
# v3.0.0
|
||||
## 04/11/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added new `template:` to choose twig template option for email form processing
|
||||
* Moved `buildMessage()` and `parseAddressValue()` to Email object and made public
|
||||
* Refactored the `EmailUtils::sendEmail()` to take an array of params or the old param list
|
||||
* Switched to SwiftMailer v.6.1.3 (requires PHP7/Grav 1.6)
|
||||
* SwiftMailer 6.x compatibility fixes
|
||||
* Updated various translations
|
||||
* Added support for Email Queue with Scheduler support
|
||||
* Code cleanup, composer update
|
||||
* Added a new `clear-queue-failures` CLI command to flush out failed sends
|
||||
1. [](#improved)
|
||||
* Added backlink for scheduler task
|
||||
* Added support for `environment` option to `flushqueue` CLI command
|
||||
* Fixed mailtrap hostname in README.md
|
||||
* Disable autocomplete on SMTP `user` and `password` fields
|
||||
|
||||
# v2.7.2
|
||||
## 01/25/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Added default for `to` address
|
||||
* Updated EN language [#99](https://github.com/getgrav/grav-plugin-email/pull/99)
|
||||
* Updated UK language [#98](https://github.com/getgrav/grav-plugin-email/pull/98)
|
||||
* Updated RU language [#100](https://github.com/getgrav/grav-plugin-email/pull/100)
|
||||
* Updated to SwiftMailer v5.4.12
|
||||
1. [](#bugfix)
|
||||
* Fixed `mailtrap` hostname
|
||||
|
||||
# v2.7.1
|
||||
## 12/05/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added new `onEmailSend()` event hook before sending [#70](https://github.com/getgrav/grav-plugin-email/pull/70)
|
||||
1. [](#improved)
|
||||
* Added examples of setting up Email plugin with various SMTP providers
|
||||
* Updated RU language [#60](https://github.com/getgrav/grav-plugin-email/pull/60)
|
||||
* Updated to SwiftMailer v5.4.8
|
||||
|
||||
# v2.7.0
|
||||
## 10/26/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Now uses a dedicated `logs/email.log` file when `debug: true`
|
||||
* Improved the README.txt file with examples, and troubleshooting
|
||||
* Changed default engine to `sendmail` as `mail` is deprecated and not functioning [swiftmailer#866](https://github.com/swiftmailer/swiftmailer/issues/866}
|
||||
|
||||
# v2.6.2
|
||||
## 09/30/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Removed extraneous files from vendor folder
|
||||
|
||||
# v2.6.1
|
||||
## 09/07/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Improved the error message when missing `from` in the configuration
|
||||
* Silently catch malformed email exceptions
|
||||
|
||||
# v2.6.0
|
||||
## 05/22/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Inherit options from plugin configuration [#39](https://github.com/getgrav/grav-plugin-email/pull/39)
|
||||
1. [](#bugfix)
|
||||
* Also process translation on the email subject [https://github.com/getgrav/grav-plugin-comments/issues/38](https://github.com/getgrav/grav-plugin-comments/issues/38)
|
||||
|
||||
# v2.5.3
|
||||
## 01/03/2017
|
||||
|
||||
1. [](#improved)
|
||||
* Updated to SwiftMailer 5.4.5 [#45](https://github.com/getgrav/grav-plugin-email/issues/45)
|
||||
|
||||
# v2.5.2
|
||||
## 12/13/2016
|
||||
|
||||
1. [](#new)
|
||||
* RC released as stable
|
||||
|
||||
# v2.5.2-rc.1
|
||||
## 11/26/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `process_markdown` option for emails in forms
|
||||
1. [](#improved)
|
||||
* Improved the `Utils::sendEmail()` method to take the email type as an option
|
||||
|
||||
# v2.5.1
|
||||
## 10/19/2016
|
||||
|
||||
1. [](#improved)
|
||||
* CLI command will fallback to use the `to` from email plugin config if not provided
|
||||
* Explicit Composer based class loader to fix issues with class case
|
||||
|
||||
# v2.5.0
|
||||
## 09/07/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `bin/plugin email test-email` CLI command
|
||||
1. [](#improved)
|
||||
* Moved Email `Utils` class from Login to Email plugin
|
||||
* Provide a sample base `email/base.html.twig` template for emails
|
||||
1. [](#bugfix)
|
||||
* Fix handling attachments with the updated file upload field
|
||||
|
||||
# v2.4.3
|
||||
## 08/16/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Added Russian translation
|
||||
* Updated Swiftmailer to 5.4.3 [#37](https://github.com/getgrav/grav-plugin-email/issues/37)
|
||||
|
||||
# v2.4.2
|
||||
## 08/10/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Added Croatian translation
|
||||
|
||||
# v2.4.1
|
||||
## 07/14/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Allow multiple email recipients (comma separated) [#31](https://github.com/getgrav/grav-plugin-email/issues/31)
|
||||
* Added Danish and Spanish translations
|
||||
|
||||
# v2.4.0
|
||||
## 05/11/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Now includes Swiftmailer v5.4.2 which introduces a number of bug fixes and improvements
|
||||
1. [](#bugfix)
|
||||
* Correct `starttls` implementation, bundled in TLS
|
||||
|
||||
# v2.3.0
|
||||
## 04/20/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Added debug option to enable logging on SwiftMailer.
|
||||
* Updated SwiftMailer from v5.1.0 to v5.4.1.
|
||||
* Added an option in the Admin settings to enable `starttls`
|
||||
1. [](#bugfix)
|
||||
* Correctly name TLS in the Admin settings, the label was `TTS` (but the value was correctly named `tls`)
|
||||
|
||||
# v2.2.0
|
||||
## 02/05/2016
|
||||
|
||||
1. [](#new)
|
||||
* Allow to send attachments in forms
|
||||
* Added French translation
|
||||
1. [](#improved)
|
||||
* Throw an exception when trying to send emails without a `from` or `to` parameters setup, to intercept less meaningful errors and provide a better description on how to fix the problem
|
||||
* Changed SMTP password in admin to use a password field instead of plain text
|
||||
|
||||
# v2.1.0
|
||||
## 12/18/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added missing `content_type` to email.yaml
|
||||
* Added default values for CC and BCC
|
||||
1. [](#improved)
|
||||
* Improved documentation of new email params in `README.md`
|
||||
* Moved config setting of `mailer.default` to `mailer.engine`
|
||||
|
||||
# v2.0.0
|
||||
## 12/11/2015
|
||||
|
||||
1. [](#new)
|
||||
* Added support for from/sender name (Thomas Keitel)
|
||||
* Added support for message content type (Thomas Keitel)
|
||||
* Added support for reply addresses (Thomas Keitel)
|
||||
* Added support for CC/BCC (Thomas Keitel)
|
||||
* Added support for multiple body parts (Thomas Keitel)
|
||||
1. [](#bugfix)
|
||||
* Fix email engine selection (z38)
|
||||
|
||||
# v1.0.0
|
||||
## 11/20/2015
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix for issue with no body parameter specified
|
||||
|
||||
# v0.2.1
|
||||
## 09/11/2015
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix onFormProcessed event
|
||||
|
||||
# v0.2.0
|
||||
## 08/11/2015
|
||||
|
||||
1. [](#improved)
|
||||
* Disable `enable` in admin
|
||||
|
||||
# v0.1.0
|
||||
## 08/04/2015
|
||||
|
||||
1. [](#new)
|
||||
* ChangeLog started...
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Grav
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,488 @@
|
||||
# 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.
|
||||
|
||||
# Installation
|
||||
|
||||
The email plugin is easy to install with GPM.
|
||||
|
||||
```
|
||||
$ bin/gpm install email
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
By default, the plugin uses PHP Mail as the mail engine.
|
||||
|
||||
```
|
||||
enabled: true
|
||||
from:
|
||||
from_name:
|
||||
to:
|
||||
to_name:
|
||||
queue:
|
||||
enabled: true
|
||||
flush_frequency: '* * * * *'
|
||||
flush_msg_limit: 10
|
||||
flush_time_limit: 100
|
||||
mailer:
|
||||
engine: sendmail
|
||||
smtp:
|
||||
server: localhost
|
||||
port: 25
|
||||
encryption: none
|
||||
user: ''
|
||||
password: ''
|
||||
sendmail:
|
||||
bin: '/usr/sbin/sendmail -bs'
|
||||
content_type: text/html
|
||||
debug: false
|
||||
```
|
||||
|
||||
You can configure the Email plugin by using the Admin plugin, navigating to the Plugins list and choosing `Email`.
|
||||
|
||||
That's the easiest route. Or you can also alter the Plugin configuration by copying the `user/plugins/email/email.yaml` file into `user/config/plugins/email.yaml` and make your modifications there.
|
||||
|
||||
The first setting you'd likely change is your `Email from` / `Email to` names and 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.
|
||||
|
||||
> 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.
|
||||
|
||||
### Mailtrap.io
|
||||
|
||||
A good way to test emails is to use a SMTP server service that's built for testing emails, for example [https://mailtrap.io](https://mailtrap.io)
|
||||
|
||||
Setup the Email plugin to use that SMTP server with the fake inbox data. For example enter this configuration in `user/config/plugins/email.yaml` or through the Admin panel:
|
||||
|
||||
```
|
||||
mailer:
|
||||
engine: smtp
|
||||
smtp:
|
||||
server: smtp.mailtrap.io
|
||||
port: 2525
|
||||
encryption: none
|
||||
user: YOUR_MAILTRAP_INBOX_USER
|
||||
password: YOUR_MAILTRAP_INBOX_PASSWORD
|
||||
```
|
||||
|
||||
That service will intercept emails and show them on their web-based interface instead of sending them for real.
|
||||
|
||||
You can try and fine tune the emails there while testing.
|
||||
|
||||
### 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:
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
mailer:
|
||||
engine: smtp
|
||||
smtp:
|
||||
server: smtp.sparkpostmail.com
|
||||
port: 587
|
||||
encryption: tls
|
||||
user: 'SMTP_Injection'
|
||||
password: 'SEND_EMAIL_API_KEY'
|
||||
```
|
||||
|
||||
Then try sending a test email...
|
||||
|
||||
#### 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:
|
||||
|
||||
```
|
||||
mailer:
|
||||
engine: smtp
|
||||
smtp:
|
||||
server: smtp.sendgrid.net
|
||||
port: 587
|
||||
encryption: tls
|
||||
user: 'apikey'
|
||||
password: 'YOUR_SENDGRID_API_KEY'
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```
|
||||
mailer:
|
||||
engine: smtp
|
||||
smtp:
|
||||
server: smtp.mailgun.org
|
||||
port: 587
|
||||
encryption: tls
|
||||
user: 'MAILGUN_EMAIL_ADDRESS'
|
||||
password: 'MAILGUN_EMAIL_PASSWORD'
|
||||
```
|
||||
|
||||
Adjust these configurations for your account.
|
||||
|
||||
#### 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:
|
||||
|
||||
```
|
||||
mailer:
|
||||
engine: smtp
|
||||
smtp:
|
||||
server: in-v3.mailjet.com
|
||||
port: 587
|
||||
encryption: tls
|
||||
user: 'MAILJUST_USERNAME_API_KEY'
|
||||
password: 'MAILJUST_PASSWORD_SECRET_KEY'
|
||||
```
|
||||
|
||||
It's that easy!
|
||||
|
||||
#### 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.
|
||||
|
||||
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`).
|
||||
|
||||
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)
|
||||
|
||||
```
|
||||
mailer:
|
||||
engine: smtp
|
||||
smtp:
|
||||
server: smtp.zoho.com
|
||||
port: 587
|
||||
encryption: tls
|
||||
user: 'ZOHO_EMAIL_ADDRESS'
|
||||
password: 'ZOHO_EMAIL_PASSWORD'
|
||||
```
|
||||
|
||||
#### 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`:
|
||||
|
||||
```
|
||||
mailer:
|
||||
engine: sendmail
|
||||
sendmail:
|
||||
bin: '/usr/sbin/sendmail -bs'
|
||||
```
|
||||
|
||||
Simply adjust your binary command line to suite your environment
|
||||
|
||||
### SMTP Email Services
|
||||
|
||||
Solid SMTP options that even provide a FREE tier for low email volumes include:
|
||||
|
||||
* SendGrid (100/day free) - https://sendgrid.com
|
||||
* Mailgun - (10k/month free) - https://www.mailgun.com
|
||||
* Mailjet - (6k/month free) - https://www.mailjet.com/
|
||||
* Sparkpost - (15k/month free) - https://www.sparkpost.com
|
||||
* Amazon SES (62k/month free) - https://aws.amazon.com/ses/
|
||||
|
||||
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
|
||||
|
||||
You can test your email configuration with the following CLI Command:
|
||||
|
||||
```
|
||||
$ bin/plugin email test-email -t steve@apple.com
|
||||
```
|
||||
|
||||
You can also pass in a configuration environment:
|
||||
|
||||
```
|
||||
$ bin/plugin email test-email -t steve@apple.com -e 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
|
||||
|
||||
# Programmatically send emails
|
||||
|
||||
Add this code in your plugins:
|
||||
|
||||
```php
|
||||
|
||||
$to = 'email@test.com';
|
||||
$from = 'email@test.com';
|
||||
|
||||
$subject = 'Test';
|
||||
$content = 'Test';
|
||||
|
||||
$message = $this->grav['Email']->message($subject, $content, 'text/html')
|
||||
->setFrom($from)
|
||||
->setTo($to);
|
||||
|
||||
$sent = $this->grav['Email']->send($message);
|
||||
```
|
||||
|
||||
# Emails sent with Forms
|
||||
|
||||
When executing email actions during form processing, action parameters are inherited from the global configuration but may also be overridden on a per-action basis.
|
||||
|
||||
```
|
||||
title: Custom form
|
||||
|
||||
form:
|
||||
name: custom_form
|
||||
fields:
|
||||
|
||||
# Any fields you'd like to add to the form:
|
||||
# Their values may be referenced in email actions via '{{ form.value.FIELDNAME|e }}'
|
||||
|
||||
process:
|
||||
email:
|
||||
subject: "[Custom form] {{ form.value.name|e }}"
|
||||
body: "{% include 'forms/data.txt.twig' %}"
|
||||
from: sender@example.com
|
||||
from_name: 'Custom sender name'
|
||||
to: recipient@example.com
|
||||
to_name: 'Custom recipient name'
|
||||
content_type: 'text/plain'
|
||||
process_markdown: true
|
||||
```
|
||||
|
||||
## Multiple Emails
|
||||
|
||||
You can send multiple emails by creating an array of emails under the `process: email:` option in the form:
|
||||
|
||||
```
|
||||
title: Custom form
|
||||
|
||||
form:
|
||||
name: custom_form
|
||||
fields:
|
||||
|
||||
# Any fields you'd like to add to the form:
|
||||
# Their values may be referenced in email actions via '{{ form.value.FIELDNAME|e }}'
|
||||
|
||||
process:
|
||||
email:
|
||||
-
|
||||
subject: "[Custom Email 1] {{ form.value.name|e }}"
|
||||
body: "{% include 'forms/data.txt.twig' %}"
|
||||
from: {mail: "owner@mysite.com", name: "Site OWner"}'
|
||||
to: {mail: "recepient_1@example.com", name: "Recepient 1"}
|
||||
template: "email/base.html.twig"
|
||||
-
|
||||
subject: "[Custom Email 2] {{ form.value.name|e }}"
|
||||
body: "{% include 'forms/data.txt.twig' %}"
|
||||
from: {mail: "owner@mysite.com", name: "Site OWner"}'
|
||||
to: {mail: "recepient_2@example.com", name: "Recepient 1"}
|
||||
template: "email/base.html.twig"
|
||||
```
|
||||
|
||||
## Templating Emails
|
||||
|
||||
You can specify a Twig template for HTML rendering, else Grav will use the default one `email/base.html.twig` which is included in this plugin. You can also specify a custom template that extends the base, where you can customize the `{% block content %}` and `{% block footer %}`. For example:
|
||||
|
||||
```twig
|
||||
{% extends 'email/base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<p>
|
||||
Greetings {{ form.value.name|e }},
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We have received your request for help. Our team will get in touch with you within 3 Business Days.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Regards,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>My Company</b>
|
||||
<br /><br />
|
||||
E - <a href="mailto:help@mycompany.com">help@mycompany.com</a><br />
|
||||
M - +1 555-123-4567<br />
|
||||
W - <a href="https://mycompany.com">mycompany.com</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
<p style="text-align: center;">My Company - All Rights Reserved</p>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
## Sending Attachments
|
||||
|
||||
You can add file inputs to your form, and send those files via Email.
|
||||
Just add an `attachments` field and list the file input fields names. You can have multiple file fields, and this will send all the files as attachments. Example:
|
||||
|
||||
```
|
||||
form:
|
||||
name: custom_form
|
||||
fields:
|
||||
|
||||
my-file:
|
||||
label: 'Add a file'
|
||||
type: file
|
||||
multiple: false
|
||||
destination: user/data/files
|
||||
accept:
|
||||
- application/pdf
|
||||
- application/x-pdf
|
||||
- image/png
|
||||
- text/plain
|
||||
|
||||
process:
|
||||
|
||||
email:
|
||||
body: '{% include "forms/data.html.twig" %}'
|
||||
attachments:
|
||||
- 'my-file'
|
||||
```
|
||||
|
||||
## Additional action parameters
|
||||
|
||||
To have more control over your generated email, you may also use the following additional parameters:
|
||||
|
||||
* `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.
|
||||
* `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
|
||||
|
||||
Email-related parameters (`from`, `to`, `reply_to`, `cc`and `bcc`) allow different notations for single / multiple values:
|
||||
|
||||
#### Single email address string
|
||||
|
||||
```
|
||||
to: mail@example.com
|
||||
```
|
||||
|
||||
#### Multiple email address strings
|
||||
|
||||
```
|
||||
to:
|
||||
- mail@example.com
|
||||
- mail+1@example.com
|
||||
- mail+2@example.com
|
||||
```
|
||||
|
||||
#### Single email address with name
|
||||
|
||||
```
|
||||
to:
|
||||
mail: mail@example.com
|
||||
name: Human-readable name
|
||||
```
|
||||
|
||||
or inline:
|
||||
|
||||
```
|
||||
to: {mail: 'mail@example.com', name: 'Human-readable name'}
|
||||
```
|
||||
|
||||
#### Multiple email addresses (with and without names)
|
||||
|
||||
```
|
||||
to:
|
||||
-
|
||||
mail: mail@example.com
|
||||
name: Human-readable name
|
||||
-
|
||||
mail: mail+2@example.com
|
||||
name: Another human-readable name
|
||||
-
|
||||
mail+3@example.com
|
||||
-
|
||||
mail+4@example.com
|
||||
```
|
||||
|
||||
or inline:
|
||||
|
||||
```
|
||||
to:
|
||||
- {mail: 'mail@example.com', name: 'Human-readable name'}
|
||||
- {mail: 'mail+2@example.com', name: 'Another human-readable name'}
|
||||
- mail+3@example.com
|
||||
- mail+4@example.com
|
||||
```
|
||||
|
||||
## 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`):
|
||||
|
||||
```
|
||||
body:
|
||||
-
|
||||
content_type: 'text/html'
|
||||
body: "{% include 'forms/default/data.html.twig' %}"
|
||||
-
|
||||
content_type: 'text/plain'
|
||||
body: "{% include 'forms/default/data.txt.twig' %}"
|
||||
charset: 'iso-8859-1'
|
||||
```
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Emails are not sent
|
||||
|
||||
#### Debugging
|
||||
|
||||
The first step in determining why emails are not sent is to enable debugging. This can be done via the `user/config/email.yaml` file or via the plugin settings in the admin. Just enable this and then try sending an email again. Then inspect the `logs/email.log` file for potential problems.
|
||||
|
||||
#### ISP Port 25 blocking
|
||||
|
||||
By default, when sending via PHP or Sendmail the machine running the webserver will attempt to send mail using the SMTP protocol. This uses port `25` which is often blocked by ISPs to protected against spamming. You can determine if this port is blocked by running this command in your temrinal (mac/linux only):
|
||||
|
||||
```
|
||||
(echo >/dev/tcp/localhost/25) &>/dev/null && echo "TCP port 25 opened" || echo "TCP port 25 closed"
|
||||
```
|
||||
|
||||
If it's blocked there are ways to configure relays to different ports, but the simplest solution is to use SMTP for mail sending.
|
||||
|
||||
|
||||
#### Exceptions
|
||||
|
||||
If you get an exception when sending email but you cannot see what the error is, you need to enable more verbose exception messages. In the `user/config/system.yaml` file ensure your have the following configuration:
|
||||
|
||||
```
|
||||
errors:
|
||||
display: 1
|
||||
log: true
|
||||
```
|
||||
|
||||
## Configuration Issues
|
||||
|
||||
As explained above in the Configuration section, if you're using the default settings, set the Plugin configuration to use a SMTP server. It can be [Gmail](https://www.digitalocean.com/community/tutorials/how-to-use-google-s-smtp-server) or another SMTP server you have at your disposal.
|
||||
|
||||
This is the first thing to check. The reason is that PHP Mail, the default system used by the Plugin, is not 100% reliable and emails might not arrive.
|
||||
@@ -0,0 +1,246 @@
|
||||
name: Email
|
||||
slug: email
|
||||
type: plugin
|
||||
version: 3.0.9
|
||||
testing: false
|
||||
description: Enables the emailing system for Grav
|
||||
icon: envelope
|
||||
author:
|
||||
name: Team Grav
|
||||
email: devs@getgrav.org
|
||||
url: http://getgrav.org
|
||||
keywords: plugin, email, sender
|
||||
homepage: https://github.com/getgrav/grav-plugin-email
|
||||
bugs: https://github.com/getgrav/grav-plugin-email/issues
|
||||
license: MIT
|
||||
|
||||
dependencies:
|
||||
- { name: grav, version: '>=1.6.0' }
|
||||
- { name: form, version: '>=3.0.3' }
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
|
||||
fields:
|
||||
enabled:
|
||||
type: hidden
|
||||
label: PLUGIN_ADMIN.PLUGIN_STATUS
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
mailer.engine:
|
||||
type: select
|
||||
label: PLUGIN_EMAIL.MAIL_ENGINE
|
||||
size: medium
|
||||
options:
|
||||
none: PLUGIN_ADMIN.DISABLED
|
||||
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:
|
||||
type: section
|
||||
title: PLUGIN_EMAIL.SMTP_CONFIGURATION
|
||||
underline: true
|
||||
|
||||
mailer.smtp.server:
|
||||
type: text
|
||||
size: medium
|
||||
label: PLUGIN_EMAIL.SMTP_SERVER
|
||||
placeholder: PLUGIN_EMAIL.SMTP_SERVER_PLACEHOLDER
|
||||
|
||||
mailer.smtp.port:
|
||||
type: text
|
||||
size: small
|
||||
label: PLUGIN_EMAIL.SMTP_PORT
|
||||
placeholder: PLUGIN_EMAIL.SMTP_PORT_PLACEHOLDER
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
max: 65535
|
||||
|
||||
mailer.smtp.encryption:
|
||||
type: select
|
||||
size: medium
|
||||
label: PLUGIN_EMAIL.SMTP_ENCRYPTION
|
||||
options:
|
||||
none: PLUGIN_EMAIL.SMTP_ENCRYPTION_NONE
|
||||
ssl: SSL
|
||||
tls: TLS
|
||||
|
||||
mailer.smtp.user:
|
||||
type: text
|
||||
size: medium
|
||||
autocomplete: 'off'
|
||||
label: PLUGIN_EMAIL.SMTP_LOGIN_NAME
|
||||
|
||||
mailer.smtp.password:
|
||||
type: password
|
||||
size: medium
|
||||
autocomplete: 'new-password'
|
||||
label: PLUGIN_EMAIL.SMTP_PASSWORD
|
||||
|
||||
sendmail_config:
|
||||
type: section
|
||||
title: PLUGIN_EMAIL.SENDMAIL_CONFIGURATION
|
||||
underline: true
|
||||
|
||||
mailer.sendmail.bin:
|
||||
type: text
|
||||
size: medium
|
||||
label: PLUGIN_EMAIL.PATH_TO_SENDMAIL
|
||||
placeholder: "/usr/sbin/sendmail"
|
||||
|
||||
queue_section:
|
||||
type: section
|
||||
title: PLUGIN_EMAIL.QUEUE_TITLE
|
||||
text: PLUGIN_EMAIL.QUEUE_DESC
|
||||
markdown: true
|
||||
underline: true
|
||||
|
||||
queue.enabled:
|
||||
type: toggle
|
||||
label: PLUGIN_EMAIL.QUEUE_ENABLED
|
||||
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
|
||||
help: PLUGIN_EMAIL.QUEUE_FLUSH_FREQUENCY_HELP
|
||||
default: '* * * * *'
|
||||
placeholder: '* * * * *'
|
||||
|
||||
queue.flush_msg_limit:
|
||||
type: number
|
||||
label: PLUGIN_EMAIL.QUEUE_FLUSH_MSG_LIMIT
|
||||
size: x-small
|
||||
append: PLUGIN_EMAIL.QUEUE_FLUSH_MSG_LIMIT_APPEND
|
||||
|
||||
queue.flush_time_limit:
|
||||
type: number
|
||||
label: PLUGIN_EMAIL.QUEUE_FLUSH_TIME_LIMIT
|
||||
size: x-small
|
||||
append: PLUGIN_EMAIL.QUEUE_FLUSH_TIME_LIMIT_APPEND
|
||||
|
||||
advanced_section:
|
||||
type: section
|
||||
title: PLUGIN_EMAIL.ADVANCED
|
||||
underline: true
|
||||
|
||||
debug:
|
||||
type: toggle
|
||||
label: PLUGIN_EMAIL.DEBUG
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
@@ -0,0 +1,545 @@
|
||||
<?php
|
||||
namespace Grav\Plugin\Email;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Framework\Form\Interfaces\FormInterface;
|
||||
use \Monolog\Logger;
|
||||
use \Monolog\Handler\StreamHandler;
|
||||
|
||||
class Email
|
||||
{
|
||||
/**
|
||||
* @var \Swift_Transport
|
||||
*/
|
||||
protected $mailer;
|
||||
|
||||
/**
|
||||
* @var \Swift_Plugins_LoggerPlugin
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
protected $queue_path;
|
||||
|
||||
/**
|
||||
* Returns true if emails have been enabled in the system.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function enabled()
|
||||
{
|
||||
return Grav::instance()['config']->get('plugins.email.mailer.engine') !== 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if debugging on emails has been enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function debug()
|
||||
{
|
||||
return Grav::instance()['config']->get('plugins.email.debug') == 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an email message.
|
||||
*
|
||||
* @param string $subject
|
||||
* @param string $body
|
||||
* @param string $contentType
|
||||
* @param string $charset
|
||||
* @return \Swift_Message
|
||||
*/
|
||||
public function message($subject = null, $body = null, $contentType = null, $charset = null)
|
||||
{
|
||||
return new \Swift_Message($subject, $body, $contentType, $charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param \Swift_Message $message
|
||||
* @return int
|
||||
*/
|
||||
public function send($message)
|
||||
{
|
||||
$mailer = $this->getMailer();
|
||||
|
||||
$result = $mailer ? $mailer->send($message) : 0;
|
||||
|
||||
// Check if emails and debugging are both enabled.
|
||||
if ($mailer && $this->debug()) {
|
||||
|
||||
$log = new Logger('email');
|
||||
$locator = Grav::instance()['locator'];
|
||||
$log_file = $locator->findResource('log://email.log', true, true);
|
||||
$log->pushHandler(new StreamHandler($log_file, Logger::DEBUG));
|
||||
|
||||
// Append the SwiftMailer log to the log.
|
||||
$log->addDebug($this->getLogs());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build e-mail message.
|
||||
*
|
||||
* @param array $params
|
||||
* @param array $vars
|
||||
* @return \Swift_Message
|
||||
*/
|
||||
public function buildMessage(array $params, array $vars = [])
|
||||
{
|
||||
/** @var Twig $twig */
|
||||
$twig = Grav::instance()['twig'];
|
||||
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = Grav::instance()['language'];
|
||||
|
||||
// Extend parameters with defaults.
|
||||
$params += [
|
||||
'bcc' => $config->get('plugins.email.bcc', []),
|
||||
'body' => $config->get('plugins.email.body', '{% include "forms/data.html.twig" %}'),
|
||||
'cc' => $config->get('plugins.email.cc', []),
|
||||
'cc_name' => $config->get('plugins.email.cc_name'),
|
||||
'charset' => $config->get('plugins.email.charset', 'utf-8'),
|
||||
'from' => $config->get('plugins.email.from'),
|
||||
'from_name' => $config->get('plugins.email.from_name'),
|
||||
'content_type' => $config->get('plugins.email.content_type', 'text/html'),
|
||||
'reply_to' => $config->get('plugins.email.reply_to', []),
|
||||
'reply_to_name' => $config->get('plugins.email.reply_to_name'),
|
||||
'subject' => !empty($vars['form']) && $vars['form'] instanceof FormInterface ? $vars['form']->page()->title() : null,
|
||||
'to' => $config->get('plugins.email.to'),
|
||||
'to_name' => $config->get('plugins.email.to_name'),
|
||||
'process_markdown' => false,
|
||||
'template' => false
|
||||
];
|
||||
|
||||
// Create message object.
|
||||
$message = $this->message();
|
||||
|
||||
if (!$params['to']) {
|
||||
throw new \RuntimeException($language->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_TO_ADDRESS'));
|
||||
}
|
||||
if (!$params['from']) {
|
||||
throw new \RuntimeException($language->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_FROM_ADDRESS'));
|
||||
}
|
||||
|
||||
// Process parameters.
|
||||
foreach ($params as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'body':
|
||||
if (is_string($value)) {
|
||||
$body = $twig->processString($value, $vars);
|
||||
|
||||
if ($params['process_markdown']) {
|
||||
$parsedown = new Parsedown();
|
||||
$body = $parsedown->text($body);
|
||||
}
|
||||
|
||||
if ($params['template']) {
|
||||
$vars = array_merge($vars, ['content' => $body]);
|
||||
$body = $twig->processTemplate($params['template'], $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)) {
|
||||
foreach ($value as $body_part) {
|
||||
$body_part += [
|
||||
'charset' => $params['charset'],
|
||||
'content_type' => $params['content_type'],
|
||||
];
|
||||
|
||||
$body = !empty($body_part['body']) ? $twig->processString($body_part['body'], $vars) : null;
|
||||
|
||||
if ($params['process_markdown']) {
|
||||
$parsedown = new Parsedown();
|
||||
$body = $parsedown->text($body);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'subject':
|
||||
$message->setSubject($twig->processString($language->translate($value), $vars));
|
||||
break;
|
||||
|
||||
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':
|
||||
if (is_string($value) && !empty($params['from_name'])) {
|
||||
$value = [
|
||||
'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':
|
||||
if (is_string($value) && !empty($params['reply_to_name'])) {
|
||||
$value = [
|
||||
'mail' => $twig->processString($value, $vars),
|
||||
'name' => $twig->processString($params['reply_to_name'], $vars),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($this->parseAddressValue($value, $vars) as $address) {
|
||||
$message->addReplyTo($address->mail, $address->name);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return parsed e-mail address value.
|
||||
*
|
||||
* @param string|string[] $value
|
||||
* @param array $vars
|
||||
* @return array
|
||||
*/
|
||||
public function parseAddressValue($value, array $vars = [])
|
||||
{
|
||||
$parsed = [];
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = Grav::instance()['twig'];
|
||||
|
||||
// Single e-mail address string
|
||||
if (is_string($value)) {
|
||||
$parsed[] = (object) [
|
||||
'mail' => $twig->processString($value, $vars),
|
||||
'name' => null,
|
||||
];
|
||||
}
|
||||
|
||||
else {
|
||||
// Cast value as array
|
||||
$value = (array) $value;
|
||||
|
||||
// Single e-mail address array
|
||||
if (!empty($value['mail'])) {
|
||||
$parsed[] = (object) [
|
||||
'mail' => $twig->processString($value['mail'], $vars),
|
||||
'name' => !empty($value['name']) ? $twig->processString($value['name'], $vars) : NULL,
|
||||
];
|
||||
}
|
||||
|
||||
// Multiple addresses (either as strings or arrays)
|
||||
elseif (!(empty($value['mail']) && !empty($value['name']))) {
|
||||
foreach ($value as $y => $itemx) {
|
||||
$addresses = $this->parseAddressValue($itemx, $vars);
|
||||
|
||||
if (($address = reset($addresses))) {
|
||||
$parsed[] = $address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return debugging logs if enabled
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLogs()
|
||||
{
|
||||
if ($this->debug()) {
|
||||
return $this->logger->dump();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return null|\Swift_Mailer
|
||||
*/
|
||||
protected function getMailer()
|
||||
{
|
||||
if (!$this->enabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->mailer) {
|
||||
/** @var Config $config */
|
||||
$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
|
||||
$this->mailer = new \Swift_Mailer($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;
|
||||
}
|
||||
|
||||
protected static function getQueuePath()
|
||||
{
|
||||
$queue_path = Grav::instance()['locator']->findResource('user://data', true) . '/email-queue';
|
||||
|
||||
if (!file_exists($queue_path)) {
|
||||
mkdir($queue_path);
|
||||
}
|
||||
|
||||
return $queue_path;
|
||||
}
|
||||
|
||||
protected static function getQueue()
|
||||
{
|
||||
$queue_path = static::getQueuePath();
|
||||
|
||||
$spool = new \Swift_FileSpool($queue_path);
|
||||
$transport = new \Swift_SpoolTransport($spool);
|
||||
|
||||
return $transport;
|
||||
}
|
||||
|
||||
public static function flushQueue()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$grav['debugger']->enabled(false);
|
||||
|
||||
$config = $grav['config']->get('plugins.email.queue');
|
||||
|
||||
$queue = static::getQueue();
|
||||
$spool = $queue->getSpool();
|
||||
$spool->setMessageLimit($config['flush_msg_limit']);
|
||||
$spool->setTimeLimit($config['flush_time_limit']);
|
||||
|
||||
try {
|
||||
$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");
|
||||
}
|
||||
|
||||
// Remove the file
|
||||
unlink($final_message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean copy a message
|
||||
*
|
||||
* @param \Swift_Message $message
|
||||
*/
|
||||
public static function cloneMessage($message)
|
||||
{
|
||||
$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());
|
||||
|
||||
return $clean;
|
||||
|
||||
}
|
||||
|
||||
protected static function getTransport()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$engine = $config->get('plugins.email.mailer.engine');
|
||||
|
||||
// Create the Transport and initialize it.
|
||||
switch ($engine) {
|
||||
case 'smtp':
|
||||
$transport = new \Swift_SmtpTransport();
|
||||
|
||||
$options = $config->get('plugins.email.mailer.smtp');
|
||||
if (!empty($options['server'])) {
|
||||
$transport->setHost($options['server']);
|
||||
}
|
||||
if (!empty($options['port'])) {
|
||||
$transport->setPort($options['port']);
|
||||
}
|
||||
if (!empty($options['encryption']) && $options['encryption'] !== 'none') {
|
||||
$transport->setEncryption($options['encryption']);
|
||||
}
|
||||
if (!empty($options['user'])) {
|
||||
$transport->setUsername($options['user']);
|
||||
}
|
||||
if (!empty($options['password'])) {
|
||||
$transport->setPassword($options['password']);
|
||||
}
|
||||
break;
|
||||
case 'sendmail':
|
||||
default:
|
||||
$options = $config->get('plugins.email.mailer.sendmail');
|
||||
$bin = !empty($options['bin']) ? $options['bin'] : '/usr/sbin/sendmail';
|
||||
$transport = new \Swift_SendmailTransport($bin);
|
||||
break;
|
||||
}
|
||||
|
||||
return $transport;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Plugin\Email;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Common\Utils as GravUtils;
|
||||
|
||||
/**
|
||||
* Class Utils
|
||||
* @package Grav\Plugin\Email
|
||||
*/
|
||||
class Utils
|
||||
{
|
||||
/**
|
||||
* Quick utility method to send an HTML email.
|
||||
*
|
||||
* @param array<int,mixed> $params
|
||||
*
|
||||
* @return bool True if the action was performed.
|
||||
*/
|
||||
public static function sendEmail(...$params)
|
||||
{
|
||||
if (is_array($params[0])) {
|
||||
$params = array_shift($params);
|
||||
} else {
|
||||
$keys = ['subject', 'body', 'to', 'from', 'content_type'];
|
||||
$params = GravUtils::arrayCombine($keys, $params);
|
||||
}
|
||||
|
||||
//Initialize twig if not yet initialized
|
||||
/** @var Twig $twig */
|
||||
$twig = Grav::instance()['twig']->init();
|
||||
|
||||
/** @var Email $email */
|
||||
$email = Grav::instance()['Email'];
|
||||
|
||||
if (empty($params['to']) || empty($params['subject']) || empty($params['body'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$params['body'] = $twig->processTemplate('email/base.html.twig', ['content' => $params['body']]);
|
||||
|
||||
$message = $email->buildMessage($params);
|
||||
|
||||
return $email->send($message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace Grav\Plugin\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Grav\Plugin\Email\Email;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* Class FlushQueueCommand
|
||||
* @package Grav\Console\Cli\
|
||||
*/
|
||||
class ClearQueueFailuresCommand extends ConsoleCommand
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('clear-queue-failures')
|
||||
->setAliases(['clearqueue'])
|
||||
->addOption(
|
||||
'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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null|void
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
// TODO: remove when requiring Grav 1.7+
|
||||
if (method_exists($this, 'initializeGrav')) {
|
||||
$this->initializeGrav();
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<yellow>Current Configuration:</yellow>');
|
||||
$this->output->writeln('');
|
||||
|
||||
dump($grav['config']->get('plugins.email'));
|
||||
|
||||
$this->output->writeln('');
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$output = Email::clearQueueFailures();
|
||||
|
||||
$this->output->writeln('<green>' . $output . '</green>');
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace Grav\Plugin\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Grav\Plugin\Email\Email;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* Class FlushQueueCommand
|
||||
* @package Grav\Console\Cli\
|
||||
*/
|
||||
class FlushQueueCommand extends ConsoleCommand
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('flush-queue')
|
||||
->setAliases(['flushqueue'])
|
||||
->addOption(
|
||||
'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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null|void
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
// TODO: remove when requiring Grav 1.7+
|
||||
if (method_exists($this, 'initializeGrav')) {
|
||||
$this->initializeGrav();
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<yellow>Current Configuration:</yellow>');
|
||||
$this->output->writeln('');
|
||||
|
||||
dump($grav['config']->get('plugins.email'));
|
||||
|
||||
$this->output->writeln('');
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$output = Email::flushQueue();
|
||||
|
||||
$this->output->writeln('<green>' . $output . '</green>');
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace Grav\Plugin\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Grav\Plugin\Email\Email;
|
||||
use Grav\Plugin\Email\Utils as EmailUtils;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* Class TestEmailCommand
|
||||
* @package Grav\Console\Cli\
|
||||
*/
|
||||
class TestEmailCommand extends ConsoleCommand
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('test-email')
|
||||
->setAliases(['testemail'])
|
||||
->addOption(
|
||||
'to',
|
||||
't',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'An email address to send the email to'
|
||||
)
|
||||
->addOption(
|
||||
'env',
|
||||
'e',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com'
|
||||
)
|
||||
->addOption(
|
||||
'subject',
|
||||
's',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'A subject for the email'
|
||||
)
|
||||
->addOption(
|
||||
'body',
|
||||
'b',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The body of the email'
|
||||
)
|
||||
->setDescription('Sends a test email using the plugin\'s configuration')
|
||||
->setHelp('The <info>test-email</info> command sends a test email using the plugin\'s configuration');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null|void
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
// TODO: remove when requiring Grav 1.7+
|
||||
if (method_exists($this, 'initializeGrav')) {
|
||||
$this->initializeThemes();
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('<yellow>Current Configuration:</yellow>');
|
||||
$this->output->writeln('');
|
||||
|
||||
dump($grav['config']->get('plugins.email'));
|
||||
|
||||
$this->output->writeln('');
|
||||
|
||||
$grav['Email'] = new Email();
|
||||
|
||||
$to = $this->input->getOption('to') ?: $grav['config']->get('plugins.email.to');
|
||||
$subject = $this->input->getOption('subject');
|
||||
$body = $this->input->getOption('body');
|
||||
|
||||
if (!$subject) {
|
||||
$subject = 'Testing Grav Email Plugin';
|
||||
}
|
||||
|
||||
if (!$body) {
|
||||
$configuration = print_r($grav['config']->get('plugins.email'), true);
|
||||
$body = $grav['language']->translate(['PLUGIN_EMAIL.TEST_EMAIL_BODY', $configuration]);
|
||||
}
|
||||
|
||||
$sent = EmailUtils::sendEmail(['subject'=>$subject, 'body'=>$body, 'to'=>$to]);
|
||||
|
||||
if ($sent) {
|
||||
$this->output->writeln("<green>Message sent successfully!</green>");
|
||||
} else {
|
||||
$this->output->writeln("<red>Problem sending email...</red>");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "getgrav/grav-plugin-email",
|
||||
"type": "grav-plugin",
|
||||
"description": "Email plugin for Grav CMS",
|
||||
"keywords": ["email", "plugin", "sender"],
|
||||
"homepage": "https://github.com/getgrav/grav-plugin-email",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Team Grav",
|
||||
"email": "devs@getgrav.org",
|
||||
"homepage": "https://getgrav.org",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/getgrav/grav-plugin-email/issues",
|
||||
"irc": "https://chat.getgrav.org",
|
||||
"forum": "https://getgrav.org/forum",
|
||||
"docs": "https://github.com/getgrav/grav-plugin-email/blob/master/README.md"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"swiftmailer/swiftmailer": "~6.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grav\\Plugin\\Email\\": "classes/",
|
||||
"Grav\\Plugin\\Console\\": "cli/"
|
||||
},
|
||||
"classmap": ["email.php"]
|
||||
}
|
||||
}
|
||||
Generated
+494
@@ -0,0 +1,494 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a841aff1de7e0364fe3e92ecd7b2fb56",
|
||||
"packages": [
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
"version": "1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/lexer.git",
|
||||
"reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
|
||||
"reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"phpstan/phpstan": "^0.11.8",
|
||||
"phpunit/phpunit": "^8.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
|
||||
"keywords": [
|
||||
"annotations",
|
||||
"docblock",
|
||||
"lexer",
|
||||
"parser",
|
||||
"php"
|
||||
],
|
||||
"time": "2019-10-30T14:39:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
"version": "2.1.17",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/egulias/EmailValidator.git",
|
||||
"reference": "ade6887fd9bd74177769645ab5c474824f8a418a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ade6887fd9bd74177769645ab5c474824f8a418a",
|
||||
"reference": "ade6887fd9bd74177769645ab5c474824f8a418a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/lexer": "^1.0.1",
|
||||
"php": ">=5.5",
|
||||
"symfony/polyfill-intl-idn": "^1.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"dominicsayers/isemail": "^3.0.7",
|
||||
"phpunit/phpunit": "^4.8.36|^7.5.15",
|
||||
"satooshi/php-coveralls": "^1.0.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Egulias\\EmailValidator\\": "EmailValidator"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Eduardo Gulias Davis"
|
||||
}
|
||||
],
|
||||
"description": "A library for validating emails against several RFCs",
|
||||
"homepage": "https://github.com/egulias/EmailValidator",
|
||||
"keywords": [
|
||||
"email",
|
||||
"emailvalidation",
|
||||
"emailvalidator",
|
||||
"validation",
|
||||
"validator"
|
||||
],
|
||||
"time": "2020-02-13T22:36:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
"version": "v6.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swiftmailer/swiftmailer.git",
|
||||
"reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/149cfdf118b169f7840bbe3ef0d4bc795d1780c9",
|
||||
"reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"egulias/email-validator": "~2.0",
|
||||
"php": ">=7.0.0",
|
||||
"symfony/polyfill-iconv": "^1.0",
|
||||
"symfony/polyfill-intl-idn": "^1.10",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~0.9.1",
|
||||
"symfony/phpunit-bridge": "^3.4.19|^4.1.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "Needed to support internationalized email addresses",
|
||||
"true/punycode": "Needed to support internationalized email addresses, if ext-intl is not installed"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/swift_required.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Corbyn"
|
||||
},
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
}
|
||||
],
|
||||
"description": "Swiftmailer, free feature-rich PHP mailer",
|
||||
"homepage": "https://swiftmailer.symfony.com",
|
||||
"keywords": [
|
||||
"email",
|
||||
"mail",
|
||||
"mailer"
|
||||
],
|
||||
"time": "2019-11-12T09:31:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-iconv",
|
||||
"version": "v1.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-iconv.git",
|
||||
"reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ad6d62792bfbcfc385dd34b424d4fcf9712a32c8",
|
||||
"reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Iconv\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Iconv extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"iconv",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-09T19:04:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
"version": "v1.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||
"reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
|
||||
"reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"symfony/polyfill-mbstring": "^1.3",
|
||||
"symfony/polyfill-php72": "^1.10"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Intl\\Idn\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Laurent Bassin",
|
||||
"email": "laurent@bassin.info"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"idn",
|
||||
"intl",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-09T19:04:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
|
||||
"reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-09T19:04:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php72",
|
||||
"version": "v1.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php72.git",
|
||||
"reference": "37b0976c78b94856543260ce09b460a7bc852747"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747",
|
||||
"reference": "37b0976c78b94856543260ce09b460a7bc852747",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php72\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-02-27T09:26:54+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.1.3"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "1.1.0"
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Plugin;
|
||||
use Grav\Plugin\Email\Email;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
class EmailPlugin extends Plugin
|
||||
{
|
||||
/**
|
||||
* @var Email
|
||||
*/
|
||||
protected $email;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
'onPluginsInitialized' => ['onPluginsInitialized', 0],
|
||||
'onFormProcessed' => ['onFormProcessed', 0],
|
||||
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
|
||||
'onSchedulerInitialized' => ['onSchedulerInitialized', 0],
|
||||
'onAdminSave' => ['onAdminSave', 0],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize emailing.
|
||||
*/
|
||||
public function onPluginsInitialized()
|
||||
{
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$this->email = new Email();
|
||||
|
||||
if ($this->email->enabled()) {
|
||||
$this->grav['Email'] = $this->email;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add twig paths to plugin templates.
|
||||
*/
|
||||
public function onTwigTemplatePaths()
|
||||
{
|
||||
$twig = $this->grav['twig'];
|
||||
$twig->twig_paths[] = __DIR__ . '/templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Force compile during save if admin plugin save
|
||||
*
|
||||
* @param Event $event
|
||||
*/
|
||||
public function onAdminSave(Event $event)
|
||||
{
|
||||
/** @var Data $obj */
|
||||
$obj = $event['object'];
|
||||
|
||||
|
||||
|
||||
if ($obj instanceof Data && $obj->blueprints()->getFilename() === 'email/blueprints') {
|
||||
$current_pw = $this->grav['config']->get('plugins.email.mailer.smtp.password');
|
||||
$new_pw = $obj->get('mailer.smtp.password');
|
||||
if (!empty($current_pw) && empty($new_pw)) {
|
||||
$obj->set('mailer.smtp.password', $current_pw);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email when processing the form data.
|
||||
*
|
||||
* @param Event $event
|
||||
*/
|
||||
public function onFormProcessed(Event $event)
|
||||
{
|
||||
$form = $event['form'];
|
||||
$action = $event['action'];
|
||||
$params = $event['params'];
|
||||
|
||||
if (!$this->email->enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'email':
|
||||
// Prepare Twig variables
|
||||
$vars = array(
|
||||
'form' => $form
|
||||
);
|
||||
|
||||
// Copy files now, we need those.
|
||||
// TODO: needs an update
|
||||
$form->legacyUploads();
|
||||
$form->copyFiles();
|
||||
|
||||
$this->grav->fireEvent('onEmailSend', new Event(['params' => &$params, 'vars' => &$vars]));
|
||||
|
||||
if ($this->isAssocArray($params)) {
|
||||
$this->sendFormEmail($form, $params, $vars);
|
||||
} else {
|
||||
foreach ($params as $email) {
|
||||
$this->sendFormEmail($form, $email, $vars);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
// Build message
|
||||
$message = $this->email->buildMessage($params, $vars);
|
||||
|
||||
if (isset($params['attachments'])) {
|
||||
$filesToAttach = (array)$params['attachments'];
|
||||
if ($filesToAttach) foreach ($filesToAttach as $fileToAttach) {
|
||||
$filesValues = $form->value($fileToAttach);
|
||||
|
||||
if ($filesValues) foreach($filesValues as $fileValues) {
|
||||
if (isset($fileValues['file'])) {
|
||||
$filename = $fileValues['file'];
|
||||
} else {
|
||||
$filename = ROOT_DIR . $fileValues['path'];
|
||||
}
|
||||
|
||||
try {
|
||||
$message->attach(\Swift_Attachment::fromPath($filename));
|
||||
} catch (\Exception $e) {
|
||||
// Log any issues
|
||||
$this->grav['log']->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send e-mail
|
||||
$this->email->send($message);
|
||||
}
|
||||
|
||||
protected function isAssocArray(array $arr)
|
||||
{
|
||||
if (array() === $arr) return false;
|
||||
$keys = array_keys($arr);
|
||||
$index_keys = range(0, count($arr) - 1);
|
||||
return $keys !== $index_keys;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
enabled: true
|
||||
from:
|
||||
from_name:
|
||||
to:
|
||||
to_name:
|
||||
queue:
|
||||
enabled: false
|
||||
flush_frequency: '* * * * *'
|
||||
flush_msg_limit: 10
|
||||
flush_time_limit: 100
|
||||
mailer:
|
||||
engine: sendmail
|
||||
smtp:
|
||||
server: localhost
|
||||
port: 25
|
||||
encryption: none
|
||||
user: ''
|
||||
password: ''
|
||||
sendmail:
|
||||
bin: '/usr/sbin/sendmail -bs'
|
||||
content_type: text/html
|
||||
debug: false
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"project":"grav-plugin-email",
|
||||
"platforms":{
|
||||
"grav":{
|
||||
"nodes":{
|
||||
"plugin":[
|
||||
{
|
||||
"source":"/",
|
||||
"destination":"/user/plugins/email"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
en:
|
||||
PLUGIN_EMAIL:
|
||||
MAIL_ENGINE: "Mail Engine"
|
||||
MAIL_ENGINE_DISABLED: "Disabled"
|
||||
CONTENT_TYPE: "Content type"
|
||||
CONTENT_TYPE_PLAIN_TEXT: "Plain text"
|
||||
CHARSET: "Charset"
|
||||
CHARSET_PLACEHOLDER: "Defaults to UTF-8"
|
||||
EMAIL_FORM: "From address"
|
||||
EMAIL_FORM_PLACEHOLDER: "Default email from address"
|
||||
EMAIL_FROM_NAME: "From name"
|
||||
EMAIL_FROM_NAME_PLACEHOLDER: "Default email from name"
|
||||
EMAIL_TO: "To address"
|
||||
EMAIL_TO_PLACEHOLDER: "Default email to address"
|
||||
EMAIL_TO_NAME: "To name"
|
||||
EMAIL_TO_NAME_PLACEHOLDER: "Default email to name"
|
||||
EMAIL_CC: "CC address"
|
||||
EMAIL_CC_PLACEHOLDER: "Default email CC address"
|
||||
EMAIL_CC_NAME: "CC name"
|
||||
EMAIL_CC_NAME_PLACEHOLDER: "Default email CC name"
|
||||
EMAIL_BCC: "BCC address"
|
||||
EMAIL_BCC_PLACEHOLDER: "Default email BCC address"
|
||||
EMAIL_REPLY_TO: "Reply-to address"
|
||||
EMAIL_REPLY_TO_PLACEHOLDER: "Default email reply-to address"
|
||||
EMAIL_REPLY_TO_NAME: "Reply-to name"
|
||||
EMAIL_REPLY_TO_NAME_PLACEHOLDER: "Default email reply-to name"
|
||||
EMAIL_BODY: "Body"
|
||||
EMAIL_BODY_PLACEHOLDER: "Defaults to a table of all form fields"
|
||||
SMTP_SERVER: "SMTP server"
|
||||
SMTP_SERVER_PLACEHOLDER: "e.g. smtp.google.com"
|
||||
SMTP_PORT: "SMTP port"
|
||||
SMTP_PORT_PLACEHOLDER: "Defaults to 25 (plaintext) / 587 (encrypted)"
|
||||
SMTP_ENCRYPTION: "SMTP encryption"
|
||||
SMTP_ENCRYPTION_NONE: "None"
|
||||
SMTP_LOGIN_NAME: "SMTP login name"
|
||||
SMTP_PASSWORD: "SMTP password"
|
||||
PATH_TO_SENDMAIL: "Path to sendmail"
|
||||
DEBUG: "Debug"
|
||||
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_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>"
|
||||
EMAIL_DEFAULTS: "Email Defaults"
|
||||
SMTP_CONFIGURATION: "SMTP Configuration"
|
||||
SENDMAIL_CONFIGURATION: "Sendmail Configuration"
|
||||
ADVANCED: "Advanced"
|
||||
EMAIL_FOOTER: "GetGrav.org"
|
||||
QUEUE_TITLE: "Email Queue"
|
||||
QUEUE_DESC: "When you enable the email queue, email is not <i>sent immediately</i>, rather the email is sent to the queue, and then the Grav <strong>scheduler will flush the queue</strong> and actually send the email based on the configured frequency. This ensures Grav is not waiting around for email connections to complete."
|
||||
QUEUE_ENABLED: "Enabled"
|
||||
QUEUE_FLUSH_FREQUENCY: "Flush Frequency"
|
||||
QUEUE_FLUSH_FREQUENCY_HELP: "Use 'cron' format"
|
||||
QUEUE_FLUSH_MSG_LIMIT: "Messages per flush"
|
||||
QUEUE_FLUSH_MSG_LIMIT_APPEND: "Messages"
|
||||
QUEUE_FLUSH_TIME_LIMIT: "Flush time limit"
|
||||
QUEUE_FLUSH_TIME_LIMIT_APPEND: "Seconds"
|
||||
|
||||
|
||||
|
||||
da:
|
||||
PLUGIN_EMAIL:
|
||||
PLEASE_CONFIGURE_A_TO_ADDRESS: "Konfigurere venligst en 'til' email adresse i Email Plugin indstillingerne eller her i formularen"
|
||||
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Konfigurere venligst en 'fra' email adresse i Email Plugin indstillingerne eller her i formularen"
|
||||
|
||||
de:
|
||||
PLUGIN_EMAIL:
|
||||
EMAIL_NOT_CONFIGURED: "E-Mail ist nicht konfiguriert"
|
||||
PLEASE_CONFIGURE_A_TO_ADDRESS: "Bitte konfigurieren sie eine 'An' ('to') Adresse in den Email-Plugin-Einstellungen oder im Formular."
|
||||
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Bitte konfigurieren sie eine 'Von' ('from') Adresse in den Email-Plugin-Einstellungen oder im Formular."
|
||||
|
||||
es:
|
||||
PLUGIN_EMAIL:
|
||||
PLEASE_CONFIGURE_A_TO_ADDRESS: "Por favor configura una dirección de 'remitente' en la configuración del Plugin de Email o en el formulario"
|
||||
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Por favor configura una dirección de 'destinatario' en la configuración del Plugin de Email o en el formulario"
|
||||
|
||||
fr:
|
||||
PLUGIN_EMAIL:
|
||||
EMAIL_NOT_CONFIGURED: "L’e-mail n’est 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_FROM_ADDRESS: "Veuillez configurer une adresse 'd'expéditeur' dans les paramètres du Plugin 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>"
|
||||
EMAIL_FOOTER: "GetGrav.org"
|
||||
|
||||
hr:
|
||||
PLUGIN_EMAIL:
|
||||
EMAIL_NOT_CONFIGURED: "Email nije konfiguriran"
|
||||
PLEASE_CONFIGURE_A_TO_ADDRESS: "Konfigurirajte 'za' ('to') adresu u postavkama Email dodatka ili u obrascu"
|
||||
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Konfigurirajte 'od' ('from') adresu u postavkama Email dodatka ili u obrascu"
|
||||
|
||||
it:
|
||||
PLUGIN_EMAIL:
|
||||
PLEASE_CONFIGURE_A_TO_ADDRESS: "Per favore, configura l'indirizzo di destinazione ('to') nella configurazione del Plugin Email, oppure direttamente nella form."
|
||||
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Per favore, configura l'indirizzo di provenienza ('from') nella configurazione del Plugin Email, oppure direttamente nella form"
|
||||
|
||||
ro:
|
||||
PLUGIN_EMAIL:
|
||||
EMAIL_NOT_CONFIGURED: "Adresa de email nu este configurată"
|
||||
PLEASE_CONFIGURE_A_TO_ADDRESS: "Vă rugam setați o adresă 'către' în setările modulului Email sau în formular"
|
||||
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Vă rugam setați o adresă 'de la' în setările modulului Email sau în formular"
|
||||
|
||||
ru:
|
||||
PLUGIN_EMAIL:
|
||||
MAIL_ENGINE: "Почтовая система"
|
||||
MAIL_ENGINE_DISABLED: "Отключена"
|
||||
CONTENT_TYPE: "Тип контента"
|
||||
CONTENT_TYPE_PLAIN_TEXT: "Простой текст"
|
||||
CHARSET: "Кодировка"
|
||||
CHARSET_PLACEHOLDER: "По умолчанию UTF-8"
|
||||
EMAIL_DEFAULTS: "Email настройки по умолчанию"
|
||||
EMAIL_FORM: "Почта отправителя"
|
||||
EMAIL_FORM_PLACEHOLDER: "Email адрес отправителя по умолчанию"
|
||||
EMAIL_FROM_NAME: "Имя почты отправителя"
|
||||
EMAIL_FROM_NAME_PLACEHOLDER: "Email имя отправителя по умолчанию"
|
||||
EMAIL_TO: "Почта получателя"
|
||||
EMAIL_TO_PLACEHOLDER: "Email адрес получателя по умолчанию"
|
||||
EMAIL_TO_NAME: "Имя почты получателя"
|
||||
EMAIL_TO_NAME_PLACEHOLDER: "Email имя получателя по умолчанию"
|
||||
EMAIL_CC: "Почта CC"
|
||||
EMAIL_CC_PLACEHOLDER: "Email CC адрес по умолчанию"
|
||||
EMAIL_CC_NAME: "Имя почты CC"
|
||||
EMAIL_CC_NAME_PLACEHOLDER: "Email CC имя по умолчанию"
|
||||
EMAIL_BCC: "Почта BCC"
|
||||
EMAIL_BCC_PLACEHOLDER: "Email BCC адрес по умолчанию"
|
||||
EMAIL_REPLY_TO: "Почта для ответов"
|
||||
EMAIL_REPLY_TO_PLACEHOLDER: "Email для ответов адрес по умолчанию"
|
||||
EMAIL_REPLY_TO_NAME: "Имя почты для ответов"
|
||||
EMAIL_REPLY_TO_NAME_PLACEHOLDER: "Email для ответов имя по умолчанию"
|
||||
EMAIL_BODY: "Тело сообщения"
|
||||
EMAIL_BODY_PLACEHOLDER: "По умолчанию используется таблица всех полей формы"
|
||||
SMTP_CONFIGURATION: "Конфигурация SMTP"
|
||||
SMTP_SERVER: "SMTP сервер"
|
||||
SMTP_SERVER_PLACEHOLDER: "e.g. smtp.google.com"
|
||||
SMTP_PORT: "SMTP порт"
|
||||
SMTP_PORT_PLACEHOLDER: "По умолчанию 25 (plaintext) / 587 (encrypted)"
|
||||
SMTP_ENCRYPTION: "SMTP шифрование"
|
||||
SMTP_ENCRYPTION_NONE: "Нет"
|
||||
SMTP_LOGIN_NAME: "SMTP логин"
|
||||
SMTP_PASSWORD: "SMTP пароль"
|
||||
SENDMAIL_CONFIGURATION: "Конфигурация Sendmail"
|
||||
PATH_TO_SENDMAIL: "Путь к sendmail"
|
||||
QUEUE_TITLE: "Очередь Email"
|
||||
QUEUE_DESC: "Когда вы включаете очередь email, электронная почта не <i>отправляется немедленно</i>, а отправляется в очередь, затем планировщик Grav <strong>обрабатывает очередь</strong> и на основе настроенной частоты фактически отправляет электронную почту. Это гарантирует, что Grav не ждет завершения подключения к электронной почте."
|
||||
QUEUE_ENABLED: "Включено"
|
||||
QUEUE_FLUSH_FREQUENCY: "Частота обраблтки"
|
||||
QUEUE_FLUSH_FREQUENCY_HELP: "Использовать формат 'cron'"
|
||||
QUEUE_FLUSH_MSG_LIMIT: "Количество сообщений на задачу"
|
||||
QUEUE_FLUSH_MSG_LIMIT_APPEND: "Сообщений"
|
||||
QUEUE_FLUSH_TIME_LIMIT: "Ограничение времени обработки"
|
||||
QUEUE_FLUSH_TIME_LIMIT_APPEND: "Секунд"
|
||||
ADVANCED: "Расширенные"
|
||||
DEBUG: "Отладка"
|
||||
EMAIL_NOT_CONFIGURED: "Электронная почта не настроена"
|
||||
PLEASE_CONFIGURE_A_TO_ADDRESS: "Пожалуйста настройте адрес получателя ('to') в настройках плагина Email Plugin, или на форме"
|
||||
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Пожалуйста настройте адрес отправителя ('from') в настройках плагина Email Plugin, или на форме"
|
||||
TEST_EMAIL_BODY: "<h1>Тестирование электронной почты</h1><p>Это тестовое письмо отправлено на основе следующей конфигурации:</p> <p><pre>%1$s</pre></p>"
|
||||
EMAIL_FOOTER: "GetGrav.org"
|
||||
|
||||
uk:
|
||||
PLUGIN_EMAIL:
|
||||
EMAIL_NOT_CONFIGURED: "Електронна пошта не налаштована"
|
||||
PLEASE_CONFIGURE_A_TO_ADDRESS: "Будь ласка налаштуйте адресу одержувача ('to') в налаштуваннях плагіна Email Plugin, або на формі"
|
||||
PLEASE_CONFIGURE_A_FROM_ADDRESS: "Будь ласка налаштуйте адресу відправника ('from') в налаштуваннях плагіна Email Plugin, або на формі"
|
||||
TEST_EMAIL_BODY: "<h1>Тестування електронної пошти</h1><p>Це тестовий лист відправлено на основі такої конфігурації:</p> <p><pre>%1$s</pre></p>"
|
||||
EMAIL_FOOTER: "GetGrav.org"
|
||||
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title></title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL
|
||||
------------------------------------- */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
|
||||
font-size: 100%;
|
||||
line-height: 1.6;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: none;
|
||||
width: 100%!important;
|
||||
height: 100%;
|
||||
}
|
||||
/* -------------------------------------
|
||||
ELEMENTS
|
||||
------------------------------------- */
|
||||
a {
|
||||
color: #348eda;
|
||||
}
|
||||
.btn-primary {
|
||||
text-decoration: none;
|
||||
color: #FFF;
|
||||
background-color: #348eda;
|
||||
border: solid #348eda;
|
||||
border-width: 10px 20px;
|
||||
line-height: 2;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border-radius: 25px;
|
||||
}
|
||||
.btn-secondary {
|
||||
text-decoration: none;
|
||||
color: #FFF;
|
||||
background-color: #aaa;
|
||||
border: solid #aaa;
|
||||
border-width: 10px 20px;
|
||||
line-height: 2;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border-radius: 25px;
|
||||
}
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
.padding {
|
||||
padding: 10px 0;
|
||||
}
|
||||
/* -------------------------------------
|
||||
BODY
|
||||
------------------------------------- */
|
||||
table.body-wrap {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
table.body-wrap .container {
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
/* -------------------------------------
|
||||
FOOTER
|
||||
------------------------------------- */
|
||||
table.footer-wrap {
|
||||
width: 100%;
|
||||
clear: both!important;
|
||||
}
|
||||
.footer-wrap .container p {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
|
||||
}
|
||||
table.footer-wrap a {
|
||||
color: #999;
|
||||
}
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1, h2, h3 {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
color: #000;
|
||||
margin: 40px 0 10px;
|
||||
line-height: 1.2;
|
||||
font-weight: 200;
|
||||
}
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
}
|
||||
p, ul, ol {
|
||||
margin-bottom: 10px;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
}
|
||||
ul li, ol li {
|
||||
margin-left: 5px;
|
||||
list-style-position: inside;
|
||||
}
|
||||
.word-break {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
|
||||
-ms-word-break: break-all;
|
||||
/* This is the dangerous one in WebKit, as it breaks things wherever */
|
||||
word-break: break-all;
|
||||
/* Instead use this non-standard one: */
|
||||
word-break: break-word;
|
||||
|
||||
/* Adds a hyphen where the word breaks, if supported (No Blink) */
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
/* ---------------------------------------------------
|
||||
RESPONSIVENESS
|
||||
Nuke it from orbit. It's the only way to be sure.
|
||||
------------------------------------------------------ */
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
.container {
|
||||
display: block!important;
|
||||
max-width: 600px!important;
|
||||
margin: 0 auto!important; /* makes it centered */
|
||||
clear: both!important;
|
||||
}
|
||||
/* Set the padding on the td rather than the div for Outlook compatibility */
|
||||
.body-wrap .container {
|
||||
padding: 20px;
|
||||
}
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
/* Let's make sure tables in the content area are 100% wide */
|
||||
.content table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body bgcolor="#f6f6f6">
|
||||
|
||||
<!-- body -->
|
||||
<table class="body-wrap" bgcolor="#f6f6f6">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="container" bgcolor="#FFFFFF">
|
||||
<div class="content">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
{% block content %}
|
||||
{{ content|raw }}
|
||||
{% endblock content %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- /body -->
|
||||
|
||||
<!-- footer -->
|
||||
<table class="footer-wrap">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
{% block footer %}
|
||||
{{ 'PLUGIN_EMAIL.EMAIL_FOOTER'|t }}
|
||||
{% endblock footer %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- /footer -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitdec5d78def1384d1f20996ded9d7efbf::getLoader();
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Grav\\Plugin\\EmailPlugin' => $baseDir . '/email.php',
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
|
||||
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
|
||||
'2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
|
||||
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
||||
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
|
||||
'Symfony\\Polyfill\\Iconv\\' => array($vendorDir . '/symfony/polyfill-iconv'),
|
||||
'Grav\\Plugin\\Email\\' => array($baseDir . '/classes'),
|
||||
'Grav\\Plugin\\Console\\' => array($baseDir . '/cli'),
|
||||
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/EmailValidator'),
|
||||
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
|
||||
);
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitdec5d78def1384d1f20996ded9d7efbf
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitdec5d78def1384d1f20996ded9d7efbf', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitdec5d78def1384d1f20996ded9d7efbf', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitdec5d78def1384d1f20996ded9d7efbf::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);
|
||||
|
||||
if ($useStaticLoader) {
|
||||
$includeFiles = Composer\Autoload\ComposerStaticInitdec5d78def1384d1f20996ded9d7efbf::$files;
|
||||
} else {
|
||||
$includeFiles = require __DIR__ . '/autoload_files.php';
|
||||
}
|
||||
foreach ($includeFiles as $fileIdentifier => $file) {
|
||||
composerRequiredec5d78def1384d1f20996ded9d7efbf($fileIdentifier, $file);
|
||||
}
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
|
||||
function composerRequiredec5d78def1384d1f20996ded9d7efbf($fileIdentifier, $file)
|
||||
{
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
require $file;
|
||||
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitdec5d78def1384d1f20996ded9d7efbf
|
||||
{
|
||||
public static $files = array (
|
||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
|
||||
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php',
|
||||
'2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Polyfill\\Php72\\' => 23,
|
||||
'Symfony\\Polyfill\\Mbstring\\' => 26,
|
||||
'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
|
||||
'Symfony\\Polyfill\\Iconv\\' => 23,
|
||||
),
|
||||
'G' =>
|
||||
array (
|
||||
'Grav\\Plugin\\Email\\' => 18,
|
||||
'Grav\\Plugin\\Console\\' => 20,
|
||||
),
|
||||
'E' =>
|
||||
array (
|
||||
'Egulias\\EmailValidator\\' => 23,
|
||||
),
|
||||
'D' =>
|
||||
array (
|
||||
'Doctrine\\Common\\Lexer\\' => 22,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Symfony\\Polyfill\\Php72\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
|
||||
),
|
||||
'Symfony\\Polyfill\\Mbstring\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
|
||||
),
|
||||
'Symfony\\Polyfill\\Intl\\Idn\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn',
|
||||
),
|
||||
'Symfony\\Polyfill\\Iconv\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-iconv',
|
||||
),
|
||||
'Grav\\Plugin\\Email\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/classes',
|
||||
),
|
||||
'Grav\\Plugin\\Console\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/cli',
|
||||
),
|
||||
'Egulias\\EmailValidator\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/egulias/email-validator/EmailValidator',
|
||||
),
|
||||
'Doctrine\\Common\\Lexer\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Grav\\Plugin\\EmailPlugin' => __DIR__ . '/../..' . '/email.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitdec5d78def1384d1f20996ded9d7efbf::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitdec5d78def1384d1f20996ded9d7efbf::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInitdec5d78def1384d1f20996ded9d7efbf::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
+489
@@ -0,0 +1,489 @@
|
||||
[
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
"version": "1.2.0",
|
||||
"version_normalized": "1.2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/lexer.git",
|
||||
"reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
|
||||
"reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"phpstan/phpstan": "^0.11.8",
|
||||
"phpunit/phpunit": "^8.2"
|
||||
},
|
||||
"time": "2019-10-30T14:39:59+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2.x-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
|
||||
"keywords": [
|
||||
"annotations",
|
||||
"docblock",
|
||||
"lexer",
|
||||
"parser",
|
||||
"php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
"version": "2.1.17",
|
||||
"version_normalized": "2.1.17.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/egulias/EmailValidator.git",
|
||||
"reference": "ade6887fd9bd74177769645ab5c474824f8a418a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ade6887fd9bd74177769645ab5c474824f8a418a",
|
||||
"reference": "ade6887fd9bd74177769645ab5c474824f8a418a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/lexer": "^1.0.1",
|
||||
"php": ">=5.5",
|
||||
"symfony/polyfill-intl-idn": "^1.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"dominicsayers/isemail": "^3.0.7",
|
||||
"phpunit/phpunit": "^4.8.36|^7.5.15",
|
||||
"satooshi/php-coveralls": "^1.0.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
|
||||
},
|
||||
"time": "2020-02-13T22:36:52+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1.x-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Egulias\\EmailValidator\\": "EmailValidator"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Eduardo Gulias Davis"
|
||||
}
|
||||
],
|
||||
"description": "A library for validating emails against several RFCs",
|
||||
"homepage": "https://github.com/egulias/EmailValidator",
|
||||
"keywords": [
|
||||
"email",
|
||||
"emailvalidation",
|
||||
"emailvalidator",
|
||||
"validation",
|
||||
"validator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
"version": "v6.2.3",
|
||||
"version_normalized": "6.2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swiftmailer/swiftmailer.git",
|
||||
"reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/149cfdf118b169f7840bbe3ef0d4bc795d1780c9",
|
||||
"reference": "149cfdf118b169f7840bbe3ef0d4bc795d1780c9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"egulias/email-validator": "~2.0",
|
||||
"php": ">=7.0.0",
|
||||
"symfony/polyfill-iconv": "^1.0",
|
||||
"symfony/polyfill-intl-idn": "^1.10",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~0.9.1",
|
||||
"symfony/phpunit-bridge": "^3.4.19|^4.1.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "Needed to support internationalized email addresses",
|
||||
"true/punycode": "Needed to support internationalized email addresses, if ext-intl is not installed"
|
||||
},
|
||||
"time": "2019-11-12T09:31:26+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.2-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/swift_required.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Corbyn"
|
||||
},
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
}
|
||||
],
|
||||
"description": "Swiftmailer, free feature-rich PHP mailer",
|
||||
"homepage": "https://swiftmailer.symfony.com",
|
||||
"keywords": [
|
||||
"email",
|
||||
"mail",
|
||||
"mailer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-iconv",
|
||||
"version": "v1.15.0",
|
||||
"version_normalized": "1.15.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-iconv.git",
|
||||
"reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ad6d62792bfbcfc385dd34b424d4fcf9712a32c8",
|
||||
"reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "For best performance"
|
||||
},
|
||||
"time": "2020-03-09T19:04:49+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Iconv\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Iconv extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"iconv",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
"version": "v1.15.0",
|
||||
"version_normalized": "1.15.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||
"reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
|
||||
"reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"symfony/polyfill-mbstring": "^1.3",
|
||||
"symfony/polyfill-php72": "^1.10"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "For best performance"
|
||||
},
|
||||
"time": "2020-03-09T19:04:49+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Intl\\Idn\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Laurent Bassin",
|
||||
"email": "laurent@bassin.info"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"idn",
|
||||
"intl",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.15.0",
|
||||
"version_normalized": "1.15.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
|
||||
"reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"time": "2020-03-09T19:04:49+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php72",
|
||||
"version": "v1.15.0",
|
||||
"version_normalized": "1.15.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php72.git",
|
||||
"reference": "37b0976c78b94856543260ce09b460a7bc852747"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747",
|
||||
"reference": "37b0976c78b94856543260ce09b460a7bc852747",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"time": "2020-02-27T09:26:54+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.15-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php72\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2006-2018 Doctrine Project
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Doctrine Lexer
|
||||
|
||||
Build Status: [](https://travis-ci.org/doctrine/lexer)
|
||||
|
||||
Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.
|
||||
|
||||
This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL).
|
||||
|
||||
https://www.doctrine-project.org/projects/lexer.html
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
"type": "library",
|
||||
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
|
||||
"keywords": [
|
||||
"php",
|
||||
"parser",
|
||||
"lexer",
|
||||
"annotations",
|
||||
"docblock"
|
||||
],
|
||||
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
|
||||
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
|
||||
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"phpstan/phpstan": "^0.11.8",
|
||||
"phpunit/phpunit": "^8.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Doctrine\\Tests\\": "tests/Doctrine" }
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2.x-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Common\Lexer;
|
||||
|
||||
use ReflectionClass;
|
||||
use const PREG_SPLIT_DELIM_CAPTURE;
|
||||
use const PREG_SPLIT_NO_EMPTY;
|
||||
use const PREG_SPLIT_OFFSET_CAPTURE;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function preg_split;
|
||||
use function sprintf;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* Base class for writing simple lexers, i.e. for creating small DSLs.
|
||||
*/
|
||||
abstract class AbstractLexer
|
||||
{
|
||||
/**
|
||||
* Lexer original input string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $input;
|
||||
|
||||
/**
|
||||
* Array of scanned tokens.
|
||||
*
|
||||
* Each token is an associative array containing three items:
|
||||
* - 'value' : the string value of the token in the input string
|
||||
* - 'type' : the type of the token (identifier, numeric, string, input
|
||||
* parameter, none)
|
||||
* - 'position' : the position of the token in the input string
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tokens = [];
|
||||
|
||||
/**
|
||||
* Current lexer position in input string.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $position = 0;
|
||||
|
||||
/**
|
||||
* Current peek of current lexer position.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $peek = 0;
|
||||
|
||||
/**
|
||||
* The next token in the input.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
public $lookahead;
|
||||
|
||||
/**
|
||||
* The last matched/seen token.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
public $token;
|
||||
|
||||
/**
|
||||
* Composed regex for input parsing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $regex;
|
||||
|
||||
/**
|
||||
* Sets the input data to be tokenized.
|
||||
*
|
||||
* The Lexer is immediately reset and the new input tokenized.
|
||||
* Any unprocessed tokens from any previous input are lost.
|
||||
*
|
||||
* @param string $input The input to be tokenized.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setInput($input)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->tokens = [];
|
||||
|
||||
$this->reset();
|
||||
$this->scan($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the lexer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->lookahead = null;
|
||||
$this->token = null;
|
||||
$this->peek = 0;
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the peek pointer to 0.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetPeek()
|
||||
{
|
||||
$this->peek = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the lexer position on the input to the given position.
|
||||
*
|
||||
* @param int $position Position to place the lexical scanner.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetPosition($position = 0)
|
||||
{
|
||||
$this->position = $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the original lexer's input until a given position.
|
||||
*
|
||||
* @param int $position
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInputUntilPosition($position)
|
||||
{
|
||||
return substr($this->input, 0, $position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given token matches the current lookahead.
|
||||
*
|
||||
* @param int|string $token
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNextToken($token)
|
||||
{
|
||||
return $this->lookahead !== null && $this->lookahead['type'] === $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any of the given tokens matches the current lookahead.
|
||||
*
|
||||
* @param array $tokens
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNextTokenAny(array $tokens)
|
||||
{
|
||||
return $this->lookahead !== null && in_array($this->lookahead['type'], $tokens, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to the next token in the input string.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function moveNext()
|
||||
{
|
||||
$this->peek = 0;
|
||||
$this->token = $this->lookahead;
|
||||
$this->lookahead = isset($this->tokens[$this->position])
|
||||
? $this->tokens[$this->position++] : null;
|
||||
|
||||
return $this->lookahead !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the lexer to skip input tokens until it sees a token with the given value.
|
||||
*
|
||||
* @param string $type The token type to skip until.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function skipUntil($type)
|
||||
{
|
||||
while ($this->lookahead !== null && $this->lookahead['type'] !== $type) {
|
||||
$this->moveNext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given value is identical to the given token.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param int|string $token
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isA($value, $token)
|
||||
{
|
||||
return $this->getType($value) === $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the lookahead token forward.
|
||||
*
|
||||
* @return array|null The next token or NULL if there are no more tokens ahead.
|
||||
*/
|
||||
public function peek()
|
||||
{
|
||||
if (isset($this->tokens[$this->position + $this->peek])) {
|
||||
return $this->tokens[$this->position + $this->peek++];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peeks at the next token, returns it and immediately resets the peek.
|
||||
*
|
||||
* @return array|null The next token or NULL if there are no more tokens ahead.
|
||||
*/
|
||||
public function glimpse()
|
||||
{
|
||||
$peek = $this->peek();
|
||||
$this->peek = 0;
|
||||
|
||||
return $peek;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the input string for tokens.
|
||||
*
|
||||
* @param string $input A query string.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function scan($input)
|
||||
{
|
||||
if (! isset($this->regex)) {
|
||||
$this->regex = sprintf(
|
||||
'/(%s)|%s/%s',
|
||||
implode(')|(', $this->getCatchablePatterns()),
|
||||
implode('|', $this->getNonCatchablePatterns()),
|
||||
$this->getModifiers()
|
||||
);
|
||||
}
|
||||
|
||||
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE;
|
||||
$matches = preg_split($this->regex, $input, -1, $flags);
|
||||
|
||||
if ($matches === false) {
|
||||
// Work around https://bugs.php.net/78122
|
||||
$matches = [[$input, 0]];
|
||||
}
|
||||
|
||||
foreach ($matches as $match) {
|
||||
// Must remain before 'value' assignment since it can change content
|
||||
$type = $this->getType($match[0]);
|
||||
|
||||
$this->tokens[] = [
|
||||
'value' => $match[0],
|
||||
'type' => $type,
|
||||
'position' => $match[1],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the literal for a given token.
|
||||
*
|
||||
* @param int|string $token
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public function getLiteral($token)
|
||||
{
|
||||
$className = static::class;
|
||||
$reflClass = new ReflectionClass($className);
|
||||
$constants = $reflClass->getConstants();
|
||||
|
||||
foreach ($constants as $name => $value) {
|
||||
if ($value === $token) {
|
||||
return $className . '::' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex modifiers
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getModifiers()
|
||||
{
|
||||
return 'iu';
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexical catchable patterns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function getCatchablePatterns();
|
||||
|
||||
/**
|
||||
* Lexical non-catchable patterns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function getNonCatchablePatterns();
|
||||
|
||||
/**
|
||||
* Retrieve token type. Also processes the token value if necessary.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return int|string|null
|
||||
*/
|
||||
abstract protected function getType(&$value);
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator;
|
||||
|
||||
use Doctrine\Common\Lexer\AbstractLexer;
|
||||
|
||||
class EmailLexer extends AbstractLexer
|
||||
{
|
||||
//ASCII values
|
||||
const C_DEL = 127;
|
||||
const C_NUL = 0;
|
||||
const S_AT = 64;
|
||||
const S_BACKSLASH = 92;
|
||||
const S_DOT = 46;
|
||||
const S_DQUOTE = 34;
|
||||
const S_OPENPARENTHESIS = 49;
|
||||
const S_CLOSEPARENTHESIS = 261;
|
||||
const S_OPENBRACKET = 262;
|
||||
const S_CLOSEBRACKET = 263;
|
||||
const S_HYPHEN = 264;
|
||||
const S_COLON = 265;
|
||||
const S_DOUBLECOLON = 266;
|
||||
const S_SP = 267;
|
||||
const S_HTAB = 268;
|
||||
const S_CR = 269;
|
||||
const S_LF = 270;
|
||||
const S_IPV6TAG = 271;
|
||||
const S_LOWERTHAN = 272;
|
||||
const S_GREATERTHAN = 273;
|
||||
const S_COMMA = 274;
|
||||
const S_SEMICOLON = 275;
|
||||
const S_OPENQBRACKET = 276;
|
||||
const S_CLOSEQBRACKET = 277;
|
||||
const S_SLASH = 278;
|
||||
const S_EMPTY = null;
|
||||
const GENERIC = 300;
|
||||
const CRLF = 301;
|
||||
const INVALID = 302;
|
||||
const ASCII_INVALID_FROM = 127;
|
||||
const ASCII_INVALID_TO = 199;
|
||||
|
||||
/**
|
||||
* US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $charValue = array(
|
||||
'(' => self::S_OPENPARENTHESIS,
|
||||
')' => self::S_CLOSEPARENTHESIS,
|
||||
'<' => self::S_LOWERTHAN,
|
||||
'>' => self::S_GREATERTHAN,
|
||||
'[' => self::S_OPENBRACKET,
|
||||
']' => self::S_CLOSEBRACKET,
|
||||
':' => self::S_COLON,
|
||||
';' => self::S_SEMICOLON,
|
||||
'@' => self::S_AT,
|
||||
'\\' => self::S_BACKSLASH,
|
||||
'/' => self::S_SLASH,
|
||||
',' => self::S_COMMA,
|
||||
'.' => self::S_DOT,
|
||||
'"' => self::S_DQUOTE,
|
||||
'-' => self::S_HYPHEN,
|
||||
'::' => self::S_DOUBLECOLON,
|
||||
' ' => self::S_SP,
|
||||
"\t" => self::S_HTAB,
|
||||
"\r" => self::S_CR,
|
||||
"\n" => self::S_LF,
|
||||
"\r\n" => self::CRLF,
|
||||
'IPv6' => self::S_IPV6TAG,
|
||||
'{' => self::S_OPENQBRACKET,
|
||||
'}' => self::S_CLOSEQBRACKET,
|
||||
'' => self::S_EMPTY,
|
||||
'\0' => self::C_NUL,
|
||||
);
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasInvalidTokens = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @psalm-var array{value:string, type:null|int, position:int}|array<empty, empty>
|
||||
*/
|
||||
protected $previous = [];
|
||||
|
||||
/**
|
||||
* The last matched/seen token.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @psalm-var array{value:string, type:null|int, position:int}
|
||||
*/
|
||||
public $token;
|
||||
|
||||
/**
|
||||
* The next token in the input.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
public $lookahead;
|
||||
|
||||
/**
|
||||
* @psalm-var array{value:'', type:null, position:0}
|
||||
*/
|
||||
private static $nullToken = [
|
||||
'value' => '',
|
||||
'type' => null,
|
||||
'position' => 0,
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->previous = $this->token = self::$nullToken;
|
||||
$this->lookahead = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->hasInvalidTokens = false;
|
||||
parent::reset();
|
||||
$this->previous = $this->token = self::$nullToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasInvalidTokens()
|
||||
{
|
||||
return $this->hasInvalidTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type
|
||||
* @throws \UnexpectedValueException
|
||||
* @return boolean
|
||||
*
|
||||
* @psalm-suppress InvalidScalarArgument
|
||||
*/
|
||||
public function find($type)
|
||||
{
|
||||
$search = clone $this;
|
||||
$search->skipUntil($type);
|
||||
|
||||
if (!$search->lookahead) {
|
||||
throw new \UnexpectedValueException($type . ' not found');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* getPrevious
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPrevious()
|
||||
{
|
||||
return $this->previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* moveNext
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function moveNext()
|
||||
{
|
||||
$this->previous = $this->token;
|
||||
$hasNext = parent::moveNext();
|
||||
$this->token = $this->token ?: self::$nullToken;
|
||||
|
||||
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 array('[\xA0-\xff]+');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve token type. Also processes the token value if necessary.
|
||||
*
|
||||
* @param string $value
|
||||
* @throws \InvalidArgumentException
|
||||
* @return integer
|
||||
*/
|
||||
protected function getType(&$value)
|
||||
{
|
||||
if ($this->isNullType($value)) {
|
||||
return self::C_NUL;
|
||||
}
|
||||
|
||||
if ($this->isValid($value)) {
|
||||
return $this->charValue[$value];
|
||||
}
|
||||
|
||||
if ($this->isUTF8Invalid($value)) {
|
||||
$this->hasInvalidTokens = true;
|
||||
return self::INVALID;
|
||||
}
|
||||
|
||||
return self::GENERIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValid($value)
|
||||
{
|
||||
if (isset($this->charValue[$value])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
protected function isNullType($value)
|
||||
{
|
||||
if ($value === "\0") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
protected function isUTF8Invalid($value)
|
||||
{
|
||||
if (preg_match('/\p{Cc}+/u', $value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getModifiers()
|
||||
{
|
||||
return 'iu';
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator;
|
||||
|
||||
use Egulias\EmailValidator\Exception\ExpectingATEXT;
|
||||
use Egulias\EmailValidator\Exception\NoLocalPart;
|
||||
use Egulias\EmailValidator\Parser\DomainPart;
|
||||
use Egulias\EmailValidator\Parser\LocalPart;
|
||||
use Egulias\EmailValidator\Warning\EmailTooLong;
|
||||
|
||||
/**
|
||||
* EmailParser
|
||||
*
|
||||
* @author Eduardo Gulias Davis <me@egulias.com>
|
||||
*/
|
||||
class EmailParser
|
||||
{
|
||||
const EMAIL_MAX_LENGTH = 254;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $warnings = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $domainPart = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $localPart = '';
|
||||
/**
|
||||
* @var EmailLexer
|
||||
*/
|
||||
protected $lexer;
|
||||
|
||||
/**
|
||||
* @var LocalPart
|
||||
*/
|
||||
protected $localPartParser;
|
||||
|
||||
/**
|
||||
* @var DomainPart
|
||||
*/
|
||||
protected $domainPartParser;
|
||||
|
||||
public function __construct(EmailLexer $lexer)
|
||||
{
|
||||
$this->lexer = $lexer;
|
||||
$this->localPartParser = new LocalPart($this->lexer);
|
||||
$this->domainPartParser = new DomainPart($this->lexer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return array
|
||||
*/
|
||||
public function parse($str)
|
||||
{
|
||||
$this->lexer->setInput($str);
|
||||
|
||||
if (!$this->hasAtToken()) {
|
||||
throw new NoLocalPart();
|
||||
}
|
||||
|
||||
|
||||
$this->localPartParser->parse($str);
|
||||
$this->domainPartParser->parse($str);
|
||||
|
||||
$this->setParts($str);
|
||||
|
||||
if ($this->lexer->hasInvalidTokens()) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
|
||||
return array('local' => $this->localPart, 'domain' => $this->domainPart);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Warning\Warning[]
|
||||
*/
|
||||
public function getWarnings()
|
||||
{
|
||||
$localPartWarnings = $this->localPartParser->getWarnings();
|
||||
$domainPartWarnings = $this->domainPartParser->getWarnings();
|
||||
$this->warnings = array_merge($localPartWarnings, $domainPartWarnings);
|
||||
|
||||
$this->addLongEmailWarning($this->localPart, $this->domainPart);
|
||||
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getParsedDomainPart()
|
||||
{
|
||||
return $this->domainPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
*/
|
||||
protected function setParts($email)
|
||||
{
|
||||
$parts = explode('@', $email);
|
||||
$this->domainPart = $this->domainPartParser->getDomainPart();
|
||||
$this->localPart = $parts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasAtToken()
|
||||
{
|
||||
$this->lexer->moveNext();
|
||||
$this->lexer->moveNext();
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $localPart
|
||||
* @param string $parsedDomainPart
|
||||
*/
|
||||
protected function addLongEmailWarning($localPart, $parsedDomainPart)
|
||||
{
|
||||
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) {
|
||||
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator;
|
||||
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
use Egulias\EmailValidator\Validation\EmailValidation;
|
||||
|
||||
class EmailValidator
|
||||
{
|
||||
/**
|
||||
* @var EmailLexer
|
||||
*/
|
||||
private $lexer;
|
||||
|
||||
/**
|
||||
* @var Warning\Warning[]
|
||||
*/
|
||||
protected $warnings = [];
|
||||
|
||||
/**
|
||||
* @var InvalidEmail|null
|
||||
*/
|
||||
protected $error;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->lexer = new EmailLexer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @param EmailValidation $emailValidation
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($email, EmailValidation $emailValidation)
|
||||
{
|
||||
$isValid = $emailValidation->isValid($email, $this->lexer);
|
||||
$this->warnings = $emailValidation->getWarnings();
|
||||
$this->error = $emailValidation->getError();
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasWarnings()
|
||||
{
|
||||
return !empty($this->warnings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getWarnings()
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvalidEmail|null
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class AtextAfterCFWS extends InvalidEmail
|
||||
{
|
||||
const CODE = 133;
|
||||
const REASON = "ATEXT found after CFWS";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class CRLFAtTheEnd extends InvalidEmail
|
||||
{
|
||||
const CODE = 149;
|
||||
const REASON = "CRLF at the end";
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class CRLFX2 extends InvalidEmail
|
||||
{
|
||||
const CODE = 148;
|
||||
const REASON = "Folding whitespace CR LF found twice";
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class CRNoLF extends InvalidEmail
|
||||
{
|
||||
const CODE = 150;
|
||||
const REASON = "Missing LF after CR";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class CharNotAllowed extends InvalidEmail
|
||||
{
|
||||
const CODE = 201;
|
||||
const REASON = "Non allowed character in domain";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class CommaInDomain extends InvalidEmail
|
||||
{
|
||||
const CODE = 200;
|
||||
const REASON = "Comma ',' is not allowed in domain part";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class ConsecutiveAt extends InvalidEmail
|
||||
{
|
||||
const CODE = 128;
|
||||
const REASON = "Consecutive AT";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class ConsecutiveDot extends InvalidEmail
|
||||
{
|
||||
const CODE = 132;
|
||||
const REASON = "Consecutive DOT";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class DomainHyphened extends InvalidEmail
|
||||
{
|
||||
const CODE = 144;
|
||||
const REASON = "Hyphen found in domain";
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class DotAtEnd extends InvalidEmail
|
||||
{
|
||||
const CODE = 142;
|
||||
const REASON = "Dot at the end";
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class DotAtStart extends InvalidEmail
|
||||
{
|
||||
const CODE = 141;
|
||||
const REASON = "Found DOT at start";
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class ExpectingAT extends InvalidEmail
|
||||
{
|
||||
const CODE = 202;
|
||||
const REASON = "Expecting AT '@' ";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class ExpectingATEXT extends InvalidEmail
|
||||
{
|
||||
const CODE = 137;
|
||||
const REASON = "Expecting ATEXT";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class ExpectingCTEXT extends InvalidEmail
|
||||
{
|
||||
const CODE = 139;
|
||||
const REASON = "Expecting CTEXT";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class ExpectingDTEXT extends InvalidEmail
|
||||
{
|
||||
const CODE = 129;
|
||||
const REASON = "Expected DTEXT";
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class ExpectingDomainLiteralClose extends InvalidEmail
|
||||
{
|
||||
const CODE = 137;
|
||||
const REASON = "Closing bracket ']' for domain literal not found";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class ExpectingQPair extends InvalidEmail
|
||||
{
|
||||
const CODE = 136;
|
||||
const REASON = "Expecting QPAIR";
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
abstract class InvalidEmail extends \InvalidArgumentException
|
||||
{
|
||||
const REASON = "Invalid email";
|
||||
const CODE = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(static::REASON, static::CODE);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class NoDNSRecord extends InvalidEmail
|
||||
{
|
||||
const CODE = 5;
|
||||
const REASON = 'No MX or A DSN record was found for this email';
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class NoDomainPart extends InvalidEmail
|
||||
{
|
||||
const CODE = 131;
|
||||
const REASON = "No Domain part";
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class NoLocalPart extends InvalidEmail
|
||||
{
|
||||
const CODE = 130;
|
||||
const REASON = "No local part";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class UnclosedComment extends InvalidEmail
|
||||
{
|
||||
const CODE = 146;
|
||||
const REASON = "No colosing comment token found";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class UnclosedQuotedString extends InvalidEmail
|
||||
{
|
||||
const CODE = 145;
|
||||
const REASON = "Unclosed quoted string";
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Exception;
|
||||
|
||||
class UnopenedComment extends InvalidEmail
|
||||
{
|
||||
const CODE = 152;
|
||||
const REASON = "No opening comment token found";
|
||||
}
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Parser;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Exception\CharNotAllowed;
|
||||
use Egulias\EmailValidator\Exception\CommaInDomain;
|
||||
use Egulias\EmailValidator\Exception\ConsecutiveAt;
|
||||
use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
|
||||
use Egulias\EmailValidator\Exception\CRNoLF;
|
||||
use Egulias\EmailValidator\Exception\DomainHyphened;
|
||||
use Egulias\EmailValidator\Exception\DotAtEnd;
|
||||
use Egulias\EmailValidator\Exception\DotAtStart;
|
||||
use Egulias\EmailValidator\Exception\ExpectingATEXT;
|
||||
use Egulias\EmailValidator\Exception\ExpectingDomainLiteralClose;
|
||||
use Egulias\EmailValidator\Exception\ExpectingDTEXT;
|
||||
use Egulias\EmailValidator\Exception\NoDomainPart;
|
||||
use Egulias\EmailValidator\Exception\UnopenedComment;
|
||||
use Egulias\EmailValidator\Warning\AddressLiteral;
|
||||
use Egulias\EmailValidator\Warning\CFWSWithFWS;
|
||||
use Egulias\EmailValidator\Warning\DeprecatedComment;
|
||||
use Egulias\EmailValidator\Warning\DomainLiteral;
|
||||
use Egulias\EmailValidator\Warning\DomainTooLong;
|
||||
use Egulias\EmailValidator\Warning\IPV6BadChar;
|
||||
use Egulias\EmailValidator\Warning\IPV6ColonEnd;
|
||||
use Egulias\EmailValidator\Warning\IPV6ColonStart;
|
||||
use Egulias\EmailValidator\Warning\IPV6Deprecated;
|
||||
use Egulias\EmailValidator\Warning\IPV6DoubleColon;
|
||||
use Egulias\EmailValidator\Warning\IPV6GroupCount;
|
||||
use Egulias\EmailValidator\Warning\IPV6MaxGroups;
|
||||
use Egulias\EmailValidator\Warning\LabelTooLong;
|
||||
use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
|
||||
use Egulias\EmailValidator\Warning\TLD;
|
||||
|
||||
class DomainPart extends Parser
|
||||
{
|
||||
const DOMAIN_MAX_LENGTH = 254;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $domainPart = '';
|
||||
|
||||
public function parse($domainPart)
|
||||
{
|
||||
$this->lexer->moveNext();
|
||||
|
||||
$this->performDomainStartChecks();
|
||||
|
||||
$domain = $this->doParseDomainPart();
|
||||
|
||||
$prev = $this->lexer->getPrevious();
|
||||
$length = strlen($domain);
|
||||
|
||||
if ($prev['type'] === EmailLexer::S_DOT) {
|
||||
throw new DotAtEnd();
|
||||
}
|
||||
if ($prev['type'] === EmailLexer::S_HYPHEN) {
|
||||
throw new DomainHyphened();
|
||||
}
|
||||
if ($length > self::DOMAIN_MAX_LENGTH) {
|
||||
$this->warnings[DomainTooLong::CODE] = new DomainTooLong();
|
||||
}
|
||||
if ($prev['type'] === EmailLexer::S_CR) {
|
||||
throw new CRLFAtTheEnd();
|
||||
}
|
||||
$this->domainPart = $domain;
|
||||
}
|
||||
|
||||
private function performDomainStartChecks()
|
||||
{
|
||||
$this->checkInvalidTokensAfterAT();
|
||||
$this->checkEmptyDomain();
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
|
||||
$this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
|
||||
$this->parseDomainComments();
|
||||
}
|
||||
}
|
||||
|
||||
private function checkEmptyDomain()
|
||||
{
|
||||
$thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
|
||||
($this->lexer->token['type'] === EmailLexer::S_SP &&
|
||||
!$this->lexer->isNextToken(EmailLexer::GENERIC));
|
||||
|
||||
if ($thereIsNoDomain) {
|
||||
throw new NoDomainPart();
|
||||
}
|
||||
}
|
||||
|
||||
private function checkInvalidTokensAfterAT()
|
||||
{
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
|
||||
throw new DotAtStart();
|
||||
}
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
|
||||
throw new DomainHyphened();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDomainPart()
|
||||
{
|
||||
return $this->domainPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $addressLiteral
|
||||
* @param int $maxGroups
|
||||
*/
|
||||
public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
|
||||
{
|
||||
$prev = $this->lexer->getPrevious();
|
||||
if ($prev['type'] === EmailLexer::S_COLON) {
|
||||
$this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
|
||||
}
|
||||
|
||||
$IPv6 = substr($addressLiteral, 5);
|
||||
//Daniel Marschall's new IPv6 testing strategy
|
||||
$matchesIP = explode(':', $IPv6);
|
||||
$groupCount = count($matchesIP);
|
||||
$colons = strpos($IPv6, '::');
|
||||
|
||||
if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
|
||||
$this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
|
||||
}
|
||||
|
||||
if ($colons === false) {
|
||||
// We need exactly the right number of groups
|
||||
if ($groupCount !== $maxGroups) {
|
||||
$this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ($colons !== strrpos($IPv6, '::')) {
|
||||
$this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
|
||||
// RFC 4291 allows :: at the start or end of an address
|
||||
//with 7 other groups in addition
|
||||
++$maxGroups;
|
||||
}
|
||||
|
||||
if ($groupCount > $maxGroups) {
|
||||
$this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
|
||||
} elseif ($groupCount === $maxGroups) {
|
||||
$this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function doParseDomainPart()
|
||||
{
|
||||
$domain = '';
|
||||
$openedParenthesis = 0;
|
||||
do {
|
||||
$prev = $this->lexer->getPrevious();
|
||||
|
||||
$this->checkNotAllowedChars($this->lexer->token);
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
|
||||
$this->parseComments();
|
||||
$openedParenthesis += $this->getOpenedParenthesis();
|
||||
$this->lexer->moveNext();
|
||||
$tmpPrev = $this->lexer->getPrevious();
|
||||
if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
|
||||
$openedParenthesis--;
|
||||
}
|
||||
}
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
|
||||
if ($openedParenthesis === 0) {
|
||||
throw new UnopenedComment();
|
||||
} else {
|
||||
$openedParenthesis--;
|
||||
}
|
||||
}
|
||||
|
||||
$this->checkConsecutiveDots();
|
||||
$this->checkDomainPartExceptions($prev);
|
||||
|
||||
if ($this->hasBrackets()) {
|
||||
$this->parseDomainLiteral();
|
||||
}
|
||||
|
||||
$this->checkLabelLength($prev);
|
||||
|
||||
if ($this->isFWS()) {
|
||||
$this->parseFWS();
|
||||
}
|
||||
|
||||
$domain .= $this->lexer->token['value'];
|
||||
$this->lexer->moveNext();
|
||||
} while (null !== $this->lexer->token['type']);
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
private function checkNotAllowedChars(array $token)
|
||||
{
|
||||
$notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
|
||||
if (isset($notAllowed[$token['type']])) {
|
||||
throw new CharNotAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
protected function parseDomainLiteral()
|
||||
{
|
||||
if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
|
||||
$this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
|
||||
}
|
||||
if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
|
||||
$lexer = clone $this->lexer;
|
||||
$lexer->moveNext();
|
||||
if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
|
||||
$this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->doParseDomainLiteral();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
protected function doParseDomainLiteral()
|
||||
{
|
||||
$IPv6TAG = false;
|
||||
$addressLiteral = '';
|
||||
do {
|
||||
if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
|
||||
throw new ExpectingDTEXT();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::INVALID ||
|
||||
$this->lexer->token['type'] === EmailLexer::C_DEL ||
|
||||
$this->lexer->token['type'] === EmailLexer::S_LF
|
||||
) {
|
||||
$this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
|
||||
throw new ExpectingDTEXT();
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextTokenAny(
|
||||
array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
|
||||
)) {
|
||||
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
|
||||
$this->parseFWS();
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
|
||||
throw new CRNoLF();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
|
||||
$this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
|
||||
$addressLiteral .= $this->lexer->token['value'];
|
||||
$this->lexer->moveNext();
|
||||
$this->validateQuotedPair();
|
||||
}
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
|
||||
$IPv6TAG = true;
|
||||
}
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
|
||||
break;
|
||||
}
|
||||
|
||||
$addressLiteral .= $this->lexer->token['value'];
|
||||
|
||||
} while ($this->lexer->moveNext());
|
||||
|
||||
$addressLiteral = str_replace('[', '', $addressLiteral);
|
||||
$addressLiteral = $this->checkIPV4Tag($addressLiteral);
|
||||
|
||||
if (false === $addressLiteral) {
|
||||
return $addressLiteral;
|
||||
}
|
||||
|
||||
if (!$IPv6TAG) {
|
||||
$this->warnings[DomainLiteral::CODE] = new DomainLiteral();
|
||||
return $addressLiteral;
|
||||
}
|
||||
|
||||
$this->warnings[AddressLiteral::CODE] = new AddressLiteral();
|
||||
|
||||
$this->checkIPV6Tag($addressLiteral);
|
||||
|
||||
return $addressLiteral;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $addressLiteral
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected function checkIPV4Tag($addressLiteral)
|
||||
{
|
||||
$matchesIP = array();
|
||||
|
||||
// Extract IPv4 part from the end of the address-literal (if there is one)
|
||||
if (preg_match(
|
||||
'/\\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
|
||||
) > 0
|
||||
) {
|
||||
$index = strrpos($addressLiteral, $matchesIP[0]);
|
||||
if ($index === 0) {
|
||||
$this->warnings[AddressLiteral::CODE] = new AddressLiteral();
|
||||
return false;
|
||||
}
|
||||
// Convert IPv4 part to IPv6 format for further testing
|
||||
$addressLiteral = substr($addressLiteral, 0, (int) $index) . '0:0';
|
||||
}
|
||||
|
||||
return $addressLiteral;
|
||||
}
|
||||
|
||||
protected function checkDomainPartExceptions(array $prev)
|
||||
{
|
||||
$invalidDomainTokens = array(
|
||||
EmailLexer::S_DQUOTE => true,
|
||||
EmailLexer::S_SEMICOLON => true,
|
||||
EmailLexer::S_GREATERTHAN => true,
|
||||
EmailLexer::S_LOWERTHAN => true,
|
||||
);
|
||||
|
||||
if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
|
||||
throw new CommaInDomain();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
|
||||
throw new ConsecutiveAt();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
|
||||
throw new DomainHyphened();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
|
||||
&& $this->lexer->isNextToken(EmailLexer::GENERIC)) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasBrackets()
|
||||
{
|
||||
if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->lexer->find(EmailLexer::S_CLOSEBRACKET);
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new ExpectingDomainLiteralClose();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function checkLabelLength(array $prev)
|
||||
{
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
|
||||
$prev['type'] === EmailLexer::GENERIC &&
|
||||
strlen($prev['value']) > 63
|
||||
) {
|
||||
$this->warnings[LabelTooLong::CODE] = new LabelTooLong();
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseDomainComments()
|
||||
{
|
||||
$this->isUnclosedComment();
|
||||
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
|
||||
$this->warnEscaping();
|
||||
$this->lexer->moveNext();
|
||||
}
|
||||
|
||||
$this->lexer->moveNext();
|
||||
if ($this->lexer->isNextToken(EmailLexer::S_DOT)) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
}
|
||||
|
||||
protected function addTLDWarnings()
|
||||
{
|
||||
if ($this->warnings[DomainLiteral::CODE]) {
|
||||
$this->warnings[TLD::CODE] = new TLD();
|
||||
}
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Parser;
|
||||
|
||||
use Egulias\EmailValidator\Exception\DotAtEnd;
|
||||
use Egulias\EmailValidator\Exception\DotAtStart;
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Exception\ExpectingAT;
|
||||
use Egulias\EmailValidator\Exception\ExpectingATEXT;
|
||||
use Egulias\EmailValidator\Exception\UnclosedQuotedString;
|
||||
use Egulias\EmailValidator\Exception\UnopenedComment;
|
||||
use Egulias\EmailValidator\Warning\CFWSWithFWS;
|
||||
use Egulias\EmailValidator\Warning\LocalTooLong;
|
||||
|
||||
class LocalPart extends Parser
|
||||
{
|
||||
public function parse($localPart)
|
||||
{
|
||||
$parseDQuote = true;
|
||||
$closingQuote = false;
|
||||
$openedParenthesis = 0;
|
||||
$totalLength = 0;
|
||||
|
||||
while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) {
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']) {
|
||||
throw new DotAtStart();
|
||||
}
|
||||
|
||||
$closingQuote = $this->checkDQUOTE($closingQuote);
|
||||
if ($closingQuote && $parseDQuote) {
|
||||
$parseDQuote = $this->parseDoubleQuote();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
|
||||
$this->parseComments();
|
||||
$openedParenthesis += $this->getOpenedParenthesis();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
|
||||
if ($openedParenthesis === 0) {
|
||||
throw new UnopenedComment();
|
||||
}
|
||||
|
||||
$openedParenthesis--;
|
||||
}
|
||||
|
||||
$this->checkConsecutiveDots();
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
|
||||
$this->lexer->isNextToken(EmailLexer::S_AT)
|
||||
) {
|
||||
throw new DotAtEnd();
|
||||
}
|
||||
|
||||
$this->warnEscaping();
|
||||
$this->isInvalidToken($this->lexer->token, $closingQuote);
|
||||
|
||||
if ($this->isFWS()) {
|
||||
$this->parseFWS();
|
||||
}
|
||||
|
||||
$totalLength += strlen($this->lexer->token['value']);
|
||||
$this->lexer->moveNext();
|
||||
}
|
||||
|
||||
if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) {
|
||||
$this->warnings[LocalTooLong::CODE] = new LocalTooLong();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function parseDoubleQuote()
|
||||
{
|
||||
$parseAgain = true;
|
||||
$special = array(
|
||||
EmailLexer::S_CR => true,
|
||||
EmailLexer::S_HTAB => true,
|
||||
EmailLexer::S_LF => true
|
||||
);
|
||||
|
||||
$invalid = array(
|
||||
EmailLexer::C_NUL => true,
|
||||
EmailLexer::S_HTAB => true,
|
||||
EmailLexer::S_CR => true,
|
||||
EmailLexer::S_LF => true
|
||||
);
|
||||
$setSpecialsWarning = true;
|
||||
|
||||
$this->lexer->moveNext();
|
||||
|
||||
while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && null !== $this->lexer->token['type']) {
|
||||
$parseAgain = false;
|
||||
if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) {
|
||||
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
|
||||
$setSpecialsWarning = false;
|
||||
}
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) {
|
||||
$this->lexer->moveNext();
|
||||
}
|
||||
|
||||
$this->lexer->moveNext();
|
||||
|
||||
if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
}
|
||||
|
||||
$prev = $this->lexer->getPrevious();
|
||||
|
||||
if ($prev['type'] === EmailLexer::S_BACKSLASH) {
|
||||
if (!$this->checkDQUOTE(false)) {
|
||||
throw new UnclosedQuotedString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) {
|
||||
throw new ExpectingAT();
|
||||
}
|
||||
|
||||
return $parseAgain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $closingQuote
|
||||
*/
|
||||
protected function isInvalidToken(array $token, $closingQuote)
|
||||
{
|
||||
$forbidden = array(
|
||||
EmailLexer::S_COMMA,
|
||||
EmailLexer::S_CLOSEBRACKET,
|
||||
EmailLexer::S_OPENBRACKET,
|
||||
EmailLexer::S_GREATERTHAN,
|
||||
EmailLexer::S_LOWERTHAN,
|
||||
EmailLexer::S_COLON,
|
||||
EmailLexer::S_SEMICOLON,
|
||||
EmailLexer::INVALID
|
||||
);
|
||||
|
||||
if (in_array($token['type'], $forbidden) && !$closingQuote) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
}
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Parser;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Exception\AtextAfterCFWS;
|
||||
use Egulias\EmailValidator\Exception\ConsecutiveDot;
|
||||
use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
|
||||
use Egulias\EmailValidator\Exception\CRLFX2;
|
||||
use Egulias\EmailValidator\Exception\CRNoLF;
|
||||
use Egulias\EmailValidator\Exception\ExpectingQPair;
|
||||
use Egulias\EmailValidator\Exception\ExpectingATEXT;
|
||||
use Egulias\EmailValidator\Exception\ExpectingCTEXT;
|
||||
use Egulias\EmailValidator\Exception\UnclosedComment;
|
||||
use Egulias\EmailValidator\Exception\UnclosedQuotedString;
|
||||
use Egulias\EmailValidator\Warning\CFWSNearAt;
|
||||
use Egulias\EmailValidator\Warning\CFWSWithFWS;
|
||||
use Egulias\EmailValidator\Warning\Comment;
|
||||
use Egulias\EmailValidator\Warning\QuotedPart;
|
||||
use Egulias\EmailValidator\Warning\QuotedString;
|
||||
|
||||
abstract class Parser
|
||||
{
|
||||
/**
|
||||
* @var \Egulias\EmailValidator\Warning\Warning[]
|
||||
*/
|
||||
protected $warnings = [];
|
||||
|
||||
/**
|
||||
* @var EmailLexer
|
||||
*/
|
||||
protected $lexer;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $openedParenthesis = 0;
|
||||
|
||||
public function __construct(EmailLexer $lexer)
|
||||
{
|
||||
$this->lexer = $lexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Egulias\EmailValidator\Warning\Warning[]
|
||||
*/
|
||||
public function getWarnings()
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
*/
|
||||
abstract public function parse($str);
|
||||
|
||||
/** @return int */
|
||||
public function getOpenedParenthesis()
|
||||
{
|
||||
return $this->openedParenthesis;
|
||||
}
|
||||
|
||||
/**
|
||||
* validateQuotedPair
|
||||
*/
|
||||
protected function validateQuotedPair()
|
||||
{
|
||||
if (!($this->lexer->token['type'] === EmailLexer::INVALID
|
||||
|| $this->lexer->token['type'] === EmailLexer::C_DEL)) {
|
||||
throw new ExpectingQPair();
|
||||
}
|
||||
|
||||
$this->warnings[QuotedPart::CODE] =
|
||||
new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']);
|
||||
}
|
||||
|
||||
protected function parseComments()
|
||||
{
|
||||
$this->openedParenthesis = 1;
|
||||
$this->isUnclosedComment();
|
||||
$this->warnings[Comment::CODE] = new Comment();
|
||||
while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
|
||||
if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) {
|
||||
$this->openedParenthesis++;
|
||||
}
|
||||
$this->warnEscaping();
|
||||
$this->lexer->moveNext();
|
||||
}
|
||||
|
||||
$this->lexer->moveNext();
|
||||
if ($this->lexer->isNextTokenAny(array(EmailLexer::GENERIC, EmailLexer::S_EMPTY))) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextToken(EmailLexer::S_AT)) {
|
||||
$this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function isUnclosedComment()
|
||||
{
|
||||
try {
|
||||
$this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
|
||||
return true;
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new UnclosedComment();
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseFWS()
|
||||
{
|
||||
$previous = $this->lexer->getPrevious();
|
||||
|
||||
$this->checkCRLFInFWS();
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_CR) {
|
||||
throw new CRNoLF();
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) {
|
||||
throw new AtextAfterCFWS();
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) {
|
||||
throw new ExpectingCTEXT();
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) {
|
||||
$this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
|
||||
} else {
|
||||
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkConsecutiveDots()
|
||||
{
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
|
||||
throw new ConsecutiveDot();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function isFWS()
|
||||
{
|
||||
if ($this->escaped()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->lexer->token['type'] === EmailLexer::S_SP ||
|
||||
$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
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function escaped()
|
||||
{
|
||||
$previous = $this->lexer->getPrevious();
|
||||
|
||||
if ($previous && $previous['type'] === EmailLexer::S_BACKSLASH
|
||||
&&
|
||||
$this->lexer->token['type'] !== EmailLexer::GENERIC
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function warnEscaping()
|
||||
{
|
||||
if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
|
||||
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->warnings[QuotedPart::CODE] =
|
||||
new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $hasClosingQuote
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkDQUOTE($hasClosingQuote)
|
||||
{
|
||||
if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) {
|
||||
return $hasClosingQuote;
|
||||
}
|
||||
if ($hasClosingQuote) {
|
||||
return $hasClosingQuote;
|
||||
}
|
||||
$previous = $this->lexer->getPrevious();
|
||||
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) {
|
||||
throw new ExpectingATEXT();
|
||||
}
|
||||
|
||||
try {
|
||||
$this->lexer->find(EmailLexer::S_DQUOTE);
|
||||
$hasClosingQuote = true;
|
||||
} catch (\Exception $e) {
|
||||
throw new UnclosedQuotedString();
|
||||
}
|
||||
$this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']);
|
||||
|
||||
return $hasClosingQuote;
|
||||
}
|
||||
|
||||
protected function checkCRLFInFWS()
|
||||
{
|
||||
if ($this->lexer->token['type'] !== EmailLexer::CRLF) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
|
||||
throw new CRLFX2();
|
||||
}
|
||||
|
||||
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
|
||||
throw new CRLFAtTheEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
|
||||
use Egulias\EmailValidator\Exception\NoDNSRecord;
|
||||
|
||||
class DNSCheckValidation implements EmailValidation
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $warnings = [];
|
||||
|
||||
/**
|
||||
* @var InvalidEmail|null
|
||||
*/
|
||||
private $error;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!function_exists('idn_to_ascii')) {
|
||||
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
|
||||
}
|
||||
}
|
||||
|
||||
public function isValid($email, EmailLexer $emailLexer)
|
||||
{
|
||||
// use the input to check DNS if we cannot extract something similar to a domain
|
||||
$host = $email;
|
||||
|
||||
// Arguable pattern to extract the domain. Not aiming to validate the domain nor the email
|
||||
if (false !== $lastAtPos = strrpos($email, '@')) {
|
||||
$host = substr($email, $lastAtPos + 1);
|
||||
}
|
||||
|
||||
return $this->checkDNS($host);
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function getWarnings()
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $host
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkDNS($host)
|
||||
{
|
||||
$variant = INTL_IDNA_VARIANT_2003;
|
||||
if ( defined('INTL_IDNA_VARIANT_UTS46') ) {
|
||||
$variant = INTL_IDNA_VARIANT_UTS46;
|
||||
}
|
||||
$host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.';
|
||||
|
||||
$Aresult = true;
|
||||
$MXresult = checkdnsrr($host, 'MX');
|
||||
|
||||
if (!$MXresult) {
|
||||
$this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
|
||||
$Aresult = checkdnsrr($host, 'A') || checkdnsrr($host, 'AAAA');
|
||||
if (!$Aresult) {
|
||||
$this->error = new NoDNSRecord();
|
||||
}
|
||||
}
|
||||
return $MXresult || $Aresult;
|
||||
}
|
||||
}
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
use Egulias\EmailValidator\Warning\Warning;
|
||||
|
||||
interface EmailValidation
|
||||
{
|
||||
/**
|
||||
* Returns true if the given email is valid.
|
||||
*
|
||||
* @param string $email The email you want to validate.
|
||||
* @param EmailLexer $emailLexer The email lexer.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($email, EmailLexer $emailLexer);
|
||||
|
||||
/**
|
||||
* Returns the validation error.
|
||||
*
|
||||
* @return InvalidEmail|null
|
||||
*/
|
||||
public function getError();
|
||||
|
||||
/**
|
||||
* Returns the validation warnings.
|
||||
*
|
||||
* @return Warning[]
|
||||
*/
|
||||
public function getWarnings();
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation\Error;
|
||||
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
|
||||
class RFCWarnings extends InvalidEmail
|
||||
{
|
||||
const CODE = 997;
|
||||
const REASON = 'Warnings were found.';
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation\Error;
|
||||
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
|
||||
class SpoofEmail extends InvalidEmail
|
||||
{
|
||||
const CODE = 998;
|
||||
const REASON = "The email contains mixed UTF8 chars that makes it suspicious";
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class EmptyValidationList extends \InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* @param int $code
|
||||
*/
|
||||
public function __construct($code = 0, Exception $previous = null)
|
||||
{
|
||||
parent::__construct("Empty validation list is not allowed", $code, $previous);
|
||||
}
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
|
||||
class MultipleErrors extends InvalidEmail
|
||||
{
|
||||
const CODE = 999;
|
||||
const REASON = "Accumulated errors for multiple validations";
|
||||
/**
|
||||
* @var InvalidEmail[]
|
||||
*/
|
||||
private $errors = [];
|
||||
|
||||
/**
|
||||
* @param InvalidEmail[] $errors
|
||||
*/
|
||||
public function __construct(array $errors)
|
||||
{
|
||||
$this->errors = $errors;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvalidEmail[]
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Validation\Exception\EmptyValidationList;
|
||||
|
||||
class MultipleValidationWithAnd implements EmailValidation
|
||||
{
|
||||
/**
|
||||
* If one of validations gets failure skips all succeeding validation.
|
||||
* This means MultipleErrors will only contain a single error which first found.
|
||||
*/
|
||||
const STOP_ON_ERROR = 0;
|
||||
|
||||
/**
|
||||
* All of validations will be invoked even if one of them got failure.
|
||||
* So MultipleErrors will contain all causes.
|
||||
*/
|
||||
const ALLOW_ALL_ERRORS = 1;
|
||||
|
||||
/**
|
||||
* @var EmailValidation[]
|
||||
*/
|
||||
private $validations = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $warnings = [];
|
||||
|
||||
/**
|
||||
* @var MultipleErrors|null
|
||||
*/
|
||||
private $error;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $mode;
|
||||
|
||||
/**
|
||||
* @param EmailValidation[] $validations The validations.
|
||||
* @param int $mode The validation mode (one of the constants).
|
||||
*/
|
||||
public function __construct(array $validations, $mode = self::ALLOW_ALL_ERRORS)
|
||||
{
|
||||
if (count($validations) == 0) {
|
||||
throw new EmptyValidationList();
|
||||
}
|
||||
|
||||
$this->validations = $validations;
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isValid($email, EmailLexer $emailLexer)
|
||||
{
|
||||
$result = true;
|
||||
$errors = [];
|
||||
foreach ($this->validations as $validation) {
|
||||
$emailLexer->reset();
|
||||
$validationResult = $validation->isValid($email, $emailLexer);
|
||||
$result = $result && $validationResult;
|
||||
$this->warnings = array_merge($this->warnings, $validation->getWarnings());
|
||||
$errors = $this->addNewError($validation->getError(), $errors);
|
||||
|
||||
if ($this->shouldStop($result)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
$this->error = new MultipleErrors($errors);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Egulias\EmailValidator\Exception\InvalidEmail|null $possibleError
|
||||
* @param \Egulias\EmailValidator\Exception\InvalidEmail[] $errors
|
||||
*
|
||||
* @return \Egulias\EmailValidator\Exception\InvalidEmail[]
|
||||
*/
|
||||
private function addNewError($possibleError, array $errors)
|
||||
{
|
||||
if (null !== $possibleError) {
|
||||
$errors[] = $possibleError;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $result
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldStop($result)
|
||||
{
|
||||
return !$result && $this->mode === self::STOP_ON_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the validation errors.
|
||||
*
|
||||
* @return MultipleErrors|null
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWarnings()
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
use Egulias\EmailValidator\Validation\Error\RFCWarnings;
|
||||
|
||||
class NoRFCWarningsValidation extends RFCValidation
|
||||
{
|
||||
/**
|
||||
* @var InvalidEmail|null
|
||||
*/
|
||||
private $error;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isValid($email, EmailLexer $emailLexer)
|
||||
{
|
||||
if (!parent::isValid($email, $emailLexer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($this->getWarnings())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->error = new RFCWarnings();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error ?: parent::getError();
|
||||
}
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\EmailParser;
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
|
||||
class RFCValidation implements EmailValidation
|
||||
{
|
||||
/**
|
||||
* @var EmailParser|null
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $warnings = [];
|
||||
|
||||
/**
|
||||
* @var InvalidEmail|null
|
||||
*/
|
||||
private $error;
|
||||
|
||||
public function isValid($email, EmailLexer $emailLexer)
|
||||
{
|
||||
$this->parser = new EmailParser($emailLexer);
|
||||
try {
|
||||
$this->parser->parse((string)$email);
|
||||
} catch (InvalidEmail $invalid) {
|
||||
$this->error = $invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->warnings = $this->parser->getWarnings();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function getWarnings()
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
}
|
||||
user/plugins/email/vendor/egulias/email-validator/EmailValidator/Validation/SpoofCheckValidation.php
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Validation;
|
||||
|
||||
use Egulias\EmailValidator\EmailLexer;
|
||||
use Egulias\EmailValidator\Exception\InvalidEmail;
|
||||
use Egulias\EmailValidator\Validation\Error\SpoofEmail;
|
||||
use \Spoofchecker;
|
||||
|
||||
class SpoofCheckValidation implements EmailValidation
|
||||
{
|
||||
/**
|
||||
* @var InvalidEmail|null
|
||||
*/
|
||||
private $error;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!extension_loaded('intl')) {
|
||||
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress InvalidArgument
|
||||
*/
|
||||
public function isValid($email, EmailLexer $emailLexer)
|
||||
{
|
||||
$checker = new Spoofchecker();
|
||||
$checker->setChecks(Spoofchecker::SINGLE_SCRIPT);
|
||||
|
||||
if ($checker->isSuspicious($email)) {
|
||||
$this->error = new SpoofEmail();
|
||||
}
|
||||
|
||||
return $this->error === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvalidEmail|null
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function getWarnings()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class AddressLiteral extends Warning
|
||||
{
|
||||
const CODE = 12;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Address literal in domain part';
|
||||
$this->rfcNumber = 5321;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class CFWSNearAt extends Warning
|
||||
{
|
||||
const CODE = 49;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = "Deprecated folding white space near @";
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class CFWSWithFWS extends Warning
|
||||
{
|
||||
const CODE = 18;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Folding whites space followed by folding white space';
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class Comment extends Warning
|
||||
{
|
||||
const CODE = 17;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = "Comments found in this email";
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class DeprecatedComment extends Warning
|
||||
{
|
||||
const CODE = 37;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Deprecated comments';
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class DomainLiteral extends Warning
|
||||
{
|
||||
const CODE = 70;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Domain Literal';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class DomainTooLong extends Warning
|
||||
{
|
||||
const CODE = 255;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Domain is too long, exceeds 255 chars';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
use Egulias\EmailValidator\EmailParser;
|
||||
|
||||
class EmailTooLong extends Warning
|
||||
{
|
||||
const CODE = 66;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Email is too long, exceeds ' . EmailParser::EMAIL_MAX_LENGTH;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class IPV6BadChar extends Warning
|
||||
{
|
||||
const CODE = 74;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Bad char in IPV6 domain literal';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class IPV6ColonEnd extends Warning
|
||||
{
|
||||
const CODE = 77;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = ':: found at the end of the domain literal';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class IPV6ColonStart extends Warning
|
||||
{
|
||||
const CODE = 76;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = ':: found at the start of the domain literal';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class IPV6Deprecated extends Warning
|
||||
{
|
||||
const CODE = 13;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Deprecated form of IPV6';
|
||||
$this->rfcNumber = 5321;
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class IPV6DoubleColon extends Warning
|
||||
{
|
||||
const CODE = 73;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Double colon found after IPV6 tag';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class IPV6GroupCount extends Warning
|
||||
{
|
||||
const CODE = 72;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Group count is not IPV6 valid';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class IPV6MaxGroups extends Warning
|
||||
{
|
||||
const CODE = 75;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Reached the maximum number of IPV6 groups allowed';
|
||||
$this->rfcNumber = 5321;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class LabelTooLong extends Warning
|
||||
{
|
||||
const CODE = 63;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Label too long';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class LocalTooLong extends Warning
|
||||
{
|
||||
const CODE = 64;
|
||||
const LOCAL_PART_LENGTH = 64;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'Local part is too long, exceeds 64 chars (octets)';
|
||||
$this->rfcNumber = 5322;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class NoDNSMXRecord extends Warning
|
||||
{
|
||||
const CODE = 6;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = 'No MX DSN record was found for this email';
|
||||
$this->rfcNumber = 5321;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class ObsoleteDTEXT extends Warning
|
||||
{
|
||||
const CODE = 71;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->rfcNumber = 5322;
|
||||
$this->message = 'Obsolete DTEXT in domain literal';
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class QuotedPart extends Warning
|
||||
{
|
||||
const CODE = 36;
|
||||
|
||||
/**
|
||||
* @param scalar $prevToken
|
||||
* @param scalar $postToken
|
||||
*/
|
||||
public function __construct($prevToken, $postToken)
|
||||
{
|
||||
$this->message = "Deprecated Quoted String found between $prevToken and $postToken";
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class QuotedString extends Warning
|
||||
{
|
||||
const CODE = 11;
|
||||
|
||||
/**
|
||||
* @param scalar $prevToken
|
||||
* @param scalar $postToken
|
||||
*/
|
||||
public function __construct($prevToken, $postToken)
|
||||
{
|
||||
$this->message = "Quoted String found between $prevToken and $postToken";
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
class TLD extends Warning
|
||||
{
|
||||
const CODE = 9;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = "RFC5321, TLD";
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Egulias\EmailValidator\Warning;
|
||||
|
||||
abstract class Warning
|
||||
{
|
||||
const CODE = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $rfcNumber = 0;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function code()
|
||||
{
|
||||
return self::CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function RFCNumber()
|
||||
{
|
||||
return $this->rfcNumber;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->message() . " rfc: " . $this->rfcNumber . "interal code: " . static::CODE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2013-2016 Eduardo Gulias Davis
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,82 @@
|
||||
# EmailValidator
|
||||
[](https://travis-ci.org/egulias/EmailValidator) [](https://coveralls.io/r/egulias/EmailValidator?branch=master) [](https://scrutinizer-ci.com/g/egulias/EmailValidator/?branch=master) [](https://insight.sensiolabs.com/projects/22ba6692-9c02-42e5-a65d-1c5696bfffc6)
|
||||
=============================
|
||||
## Suported RFCs ##
|
||||
This library aims to support:
|
||||
|
||||
RFC 5321, 5322, 6530, 6531, 6532.
|
||||
|
||||
## Requirements ##
|
||||
|
||||
* [Composer](https://getcomposer.org) is required for installation
|
||||
* [Spoofchecking](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/SpoofCheckValidation.php) and [DNSCheckValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/DNSCheckValidation.php) validation requires that your PHP system has the [PHP Internationalization Libraries](https://php.net/manual/en/book.intl.php) (also known as PHP Intl)
|
||||
|
||||
## Installation ##
|
||||
|
||||
Run the command below to install via Composer
|
||||
|
||||
```shell
|
||||
composer require egulias/email-validator
|
||||
```
|
||||
|
||||
## Getting Started ##
|
||||
`EmailValidator`requires you to decide which (or combination of them) validation/s strategy/ies you'd like to follow for each [validation](#available-validations).
|
||||
|
||||
A basic example with the RFC validation
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Egulias\EmailValidator\EmailValidator;
|
||||
use Egulias\EmailValidator\Validation\RFCValidation;
|
||||
|
||||
$validator = new EmailValidator();
|
||||
$validator->isValid("example@example.com", new RFCValidation()); //true
|
||||
```
|
||||
|
||||
|
||||
### Available validations ###
|
||||
|
||||
1. [RFCValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/RFCValidation.php)
|
||||
2. [NoRFCWarningsValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/NoRFCWarningsValidation.php)
|
||||
3. [DNSCheckValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/DNSCheckValidation.php)
|
||||
4. [SpoofCheckValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/SpoofCheckValidation.php)
|
||||
5. [MultipleValidationWithAnd](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/MultipleValidationWithAnd.php)
|
||||
6. [Your own validation](#how-to-extend)
|
||||
|
||||
`MultipleValidationWithAnd`
|
||||
|
||||
It is a validation that operates over other validations performing a logical and (&&) over the result of each validation.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Egulias\EmailValidator\EmailValidator;
|
||||
use Egulias\EmailValidator\Validation\DNSCheckValidation;
|
||||
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
|
||||
use Egulias\EmailValidator\Validation\RFCValidation;
|
||||
|
||||
$validator = new EmailValidator();
|
||||
$multipleValidations = new MultipleValidationWithAnd([
|
||||
new RFCValidation(),
|
||||
new DNSCheckValidation()
|
||||
]);
|
||||
$validator->isValid("example@example.com", $multipleValidations); //true
|
||||
```
|
||||
|
||||
### How to extend ###
|
||||
|
||||
It's easy! You just need to implement [EmailValidation](https://github.com/egulias/EmailValidator/blob/master/EmailValidator/Validation/EmailValidation.php) and you can use your own validation.
|
||||
|
||||
|
||||
## Other Contributors ##
|
||||
(You can find current contributors [here](https://github.com/egulias/EmailValidator/graphs/contributors))
|
||||
|
||||
As this is a port from another library and work, here are other people related to the previous one:
|
||||
|
||||
* Ricard Clau [@ricardclau](https://github.com/ricardclau): Performance against PHP built-in filter_var
|
||||
* Josepf Bielawski [@stloyd](https://github.com/stloyd): For its first re-work of Dominic's lib
|
||||
* Dominic Sayers [@dominicsayers](https://github.com/dominicsayers): The original isemail function
|
||||
|
||||
## License ##
|
||||
Released under the MIT License attached with this code.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
"description": "A library for validating emails against several RFCs",
|
||||
"homepage": "https://github.com/egulias/EmailValidator",
|
||||
"keywords": ["email", "validation", "validator", "emailvalidation", "emailvalidator"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{"name": "Eduardo Gulias Davis"}
|
||||
],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1.x-dev"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5",
|
||||
"doctrine/lexer": "^1.0.1",
|
||||
"symfony/polyfill-intl-idn": "^1.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"satooshi/php-coveralls": "^1.0.1",
|
||||
"phpunit/phpunit": "^4.8.36|^7.5.15",
|
||||
"dominicsayers/isemail": "^3.0.7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Egulias\\EmailValidator\\": "EmailValidator"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Egulias\\Tests\\": "Tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="EmailValidator Test Suite">
|
||||
<directory>./Tests/EmailValidator</directory>
|
||||
<exclude>./vendor/</exclude>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./EmailValidator/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="3.8.3@389af1bfc739bfdff3f9e3dc7bd6499aee51a831">
|
||||
<file src="EmailValidator/EmailLexer.php">
|
||||
<DocblockTypeContradiction occurrences="1">
|
||||
<code>self::$nullToken</code>
|
||||
</DocblockTypeContradiction>
|
||||
</file>
|
||||
<file src="EmailValidator/Parser/Parser.php">
|
||||
<MissingReturnType occurrences="1">
|
||||
<code>parse</code>
|
||||
</MissingReturnType>
|
||||
</file>
|
||||
<file src="EmailValidator/Validation/SpoofCheckValidation.php">
|
||||
<UndefinedClass occurrences="2">
|
||||
<code>Spoofchecker</code>
|
||||
<code>Spoofchecker</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
</files>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
requireVoidReturnType="false"
|
||||
totallyTyped="false"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config ./vendor/vimeo/psalm/config.xsd"
|
||||
errorBaseline="./psalm.baseline.xml"
|
||||
>
|
||||
<projectFiles>
|
||||
<directory name="EmailValidator" />
|
||||
<ignoreFiles>
|
||||
<directory name="vendor" />
|
||||
</ignoreFiles>
|
||||
</projectFiles>
|
||||
|
||||
<issueHandlers>
|
||||
</issueHandlers>
|
||||
</psalm>
|
||||
@@ -0,0 +1,9 @@
|
||||
*.crt -crlf
|
||||
*.key -crlf
|
||||
*.srl -crlf
|
||||
*.pub -crlf
|
||||
*.priv -crlf
|
||||
*.txt -crlf
|
||||
|
||||
# ignore /notes in the git-generated distributed .zip archive
|
||||
/doc/notes export-ignore
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user