Completed
Push — master ( 45312b...26f5f6 )
by Peter
06:31
created

EmailValidator::isValid()   D

Complexity

Conditions 10
Paths 13

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 12.332

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 40
ccs 15
cts 21
cp 0.7143
rs 4.8196
c 1
b 0
f 0
cc 10
eloc 21
nc 13
nop 2
crap 12.332

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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