|
|
|
|
@@ -1,5 +1,6 @@
|
|
|
|
|
<?php namespace Propaganistas\LaravelPhone;
|
|
|
|
|
|
|
|
|
|
use Iso3166\Codes as ISO3166;
|
|
|
|
|
use libphonenumber\NumberParseException;
|
|
|
|
|
use libphonenumber\PhoneNumberType;
|
|
|
|
|
use libphonenumber\PhoneNumberUtil;
|
|
|
|
|
@@ -9,218 +10,247 @@ use Propaganistas\LaravelPhone\Exceptions\InvalidParameterException;
|
|
|
|
|
class PhoneValidator
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var \libphonenumber\PhoneNumberUtil
|
|
|
|
|
*/
|
|
|
|
|
protected $lib;
|
|
|
|
|
/**
|
|
|
|
|
* @var \libphonenumber\PhoneNumberUtil
|
|
|
|
|
*/
|
|
|
|
|
protected $lib;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether the country should be auto-detected.
|
|
|
|
|
*
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
protected $autodetect = false;
|
|
|
|
|
/**
|
|
|
|
|
* Dotted validator data.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $data;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether to allow lenient checking of numbers (i.e. landline without area codes).
|
|
|
|
|
*
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
protected $lenient = false;
|
|
|
|
|
/**
|
|
|
|
|
* Whether the country should be auto-detected.
|
|
|
|
|
*
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
protected $autodetect = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Countries to validate against.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $countries = [];
|
|
|
|
|
/**
|
|
|
|
|
* Whether to allow lenient checking of numbers (i.e. landline without area codes).
|
|
|
|
|
*
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
|
|
|
|
protected $lenient = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Transformed phone number types to validate against.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $types = [];
|
|
|
|
|
/**
|
|
|
|
|
* The field to use for country if not passed as a parameter.
|
|
|
|
|
*
|
|
|
|
|
* @var string|null
|
|
|
|
|
*/
|
|
|
|
|
protected $countryField = null;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PhoneValidator constructor.
|
|
|
|
|
*/
|
|
|
|
|
public function __construct(PhoneNumberUtil $lib)
|
|
|
|
|
{
|
|
|
|
|
$this->lib = $lib;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Countries to validate against.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $countries = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validates a phone number.
|
|
|
|
|
*
|
|
|
|
|
* @param string $attribute
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @param array $parameters
|
|
|
|
|
* @param object $validator
|
|
|
|
|
* @return bool
|
|
|
|
|
* @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
|
|
|
|
|
* @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
|
|
|
|
|
*/
|
|
|
|
|
public function validatePhone($attribute, $value, array $parameters, $validator)
|
|
|
|
|
{
|
|
|
|
|
$this->assignParameters(array_map('strtoupper', $parameters));
|
|
|
|
|
/**
|
|
|
|
|
* Transformed phone number types to validate against.
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $types = [];
|
|
|
|
|
|
|
|
|
|
$this->checkCountries($attribute, $validator);
|
|
|
|
|
/**
|
|
|
|
|
* PhoneValidator constructor.
|
|
|
|
|
*/
|
|
|
|
|
public function __construct(PhoneNumberUtil $lib)
|
|
|
|
|
{
|
|
|
|
|
$this->lib = $lib;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If autodetecting, let's first try without a country.
|
|
|
|
|
// Otherwise use provided countries as default.
|
|
|
|
|
if ($this->autodetect || ($this->lenient && empty($this->countries))) {
|
|
|
|
|
array_unshift($this->countries, null);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Validates a phone number.
|
|
|
|
|
*
|
|
|
|
|
* @param string $attribute
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @param array $parameters
|
|
|
|
|
* @param object $validator
|
|
|
|
|
* @return bool
|
|
|
|
|
* @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
|
|
|
|
|
* @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
|
|
|
|
|
*/
|
|
|
|
|
public function validatePhone($attribute, $value, array $parameters, $validator)
|
|
|
|
|
{
|
|
|
|
|
$this->data = array_dot($validator->getData());
|
|
|
|
|
|
|
|
|
|
foreach ($this->countries as $country) {
|
|
|
|
|
if ($this->isValidNumber($value, $country)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->assignParameters($parameters);
|
|
|
|
|
|
|
|
|
|
// All specified country validations have failed.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$this->checkCountries($attribute);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parses the supplied validator parameters.
|
|
|
|
|
*
|
|
|
|
|
* @param array $parameters
|
|
|
|
|
* @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
|
|
|
|
|
*/
|
|
|
|
|
protected function assignParameters(array $parameters)
|
|
|
|
|
{
|
|
|
|
|
$types = array();
|
|
|
|
|
// If autodetecting, let's first try without a country.
|
|
|
|
|
// Otherwise use provided countries as default.
|
|
|
|
|
if ($this->autodetect || ($this->lenient && empty($this->countries))) {
|
|
|
|
|
array_unshift($this->countries, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($parameters as $parameter) {
|
|
|
|
|
if ($this->isPhoneCountry($parameter)) {
|
|
|
|
|
$this->countries[] = $parameter;
|
|
|
|
|
} elseif ($this->isPhoneType($parameter)) {
|
|
|
|
|
$types[] = $parameter;
|
|
|
|
|
} elseif ($parameter == 'AUTO') {
|
|
|
|
|
$this->autodetect = true;
|
|
|
|
|
} elseif ($parameter == 'LENIENT') {
|
|
|
|
|
$this->lenient = true;
|
|
|
|
|
} else {
|
|
|
|
|
// Force developers to write proper code.
|
|
|
|
|
throw new InvalidParameterException($parameter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
foreach ($this->countries as $country) {
|
|
|
|
|
if ($this->isValidNumber($value, $country)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->types = $this->parseTypes($types);
|
|
|
|
|
// All specified country validations have failed.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Parses the supplied validator parameters.
|
|
|
|
|
*
|
|
|
|
|
* @param array $parameters
|
|
|
|
|
* @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
|
|
|
|
|
*/
|
|
|
|
|
protected function assignParameters(array $parameters)
|
|
|
|
|
{
|
|
|
|
|
$types = array();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks the detected countries. Overrides countries if a country field is present.
|
|
|
|
|
* When using a country field, we should validate to false if country is empty so no exception
|
|
|
|
|
* will be thrown.
|
|
|
|
|
*
|
|
|
|
|
* @param string $attribute
|
|
|
|
|
* @param $validator
|
|
|
|
|
* @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
|
|
|
|
|
*/
|
|
|
|
|
protected function checkCountries($attribute, $validator)
|
|
|
|
|
{
|
|
|
|
|
$data = array_dot($validator->getData());
|
|
|
|
|
$countryField = $attribute . '_country';
|
|
|
|
|
foreach ($parameters as $parameter) {
|
|
|
|
|
// First check if the parameter is some phone type configuration.
|
|
|
|
|
if ($this->isPhoneType($parameter)) {
|
|
|
|
|
$types[] = strtoupper($parameter);
|
|
|
|
|
} elseif ($this->isPhoneCountry($parameter)) {
|
|
|
|
|
$this->countries[] = strtoupper($parameter);
|
|
|
|
|
} elseif ($parameter == 'AUTO') {
|
|
|
|
|
$this->autodetect = true;
|
|
|
|
|
} elseif ($parameter == 'LENIENT') {
|
|
|
|
|
$this->lenient = true;
|
|
|
|
|
}
|
|
|
|
|
// Lastly check if it is an input field containing the country.
|
|
|
|
|
elseif ($this->isInputField($parameter)) {
|
|
|
|
|
$this->countryField = $parameter;
|
|
|
|
|
} else {
|
|
|
|
|
// Force developers to write proper code.
|
|
|
|
|
throw new InvalidParameterException($parameter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isset($data[$countryField])) {
|
|
|
|
|
$this->countries = array($data[$countryField]);
|
|
|
|
|
} elseif (!$this->autodetect && !$this->lenient && empty($this->countries)) {
|
|
|
|
|
throw new NoValidCountryFoundException;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$this->types = $this->parseTypes($types);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Performs the actual validation of the phone number.
|
|
|
|
|
*
|
|
|
|
|
* @param mixed $number
|
|
|
|
|
* @param null $country
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected function isValidNumber($number, $country = null)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// Throws NumberParseException if not parsed correctly against the supplied country.
|
|
|
|
|
// If no country was given, tries to discover the country code from the number itself.
|
|
|
|
|
$phoneNumber = $this->lib->parse($number, $country);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if type is allowed.
|
|
|
|
|
if (empty($this->types) || in_array($this->lib->getNumberType($phoneNumber), $this->types)) {
|
|
|
|
|
/**
|
|
|
|
|
* Checks the detected countries. Overrides countries if a country field is present.
|
|
|
|
|
* When using a country field, we should validate to false if country is empty so no exception
|
|
|
|
|
* will be thrown.
|
|
|
|
|
*
|
|
|
|
|
* @param string $attribute
|
|
|
|
|
* @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
|
|
|
|
|
*/
|
|
|
|
|
protected function checkCountries($attribute)
|
|
|
|
|
{
|
|
|
|
|
$countryField = (is_null($this->countryField) ? $attribute . '_country' : $this->countryField);
|
|
|
|
|
|
|
|
|
|
// Lenient validation.
|
|
|
|
|
if ($this->lenient) {
|
|
|
|
|
return $this->lib->isPossibleNumber($phoneNumber, $country);
|
|
|
|
|
}
|
|
|
|
|
if ($this->isInputField($countryField)) {
|
|
|
|
|
$this->countries = array(array_get($this->data, $countryField));
|
|
|
|
|
} elseif (!$this->autodetect && !$this->lenient && empty($this->countries)) {
|
|
|
|
|
throw new NoValidCountryFoundException;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For automatic detection, the number should have a country code.
|
|
|
|
|
if ($phoneNumber->hasCountryCode()) {
|
|
|
|
|
/**
|
|
|
|
|
* Performs the actual validation of the phone number.
|
|
|
|
|
*
|
|
|
|
|
* @param mixed $number
|
|
|
|
|
* @param null $country
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
protected function isValidNumber($number, $country = null)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// Throws NumberParseException if not parsed correctly against the supplied country.
|
|
|
|
|
// If no country was given, tries to discover the country code from the number itself.
|
|
|
|
|
$phoneNumber = $this->lib->parse($number, $country);
|
|
|
|
|
|
|
|
|
|
// Automatic detection:
|
|
|
|
|
if ($this->autodetect) {
|
|
|
|
|
// Validate if the international phone number is valid for its contained country.
|
|
|
|
|
return $this->lib->isValidNumber($phoneNumber);
|
|
|
|
|
}
|
|
|
|
|
// Check if type is allowed.
|
|
|
|
|
if (empty($this->types) || in_array($this->lib->getNumberType($phoneNumber), $this->types)) {
|
|
|
|
|
|
|
|
|
|
// Validate number against the specified country.
|
|
|
|
|
return $this->lib->isValidNumberForRegion($phoneNumber, $country);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Lenient validation.
|
|
|
|
|
if ($this->lenient) {
|
|
|
|
|
return $this->lib->isPossibleNumber($phoneNumber, $country);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (NumberParseException $e) {
|
|
|
|
|
// Proceed to default validation error.
|
|
|
|
|
}
|
|
|
|
|
// For automatic detection, the number should have a country code.
|
|
|
|
|
if ($phoneNumber->hasCountryCode()) {
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Automatic detection:
|
|
|
|
|
if ($this->autodetect) {
|
|
|
|
|
// Validate if the international phone number is valid for its contained country.
|
|
|
|
|
return $this->lib->isValidNumber($phoneNumber);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parses the supplied phone number types.
|
|
|
|
|
*
|
|
|
|
|
* @param array $types
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
protected function parseTypes(array $types)
|
|
|
|
|
{
|
|
|
|
|
// Transform types to their namespaced class constant.
|
|
|
|
|
array_walk($types, function(&$type) {
|
|
|
|
|
$type = constant('\libphonenumber\PhoneNumberType::' . $type);
|
|
|
|
|
});
|
|
|
|
|
// Validate number against the specified country.
|
|
|
|
|
return $this->lib->isValidNumberForRegion($phoneNumber, $country);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add in the unsure number type if applicable.
|
|
|
|
|
if (array_intersect([PhoneNumberType::FIXED_LINE, PhoneNumberType::MOBILE], $types)) {
|
|
|
|
|
$types[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
|
|
|
|
|
}
|
|
|
|
|
} catch (NumberParseException $e) {
|
|
|
|
|
// Proceed to default validation error.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $types;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if the supplied string is a valid country code using some arbitrary country validation.
|
|
|
|
|
* If using a package based on umpirsky/country-list, invalidate the option 'ZZ => Unknown or invalid region'.
|
|
|
|
|
*
|
|
|
|
|
* @param string $country
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isPhoneCountry($country)
|
|
|
|
|
{
|
|
|
|
|
return (strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && $country != 'ZZ');
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Parses the supplied phone number types.
|
|
|
|
|
*
|
|
|
|
|
* @param array $types
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
protected function parseTypes(array $types)
|
|
|
|
|
{
|
|
|
|
|
// Transform types to their namespaced class constant.
|
|
|
|
|
array_walk($types, function(&$type) {
|
|
|
|
|
$type = constant('\libphonenumber\PhoneNumberType::' . $type);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if the supplied string is a valid phone number type.
|
|
|
|
|
*
|
|
|
|
|
* @param string $type
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isPhoneType($type)
|
|
|
|
|
{
|
|
|
|
|
// Legacy support.
|
|
|
|
|
$type = ($type == 'LANDLINE' ? 'FIXED_LINE' : $type);
|
|
|
|
|
// Add in the unsure number type if applicable.
|
|
|
|
|
if (array_intersect([PhoneNumberType::FIXED_LINE, PhoneNumberType::MOBILE], $types)) {
|
|
|
|
|
$types[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return defined('\libphonenumber\PhoneNumberType::' . $type);
|
|
|
|
|
}
|
|
|
|
|
return $types;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if the given field is an actual input field.
|
|
|
|
|
*
|
|
|
|
|
* @param string $field
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isInputField($field)
|
|
|
|
|
{
|
|
|
|
|
return !is_null(array_get($this->data, $field));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if the supplied string is a valid country code.
|
|
|
|
|
*
|
|
|
|
|
* @param string $country
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isPhoneCountry($country)
|
|
|
|
|
{
|
|
|
|
|
return ISO3166::isValid(strtoupper($country));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if the supplied string is a valid phone number type.
|
|
|
|
|
*
|
|
|
|
|
* @param string $type
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isPhoneType($type)
|
|
|
|
|
{
|
|
|
|
|
// Legacy support.
|
|
|
|
|
$type = ($type == 'LANDLINE' ? 'FIXED_LINE' : $type);
|
|
|
|
|
|
|
|
|
|
return defined('\libphonenumber\PhoneNumberType::' . strtoupper($type));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|