Passed
Pull Request — master (#194)
by
unknown
02:29
created

MailboxCheckValidation::isValid()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 12
cts 12
cp 1
rs 9.6666
c 0
b 0
f 0
cc 4
nc 6
nop 2
crap 4
1
<?php
2
3
namespace Egulias\EmailValidator\Validation;
4
5
use Egulias\EmailValidator\EmailLexer;
6
use Egulias\EmailValidator\Exception\InvalidEmail;
7
use Egulias\EmailValidator\Helper\SmtpSocketHelper;
8
use Egulias\EmailValidator\Validation\Error\IllegalMailbox;
9
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
10
use Egulias\EmailValidator\Warning\SocketWarning;
11
use Egulias\EmailValidator\Warning\Warning;
12
13
class MailboxCheckValidation implements EmailValidation
14
{
15
    const END_OF_LINE = "\r\n";
16
17
    /**
18
     * @var InvalidEmail
19
     */
20
    private $error;
21
22
    /**
23
     * @var Warning[]
24
     */
25
    private $warnings = [];
26
27
    /**
28
     * @var SmtpSocketHelper
29
     */
30
    private $socketHelper;
31
32
    /**
33
     * @var string
34
     */
35
    private $fromEmail;
36
37
    /**
38
     * @var int
39
     */
40
    private $lastResponseCode;
41
42
    /**
43
     * MailboxCheckValidation constructor.
44
     *
45
     * @param SmtpSocketHelper $socketHelper
46
     * @param string $fromEmail
47
     */
48 3
    public function __construct(SmtpSocketHelper $socketHelper, $fromEmail)
49
    {
50 3
        if (!extension_loaded('intl')) {
51
            throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
52
        }
53
54 3
        $this->socketHelper = $socketHelper;
55 3
        $this->fromEmail = $fromEmail;
56 3
    }
57
58
    /**
59
     * @inheritDoc
60
     */
61 1
    public function getError()
62
    {
63 1
        return $this->error;
64
    }
65
66
    /**
67
     * @inheritDoc
68
     */
69 1
    public function getWarnings()
70
    {
71 1
        return $this->warnings;
72
    }
73
74
    /**
75
     * @inheritDoc
76
     */
77 3
    public function isValid($email, EmailLexer $emailLexer)
78
    {
79 3
        $mxHosts = $this->getMXHosts($email);
80
81 3
        $isValid = false;
82 3
        foreach ($mxHosts as $mxHost) {
83 2
            if ($this->checkMailboxExists($mxHost, $email)) {
84 1
                $isValid = true;
85 1
                break;
86
            }
87 3
        }
88
89 3
        if (!$isValid) {
90 2
            $this->error = new IllegalMailbox($this->lastResponseCode);
91 2
        }
92
93 3
        return $this->error === null;
94
    }
95
96
    /**
97
     * Gets MX Hosts from email
98
     *
99
     * @param string $email
100
     *
101
     * @return array
102
     */
103 3
    protected function getMXHosts($email)
104
    {
105 3
        $hostname = $this->extractHostname($email);
106
107 3
        $result = getmxrr($hostname, $mxHosts);
108 3
        if (!$result) {
109 1
            $this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
110 1
        }
111
112 3
        return $mxHosts;
113
    }
114
115
    /**
116
     * Extracts hostname from email
117
     *
118
     * @param string $email
119
     *
120
     * @return string
121
     */
122 3
    private function extractHostname($email)
123
    {
124 3
        $variant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : INTL_IDNA_VARIANT_2003;
125
126 3
        $lastAtPos = strrpos($email, '@');
127 3
        if ((bool) $lastAtPos) {
128 3
            $hostname = substr($email, $lastAtPos + 1);
129 3
            return rtrim(idn_to_ascii($hostname, IDNA_DEFAULT, $variant), '.') . '.';
130
        }
131
132
        return rtrim(idn_to_ascii($email, IDNA_DEFAULT, $variant), '.') . '.';
133
    }
134
135
    /**
136
     * Checks mailbox
137
     *
138
     * @param string $hostname
139
      * @param string $email
140
     * @return bool
141
     */
142 2
    private function checkMailboxExists($hostname, $email)
143
    {
144 2
        $this->socketHelper->open($hostname, $errno, $errstr);
145
146 2
        if (!$this->socketHelper->isResource()) {
147
            $this->warnings[SocketWarning::CODE][] = new SocketWarning($hostname, $errno, $errstr);
148
149
            return false;
150
        }
151
152 2
        $this->lastResponseCode = $this->socketHelper->getResponseCode();
153 2
        if ($this->lastResponseCode !== 220) {
154
            return false;
155
        }
156
157 2
        $this->socketHelper->write("EHLO {$hostname}" . self::END_OF_LINE);
158 2
        $this->lastResponseCode = $this->socketHelper->getResponseCode();
159 2
        if ($this->lastResponseCode !== 250) {
160
            return false;
161
        }
162
163 2
        $this->socketHelper->write("MAIL FROM: <{$this->fromEmail}>" . self::END_OF_LINE);
164 2
        $this->lastResponseCode = $this->socketHelper->getResponseCode();
165 2
        if ($this->lastResponseCode !== 250) {
166
            return false;
167
        }
168
169 2
        $this->socketHelper->write("RCPT TO: <{$email}>" . self::END_OF_LINE);
170 2
        $this->lastResponseCode = $this->socketHelper->getResponseCode();
171 2
        if ($this->lastResponseCode !== 250) {
172 1
            return false;
173
        }
174
175 1
        $this->socketHelper->write('QUIT' . self::END_OF_LINE);
176
177 1
        $this->socketHelper->close();
178
179 1
        return true;
180
    }
181
}