Passed
Pull Request — master (#41)
by Alexander
02:18
created

Email::__construct()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4.128

Importance

Changes 0
Metric Value
cc 4
eloc 4
nc 3
nop 0
dl 0
loc 7
ccs 4
cts 5
cp 0.8
crap 4.128
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Yiisoft\Validator\Rule;
3
4
use Yiisoft\Validator\DataSetInterface;
5
use Yiisoft\Validator\Result;
6
use Yiisoft\Validator\Rule;
7
8
/**
9
 * EmailValidator validates that the attribute value is a valid email address.
10
 */
11
class Email extends Rule
12
{
13
    /**
14
     * @var string the regular expression used to validateValue the attribute value.
15
     * @see http://www.regular-expressions.info/email.html
16
     */
17
    private $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/';
18
    /**
19
     * @var string the regular expression used to validateValue email addresses with the name part.
20
     * This property is used only when [[allowName]] is true.
21
     * @see allowName
22
     */
23
    private $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/';
24
    /**
25
     * @var bool whether to allow name in the email address (e.g. "John Smith <[email protected]>"). Defaults to false.
26
     * @see fullPattern
27
     */
28
    private $allowName = false;
29
    /**
30
     * @var bool whether to check whether the email's domain exists and has either an A or MX record.
31
     * Be aware that this check can fail due to temporary DNS problems even if the email address is
32
     * valid and an email would be deliverable. Defaults to false.
33
     */
34
    private $checkDNS = false;
35
    /**
36
     * @var bool whether validation process should take into account IDN (internationalized domain
37
     * names). Defaults to false meaning that validation of emails containing IDN will always fail.
38
     * Note that in order to use IDN validation you have to install and enable `intl` PHP extension,
39
     * otherwise an exception would be thrown.
40
     */
41
    private $enableIDN = false;
42
43
44
    private $message;
45
46 37
    public function __construct()
47
    {
48 37
        if ($this->enableIDN && !function_exists('idn_to_ascii')) {
49
            throw new \RuntimeException('In order to use IDN validation intl extension must be installed and enabled.');
50
        }
51 37
        if ($this->message === null) {
52 37
            $this->message = $this->formatMessage('{attribute} is not a valid email address.');
53
        }
54
    }
55
56 37
    protected function validateValue($value, DataSetInterface $dataSet = null): Result
57
    {
58 37
        $result = new Result();
59
60 37
        if (!is_string($value)) {
61 1
            $valid = false;
62 37
        } elseif (!preg_match('/^(?P<name>(?:"?([^"]*)"?\s)?)(?:\s+)?(?:(?P<open><?)((?P<local>.+)@(?P<domain>[^>]+))(?P<close>>?))$/i', $value, $matches)) {
63 2
            $valid = false;
64
        } else {
65 37
            if ($this->enableIDN) {
66 35
                $matches['local'] = $this->idnToAscii($matches['local']);
67 35
                $matches['domain'] = $this->idnToAscii($matches['domain']);
68 35
                $value = $matches['name'] . $matches['open'] . $matches['local'] . '@' . $matches['domain'] . $matches['close'];
69
            }
70
71 37
            if (strlen($matches['local']) > 64) {
72
                // The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1
73
                // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
74 1
                $valid = false;
75 37
            } elseif (strlen($matches['local'] . '@' . $matches['domain']) > 254) {
76
                // There is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands
77
                // of 254 characters. Since addresses that do not fit in those fields are not normally useful, the
78
                // upper limit on address lengths should normally be considered to be 254.
79
                //
80
                // Dominic Sayers, RFC 3696 erratum 1690
81
                // http://www.rfc-editor.org/errata_search.php?eid=1690
82 1
                $valid = false;
83
            } else {
84 37
                $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
85 37
                if ($valid && $this->checkDNS) {
86 1
                    $valid = checkdnsrr($matches['domain'] . '.', 'MX') || checkdnsrr($matches['domain'] . '.', 'A');
87
                }
88
            }
89
        }
90
91
92 37
        if ($valid === false) {
93 37
            $result = $result->addError($this->message ?? $this->formatMessage('{attribute} is not a valid email address.'));
94
        }
95
96 37
        return $result;
97
    }
98
99 35
    private function idnToAscii($idn)
100
    {
101 35
        return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46);
102
    }
103
104
    /**
105
     * @param bool $allowName
106
     *
107
     * @return Email
108
     */
109 3
    public function allowName(bool $allowName): self
110
    {
111 3
        $this->allowName = $allowName;
112
113 3
        return $this;
114
    }
115
116
    /**
117
     * @param bool $checkDNS
118
     *
119
     * @return Email
120
     */
121 1
    public function checkDNS(bool $checkDNS): self
122
    {
123 1
        $this->checkDNS = $checkDNS;
124
125 1
        return $this;
126
    }
127
128
    /**
129
     * @param bool $enableIDN
130
     *
131
     * @return Email
132
     */
133 35
    public function enableIDN(bool $enableIDN): self
134
    {
135 35
        if ($enableIDN && !function_exists('idn_to_ascii')) {
136
            throw new \RuntimeException('In order to use IDN validation intl extension must be installed and enabled.');
137
        }
138
        
139 35
        $this->enableIDN = $enableIDN;
140
141 35
        return $this;
142
    }
143
144
    /**
145
     * @param string $message
146
     *
147
     * @return $this
148
     */
149
    public function message(string $message)
150
    {
151
        $this->message = $message;
152
153
        return $this;
154
    }
155
}
156