Completed
Push — master ( 9ade78...6d5aeb )
by Propa
04:43
created

PhoneValidator::isCountryField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
ccs 0
cts 0
cp 0
cc 1
eloc 1
nc 1
nop 1
crap 2
1
<?php namespace Propaganistas\LaravelPhone;
2
3
use libphonenumber\NumberParseException;
4
use libphonenumber\PhoneNumberType;
5
use libphonenumber\PhoneNumberUtil;
6
use Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException;
7
use Propaganistas\LaravelPhone\Exceptions\InvalidParameterException;
8
9
class PhoneValidator
10
{
11
12
	/**
13
	 * @var \libphonenumber\PhoneNumberUtil
14
	 */
15
	protected $lib;
16
17
	/**
18
	 * Whether the country should be auto-detected.
19
	 *
20
	 * @var bool
21
	 */
22
	protected $autodetect = false;
23
24
	/**
25
	 * Whether to allow lenient checking of numbers (i.e. landline without area codes).
26
	 *
27
	 * @var bool
28
	 */
29
	protected $lenient = false;
30
31
	/**
32
	 * The field to use for country if not passed as a parameter.
33
	 *
34
	 * @var null|string
35
	 */
36
	protected $countryField = null;
37
38
	/**
39
	 * Countries to validate against.
40
	 *
41
	 * @var array
42
	 */
43
	protected $countries = [];
44
45
	/**
46
	 * Transformed phone number types to validate against.
47
	 *
48 18
	 * @var array
49
	 */
50 18
	protected $types = [];
51 16
52
	/**
53
	 * PhoneValidator constructor.
54
	 */
55
	public function __construct(PhoneNumberUtil $lib)
56
	{
57
		$this->lib = $lib;
58
	}
59
60
	/**
61
	 * Validates a phone number.
62
	 *
63
	 * @param  string $attribute
64 18
	 * @param  mixed  $value
65
	 * @param  array  $parameters
66 16
	 * @param  object $validator
67
	 * @return bool
68 15
	 * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
69
	 * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
70
	 */
71
	public function validatePhone($attribute, $value, array $parameters, $validator)
72 12
	{
73 4
		$countryField = $this->checkCountryField($parameters, $validator);
74 4
75
		$filterCountryFieldCallback = function($value) use ($countryField) {
76 12
			return $value !== $countryField;
77 12
		};
78 12
79
		$this->assignParameters(array_map('strtoupper', array_filter($parameters, $filterCountryFieldCallback)));
80 12
81
		$this->checkCountries($attribute, $validator);
82
83 12
		// If autodetecting, let's first try without a country.
84 2
		// Otherwise use provided countries as default.
85
		if ($this->autodetect || ($this->lenient && empty($this->countries))) {
86
			array_unshift($this->countries, null);
87
		}
88
89
		foreach ($this->countries as $country) {
90
			if ($this->isValidNumber($value, $country)) {
91
				return true;
92 18
			}
93
		}
94 18
95
		// All specified country validations have failed.
96 16
		return false;
97 12
	}
98 11
99 12
	/**
100 7
	 * Parses parameters to find one that matches a field for country input.
101 11
	 *
102 2
	 * @param array  $parameters
103 7
	 * @param object $validator
104 2
	 *
105 2
	 * @return null|string
106
	 */
107 2
	public function checkCountryField(array $parameters, $validator)
108
	{
109 17
		$data = array_dot($validator->getData());
110
111 14
		foreach ($parameters as $parameter) {
112
			if (isset($data[$parameter])) {
113 14
				return $this->countryField = $parameter;
114
			}
115
		}
116
117
		return null;
118
	}
119
120
	/**
121
	 * Parses the supplied validator parameters.
122
	 *
123
	 * @param array $parameters
124 15
	 * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
125
	 */
126 14
	protected function assignParameters(array $parameters)
127 15
	{
128
		$types = array();
129 15
130 6
		foreach ($parameters as $parameter) {
131 15
			if ($fieldValue = $this->isInputField($parameter)) {
0 ignored issues
show
Bug introduced by
The method isInputField() does not seem to exist on object<Propaganistas\LaravelPhone\PhoneValidator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
132 2
				$parameter = $fieldValue;
133
			} elseif ($this->isPhoneCountry($parameter)) {
134 12
				$this->countries[] = $parameter;
135
			} elseif ($this->isPhoneType($parameter)) {
136
				$types[] = $parameter;
137
			} elseif ($parameter == 'AUTO') {
138
				$this->autodetect = true;
139
			} elseif ($parameter == 'LENIENT') {
140
				$this->lenient = true;
141
			} else {
142
				// Force developers to write proper code.
143 12
				throw new InvalidParameterException($parameter);
144
			}
145
		}
146
147
		$this->types = $this->parseTypes($types);
148 12
149
	}
150
151 12
	/**
152
	 * Checks the detected countries. Overrides countries if a country field is present.
153
	 * When using a country field, we should validate to false if country is empty so no exception
154 12
	 * will be thrown.
155 2
	 *
156
	 * @param string $attribute
157
	 * @param        $validator
158
	 * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
159 12
	 */
160
	protected function checkCountries($attribute, $validator)
161
	{
162 12
		$data = array_dot($validator->getData());
163
		$countryField = ($this->countryField == false ? $attribute . '_country' : $this->countryField);
164 2
165
		if (isset($data[$countryField])) {
166
			$this->countries = array($data[$countryField]);
167
		} elseif (!$this->autodetect && !$this->lenient && empty($this->countries)) {
168 10
			throw new NoValidCountryFoundException;
169
		}
170
	}
171
172 8
	/**
173
	 * Performs the actual validation of the phone number.
174
	 *
175
	 * @param mixed $number
176 8
	 * @param null  $country
177
	 * @return bool
178
	 */
179
	protected function isValidNumber($number, $country = null)
180
	{
181
		try {
182
			// Throws NumberParseException if not parsed correctly against the supplied country.
183
			// If no country was given, tries to discover the country code from the number itself.
184
			$phoneNumber = $this->lib->parse($number, $country);
185
186
			// Check if type is allowed.
187
			if (empty($this->types) || in_array($this->lib->getNumberType($phoneNumber), $this->types)) {
188 14
189 4
				// Lenient validation.
190 14
				if ($this->lenient) {
191
					return $this->lib->isPossibleNumber($phoneNumber, $country);
192
				}
193 14
194 4
				// For automatic detection, the number should have a country code.
195 4
				if ($phoneNumber->hasCountryCode()) {
196
197 15
					// Automatic detection:
198 1
					if ($this->autodetect) {
199
						// Validate if the international phone number is valid for its contained country.
200
						return $this->lib->isValidNumber($phoneNumber);
201
					}
202
203
					// Validate number against the specified country.
204
					return $this->lib->isValidNumberForRegion($phoneNumber, $country);
205
				}
206
			}
207 12
208
		} catch (NumberParseException $e) {
209 12
			// Proceed to default validation error.
210
		}
211
212
		return false;
213
	}
214
215
	/**
216
	 * Parses the supplied phone number types.
217
	 *
218 10
	 * @param array $types
219
	 * @return array
220
	 */
221 10
	protected function parseTypes(array $types)
222
	{
223 10
		// Transform types to their namespaced class constant.
224
		array_walk($types, function(&$type) {
225
			$type = constant('\libphonenumber\PhoneNumberType::' . $type);
226
		});
227
228
		// Add in the unsure number type if applicable.
229
		if (array_intersect([PhoneNumberType::FIXED_LINE, PhoneNumberType::MOBILE], $types)) {
230
			$types[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
231
		}
232
233
		return $types;
234
	}
235
236
	public function isCountryField($field)
0 ignored issues
show
Unused Code introduced by
The parameter $field is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
237
	{
238
239
	}
240
241
	/**
242
	 * Checks if the supplied string is a valid country code using some arbitrary country validation.
243
	 * If using a package based on umpirsky/country-list, invalidate the option 'ZZ => Unknown or invalid region'.
244
	 *
245
	 * @param  string $country
246
	 * @return bool
247
	 */
248
	public function isPhoneCountry($country)
249
	{
250
		return (strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && $country != 'ZZ');
251
	}
252
253
	/**
254
	 * Checks if the supplied string is a valid phone number type.
255
	 *
256
	 * @param  string $type
257
	 * @return bool
258
	 */
259
	public function isPhoneType($type)
260
	{
261
		// Legacy support.
262
		$type = ($type == 'LANDLINE' ? 'FIXED_LINE' : $type);
263
264
		return defined('\libphonenumber\PhoneNumberType::' . $type);
265
	}
266
267
}
268