Completed
Push — master ( 1a52fa...33fb86 )
by Peter
16:06
created

EmailValidator   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 42.5%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 16
lcom 1
cbo 6
dl 0
loc 128
ccs 17
cts 40
cp 0.425
rs 10
c 3
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
C isValid() 0 46 11
B checkMxPorts() 0 25 5
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 http://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
20
/**
21
 * EmailValidator
22
 *
23
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
24
 */
25
class EmailValidator implements ValidatorInterface
26
{
27
28
	use \Maslosoft\Mangan\Validators\Traits\AllowEmpty,
29
	  \Maslosoft\Mangan\Validators\Traits\Messages,
30
	  \Maslosoft\Mangan\Validators\Traits\OnScenario,
31
	  \Maslosoft\Mangan\Validators\Traits\Safe;
32
33
	/**
34
	 * @var string the regular expression used to validate the attribute value.
35
	 * @see http://www.regular-expressions.info/email.html
36
	 */
37
	public $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])?$/';
38
39
	/**
40
	 * @var boolean whether to check the MX record for the email address.
41
	 * Defaults to false. To enable it, you need to make sure the PHP function 'checkdnsrr'
42
	 * exists in your PHP installation.
43
	 * Please note that this check may fail due to temporary problems even if email is deliverable.
44
	 */
45
	public $checkMX = false;
46
47
	/**
48
	 * @var boolean whether to check port 25 for the email address.
49
	 * Defaults to false. To enable it, ensure that the PHP functions 'dns_get_record' and
50
	 * 'fsockopen' are available in your PHP installation.
51
	 * Please note that this check may fail due to temporary problems even if email is deliverable.
52
	 */
53
	public $checkPort = false;
54
55
	/**
56
	 * @Label('{attribute} must be valid email address')
57
	 * @var string
58
	 */
59
	public $msgValid = '';
60
61
	/**
62
	 * @Label('Email domain "{domain}" does not exists')
63
	 * @var string
64
	 */
65
	public $msgDomain = '';
66
67
	/**
68
	 * @Label('Email service does not seem to be running at "{domain}"')
69
	 * @var string
70
	 */
71
	public $msgPort = '';
72
73 2
	public function isValid(AnnotatedInterface $model, $attribute)
74
	{
75 2
		if ($this->allowEmpty && empty($model->$attribute))
76
		{
77
			return true;
78
		}
79 2
		$label = ManganMeta::create($model)->field($attribute)->label;
80
81 2
		if (!is_scalar($model->$attribute))
82
		{
83
			$this->addError('msgValid', ['{attribute}' => $label]);
84
			return false;
85
		}
86 2
		$valid = filter_var($model->$attribute, FILTER_VALIDATE_EMAIL);
87 2
		if (!$valid)
88
		{
89 1
			$this->addError('msgValid', ['{attribute}' => $label]);
90 1
			return false;
91
		}
92 2
		if (!preg_match($this->pattern, $model->$attribute))
93
		{
94
			$this->addError('msgValid', ['{attribute}' => $label]);
95
			return false;
96
		}
97 2
		$domain = rtrim(substr($model->$attribute, strpos($model->$attribute, '@') + 1), '>');
98 2
		if ($this->checkMX)
99
		{
100 1
			if (function_exists('checkdnsrr'))
101
			{
102 1
				if (!checkdnsrr($domain, 'MX'))
103
				{
104 1
					$this->addError('msgDomain', ['{domain}' => $domain]);
105 1
					return false;
106
				}
107
			}
108
		}
109 2
		if ($this->checkPort)
110
		{
111
			if ($this->checkMxPorts($domain))
112
			{
113
				$this->addError('msgPort', ['{domain}' => $domain]);
114
				return false;
115
			}
116
		}
117 2
		return true;
118
	}
119
120
	/**
121
	 * Retrieves the list of MX records for $domain and checks if port 25
122
	 * is opened on any of these.
123
	 * @param string $domain domain to be checked
124
	 * @return boolean true if a reachable MX server has been found
125
	 */
126
	protected function checkMxPorts($domain)
127
	{
128
		$records = dns_get_record($domain, DNS_MX);
129
		if ($records === false || empty($records))
130
		{
131
			return false;
132
		}
133
		$sort = function ($a, $b)
134
		{
135
			return $a['pri'] - $b['pri'];
136
		};
137
		usort($records, $sort);
138
		foreach ($records as $record)
139
		{
140
			$errno = null;
141
			$errstr = null;
142
			$handle = @fsockopen($record['target'], 25, $errno, $errstr, 3);
143
			if ($handle !== false)
144
			{
145
				fclose($handle);
146
				return true;
147
			}
148
		}
149
		return false;
150
	}
151
152
}
153