2013-01-12 13:38:26 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Validator\Constraints ;
use Symfony\Component\Validator\Constraint ;
use Symfony\Component\Validator\ConstraintValidator ;
2018-07-26 10:03:18 +01:00
use Symfony\Component\Validator\Context\ExecutionContextInterface ;
2013-01-12 13:38:26 +00:00
use Symfony\Component\Validator\Exception\UnexpectedTypeException ;
/**
2015-08-24 07:53:33 +01:00
* Validates whether the value is a valid ISBN - 10 or ISBN - 13.
2013-04-20 14:37:23 +01:00
*
2013-01-12 13:38:26 +00:00
* @ author The Whole Life To Learn < thewholelifetolearn @ gmail . com >
2014-03-27 06:27:38 +00:00
* @ author Manuel Reinhard < manu @ sprain . ch >
2014-04-10 17:57:38 +01:00
* @ author Bernhard Schussek < bschussek @ gmail . com >
2013-01-12 13:38:26 +00:00
*
2013-04-20 14:37:23 +01:00
* @ see https :// en . wikipedia . org / wiki / Isbn
2013-01-12 13:38:26 +00:00
*/
class IsbnValidator extends ConstraintValidator
{
/**
2014-04-15 06:57:34 +01:00
* { @ inheritdoc }
2013-01-12 13:38:26 +00:00
*/
public function validate ( $value , Constraint $constraint )
{
2014-03-10 12:51:04 +00:00
if ( ! $constraint instanceof Isbn ) {
throw new UnexpectedTypeException ( $constraint , __NAMESPACE__ . '\Isbn' );
}
2013-01-12 13:38:26 +00:00
if ( null === $value || '' === $value ) {
return ;
}
2018-07-05 12:24:53 +01:00
if ( ! is_scalar ( $value ) && ! ( \is_object ( $value ) && method_exists ( $value , '__toString' ))) {
2013-01-12 13:38:26 +00:00
throw new UnexpectedTypeException ( $value , 'string' );
}
2014-04-10 17:57:38 +01:00
$value = ( string ) $value ;
2014-08-05 10:00:40 +01:00
$canonical = str_replace ( '-' , '' , $value );
2013-04-20 14:37:23 +01:00
2015-01-20 21:49:28 +00:00
if ( null === $constraint -> type ) {
2014-03-27 06:27:38 +00:00
if ( $constraint -> isbn10 && ! $constraint -> isbn13 ) {
2017-12-31 04:55:05 +00:00
@ trigger_error ( 'The "isbn10" option of the Isbn constraint is deprecated since Symfony 2.5 and will be removed in 3.0. Use the "type" option instead.' , E_USER_DEPRECATED );
2014-03-27 06:27:38 +00:00
$constraint -> type = 'isbn10' ;
} elseif ( $constraint -> isbn13 && ! $constraint -> isbn10 ) {
2017-12-31 04:55:05 +00:00
@ trigger_error ( 'The "isbn13" option of the Isbn constraint is deprecated since Symfony 2.5 and will be removed in 3.0. Use the "type" option instead.' , E_USER_DEPRECATED );
2014-03-27 06:27:38 +00:00
$constraint -> type = 'isbn13' ;
}
}
2014-09-24 10:48:52 +01:00
// Explicitly validate against ISBN-10
if ( 'isbn10' === $constraint -> type ) {
2014-09-24 11:08:18 +01:00
if ( true !== ( $code = $this -> validateIsbn10 ( $canonical ))) {
2015-01-09 08:12:06 +00:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $this -> getMessage ( $constraint , $constraint -> type ))
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( $code )
-> addViolation ();
} else {
$this -> buildViolation ( $this -> getMessage ( $constraint , $constraint -> type ))
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( $code )
-> addViolation ();
}
2014-09-24 10:48:52 +01:00
}
return ;
}
// Explicitly validate against ISBN-13
if ( 'isbn13' === $constraint -> type ) {
2014-09-24 11:08:18 +01:00
if ( true !== ( $code = $this -> validateIsbn13 ( $canonical ))) {
2015-01-09 08:12:06 +00:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $this -> getMessage ( $constraint , $constraint -> type ))
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( $code )
-> addViolation ();
} else {
$this -> buildViolation ( $this -> getMessage ( $constraint , $constraint -> type ))
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( $code )
-> addViolation ();
}
2014-09-24 10:48:52 +01:00
}
return ;
}
// Try both ISBNs
2014-09-24 11:08:18 +01:00
// First, try ISBN-10
$code = $this -> validateIsbn10 ( $canonical );
// The ISBN can only be an ISBN-13 if the value was too long for ISBN-10
if ( Isbn :: TOO_LONG_ERROR === $code ) {
// Try ISBN-13 now
$code = $this -> validateIsbn13 ( $canonical );
// If too short, this means we have 11 or 12 digits
if ( Isbn :: TOO_SHORT_ERROR === $code ) {
$code = Isbn :: TYPE_NOT_RECOGNIZED_ERROR ;
}
}
if ( true !== $code ) {
2015-01-09 08:12:06 +00:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $this -> getMessage ( $constraint ))
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( $code )
-> addViolation ();
} else {
$this -> buildViolation ( $this -> getMessage ( $constraint ))
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( $code )
-> addViolation ();
}
2014-03-27 06:27:38 +00:00
}
}
2014-08-05 10:00:40 +01:00
protected function validateIsbn10 ( $isbn )
2014-03-27 06:27:38 +00:00
{
2014-09-24 11:08:18 +01:00
// Choose an algorithm so that ERROR_INVALID_CHARACTERS is preferred
// over ERROR_TOO_SHORT/ERROR_TOO_LONG
// Otherwise "0-45122-5244" passes, but "0-45122_5244" reports
// "too long"
// Error priority:
// 1. ERROR_INVALID_CHARACTERS
// 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
// 3. ERROR_CHECKSUM_FAILED
2014-04-10 17:57:38 +01:00
$checkSum = 0 ;
for ( $i = 0 ; $i < 10 ; ++ $i ) {
2014-09-24 11:08:18 +01:00
// If we test the length before the loop, we get an ERROR_TOO_SHORT
// when actually an ERROR_INVALID_CHARACTERS is wanted, e.g. for
// "0-45122_5244" (typo)
2016-10-30 09:34:06 +00:00
if ( ! isset ( $isbn [ $i ])) {
2014-09-24 11:08:18 +01:00
return Isbn :: TOO_SHORT_ERROR ;
2014-09-24 10:48:52 +01:00
}
2016-10-30 09:34:06 +00:00
if ( 'X' === $isbn [ $i ]) {
2014-04-10 17:57:38 +01:00
$digit = 10 ;
2016-10-30 09:34:06 +00:00
} elseif ( ctype_digit ( $isbn [ $i ])) {
$digit = $isbn [ $i ];
2014-03-27 06:27:38 +00:00
} else {
2014-09-24 11:08:18 +01:00
return Isbn :: INVALID_CHARACTERS_ERROR ;
2013-01-12 13:38:26 +00:00
}
2014-04-10 17:57:38 +01:00
2015-03-07 19:12:23 +00:00
$checkSum += $digit * ( 10 - $i );
2014-03-27 06:27:38 +00:00
}
2016-10-30 09:34:06 +00:00
if ( isset ( $isbn [ $i ])) {
2014-09-24 11:08:18 +01:00
return Isbn :: TOO_LONG_ERROR ;
2014-09-24 10:48:52 +01:00
}
2014-09-24 11:08:18 +01:00
return 0 === $checkSum % 11 ? true : Isbn :: CHECKSUM_FAILED_ERROR ;
2014-03-27 06:27:38 +00:00
}
2014-08-05 10:00:40 +01:00
protected function validateIsbn13 ( $isbn )
2014-03-27 06:27:38 +00:00
{
2014-09-24 11:08:18 +01:00
// Error priority:
// 1. ERROR_INVALID_CHARACTERS
// 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
// 3. ERROR_CHECKSUM_FAILED
2014-09-24 10:48:52 +01:00
if ( ! ctype_digit ( $isbn )) {
2014-09-24 11:08:18 +01:00
return Isbn :: INVALID_CHARACTERS_ERROR ;
2014-09-24 10:48:52 +01:00
}
2018-07-05 12:24:53 +01:00
$length = \strlen ( $isbn );
2014-09-24 10:48:52 +01:00
if ( $length < 13 ) {
2014-09-24 11:08:18 +01:00
return Isbn :: TOO_SHORT_ERROR ;
2014-09-24 10:48:52 +01:00
}
if ( $length > 13 ) {
2014-09-24 11:08:18 +01:00
return Isbn :: TOO_LONG_ERROR ;
2014-04-10 17:57:38 +01:00
}
2014-03-27 06:27:38 +00:00
2014-04-10 17:57:38 +01:00
$checkSum = 0 ;
2013-01-12 13:38:26 +00:00
2014-04-10 17:57:38 +01:00
for ( $i = 0 ; $i < 13 ; $i += 2 ) {
2016-10-30 09:34:06 +00:00
$checkSum += $isbn [ $i ];
2013-01-12 13:38:26 +00:00
}
2014-03-27 06:27:38 +00:00
2014-04-10 17:57:38 +01:00
for ( $i = 1 ; $i < 12 ; $i += 2 ) {
2016-10-30 09:34:06 +00:00
$checkSum += $isbn [ $i ]
2014-09-21 19:53:12 +01:00
* 3 ;
2014-04-10 17:57:38 +01:00
}
2014-09-24 11:08:18 +01:00
return 0 === $checkSum % 10 ? true : Isbn :: CHECKSUM_FAILED_ERROR ;
2014-03-27 06:27:38 +00:00
}
2014-08-05 10:00:40 +01:00
protected function getMessage ( $constraint , $type = null )
2014-03-27 06:27:38 +00:00
{
if ( null !== $constraint -> message ) {
return $constraint -> message ;
2014-09-24 10:48:52 +01:00
} elseif ( 'isbn10' === $type ) {
2014-03-27 06:27:38 +00:00
return $constraint -> isbn10Message ;
2014-09-24 10:48:52 +01:00
} elseif ( 'isbn13' === $type ) {
2014-03-27 06:27:38 +00:00
return $constraint -> isbn13Message ;
}
2014-08-05 10:00:40 +01:00
return $constraint -> bothIsbnMessage ;
2013-01-12 13:38:26 +00:00
}
}