Completed
Push — master ( 183075...9c0b34 )
by Peter
07:30
created

EmailValidator::checkMxPorts()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 0
cts 15
cp 0
rs 8.439
c 0
b 0
f 0
cc 5
eloc 15
nc 4
nop 1
crap 30
1
<?php
2
3
/**
4
 * This software package is licensed under AGPL or Commercial license.
5
 *
6
 * @package maslosoft/mangan
7
 * @licence AGPL or Commercial
8
 * @copyright Copyright (c) Piotr Masełkowski <[email protected]>
9
 * @copyright Copyright (c) Maslosoft
10
 * @copyright Copyright (c) Others as mentioned in code
11
 * @link https://maslosoft.com/mangan/
12
 */
13
14
namespace Maslosoft\Mangan\Validators\BuiltIn;
15
16
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
17
use Maslosoft\Mangan\Interfaces\Validators\ValidatorInterface;
18
use Maslosoft\Mangan\Meta\ManganMeta;
19
use Maslosoft\Mangan\Validators\Traits\AllowEmpty;
20
use Maslosoft\Mangan\Validators\Traits\Messages;
21
use Maslosoft\Mangan\Validators\Traits\OnScenario;
22
use Maslosoft\Mangan\Validators\Traits\Safe;
23
24
/**
25
 * EmailValidator
26
 *
27
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
28
 */
29
class EmailValidator implements ValidatorInterface
30
{
31
32
	use AllowEmpty,
33
	  Messages,
34
	  OnScenario,
35
	  Safe;
36
37
	const EmailPattern = '/^[\p{L}0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[\p{L}0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[\p{L}0-9](?:[\p{L}0-9-]*[\p{L}0-9])?\.)+[\p{L}0-9](?:[\p{L}0-9-]*[\p{L}0-9])?$/u';
38
	const FullEmailPattern = '/^[^@]*<[\p{L}0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\\.[\p{L}0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[\p{L}0-9](?:[\p{L}0-9-]*[\p{L}0-9])?\\.)+[\p{L}0-9](?:[\p{L}0-9-]*[\p{L}0-9])?>$/u';
39
40
	/**
41
	 * @var string the regular expression used to validate the attribute value.
42
	 * @see http://www.regular-expressions.info/email.html
43
	 */
44
	public $pattern = self::EmailPattern;
45
46
	/**
47
	 * @var boolean whether to check the MX record for the email address.
48
	 * Defaults to false. To enable it, you need to make sure the PHP function 'checkdnsrr'
49
	 * exists in your PHP installation.
50
	 * Please note that this check may fail due to temporary problems even if email is deliverable.
51
	 */
52
	public $checkMX = false;
53
54
	/**
55
	 * @var boolean whether to check port 25 for the email address.
56
	 * Defaults to false. To enable it, ensure that the PHP functions 'dns_get_record' and
57
	 * 'fsockopen' are available in your PHP installation.
58
	 * Please note that this check may fail due to temporary problems even if email is deliverable.
59
	 */
60
	public $checkPort = false;
61
62
	/**
63
	 * @Label('{attribute} must be valid email address')
64
	 * @var string
65
	 */
66
	public $msgValid = '';
67
68
	/**
69
	 * @Label('Email domain "{domain}" does not exists')
70
	 * @var string
71
	 */
72
	public $msgDomain = '';
73
74
	/**
75
	 * @Label('Email service does not seem to be running at "{domain}"')
76
	 * @var string
77
	 */
78
	public $msgPort = '';
79
80 6
	public function isValid(AnnotatedInterface $model, $attribute)
81
	{
82 6
		if ($this->allowEmpty && empty($model->$attribute))
83
		{
84 2
			return true;
85
		}
86 4
		$label = ManganMeta::create($model)->field($attribute)->label;
87
88 4
		if (!is_scalar($model->$attribute))
89
		{
90
			$this->addError('msgValid', ['{attribute}' => $label]);
91
			return false;
92
		}
93 4
		if (!preg_match($this->pattern, $model->$attribute))
94
		{
95 1
			$this->addError('msgValid', ['{attribute}' => $label]);
96 1
			return false;
97
		}
98 4
		$domain = rtrim(substr($model->$attribute, strpos($model->$attribute, '@') + 1), '>');
99 4
		if ($this->checkMX)
100
		{
101 1
			if (function_exists('checkdnsrr'))
102
			{
103 1
				if (!checkdnsrr($domain, 'MX'))
104
				{
105 1
					$this->addError('msgDomain', ['{domain}' => $domain]);
106 1
					return false;
107
				}
108
			}
109
		}
110 4
		if ($this->checkPort)
111
		{
112
			if ($this->checkMxPorts($domain))
113
			{
114
				$this->addError('msgPort', ['{domain}' => $domain]);
115
				return false;
116
			}
117
		}
118 4
		return true;
119
	}
120
121
	/**
122
	 * Retrieves the list of MX records for $domain and checks if port 25
123
	 * is opened on any of these.
124
	 * @param string $domain domain to be checked
125
	 * @return boolean true if a reachable MX server has been found
126
	 */
127
	protected function checkMxPorts($domain)
128
	{
129
		$records = dns_get_record($domain, DNS_MX);
130
		if ($records === false || empty($records))
131
		{
132
			return false;
133
		}
134
		$sort = function ($a, $b)
135
		{
136
			return $a['pri'] - $b['pri'];
137
		};
138
		usort($records, $sort);
139
		foreach ($records as $record)
140
		{
141
			$errno = null;
142
			$errstr = null;
143
			$handle = @fsockopen($record['target'], 25, $errno, $errstr, 3);
144
			if ($handle !== false)
145
			{
146
				fclose($handle);
147
				return true;
148
			}
149
		}
150
		return false;
151
	}
152
153
}
154