6ca8488a5c72577dc2dc573e8d4d9eed2cfab3af
[yaffs-website] / vendor / symfony / validator / Constraints / IsbnValidator.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Validator\Constraints;
13
14 use Symfony\Component\Validator\Context\ExecutionContextInterface;
15 use Symfony\Component\Validator\Constraint;
16 use Symfony\Component\Validator\ConstraintValidator;
17 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
18
19 /**
20  * Validates whether the value is a valid ISBN-10 or ISBN-13.
21  *
22  * @author The Whole Life To Learn <thewholelifetolearn@gmail.com>
23  * @author Manuel Reinhard <manu@sprain.ch>
24  * @author Bernhard Schussek <bschussek@gmail.com>
25  *
26  * @see https://en.wikipedia.org/wiki/Isbn
27  */
28 class IsbnValidator extends ConstraintValidator
29 {
30     /**
31      * {@inheritdoc}
32      */
33     public function validate($value, Constraint $constraint)
34     {
35         if (!$constraint instanceof Isbn) {
36             throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Isbn');
37         }
38
39         if (null === $value || '' === $value) {
40             return;
41         }
42
43         if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
44             throw new UnexpectedTypeException($value, 'string');
45         }
46
47         $value = (string) $value;
48         $canonical = str_replace('-', '', $value);
49
50         if (null === $constraint->type) {
51             if ($constraint->isbn10 && !$constraint->isbn13) {
52                 @trigger_error('The "isbn10" option of the Isbn constraint is deprecated since version 2.5 and will be removed in 3.0. Use the "type" option instead.', E_USER_DEPRECATED);
53                 $constraint->type = 'isbn10';
54             } elseif ($constraint->isbn13 && !$constraint->isbn10) {
55                 @trigger_error('The "isbn13" option of the Isbn constraint is deprecated since version 2.5 and will be removed in 3.0. Use the "type" option instead.', E_USER_DEPRECATED);
56                 $constraint->type = 'isbn13';
57             }
58         }
59
60         // Explicitly validate against ISBN-10
61         if ('isbn10' === $constraint->type) {
62             if (true !== ($code = $this->validateIsbn10($canonical))) {
63                 if ($this->context instanceof ExecutionContextInterface) {
64                     $this->context->buildViolation($this->getMessage($constraint, $constraint->type))
65                         ->setParameter('{{ value }}', $this->formatValue($value))
66                         ->setCode($code)
67                         ->addViolation();
68                 } else {
69                     $this->buildViolation($this->getMessage($constraint, $constraint->type))
70                         ->setParameter('{{ value }}', $this->formatValue($value))
71                         ->setCode($code)
72                         ->addViolation();
73                 }
74             }
75
76             return;
77         }
78
79         // Explicitly validate against ISBN-13
80         if ('isbn13' === $constraint->type) {
81             if (true !== ($code = $this->validateIsbn13($canonical))) {
82                 if ($this->context instanceof ExecutionContextInterface) {
83                     $this->context->buildViolation($this->getMessage($constraint, $constraint->type))
84                         ->setParameter('{{ value }}', $this->formatValue($value))
85                         ->setCode($code)
86                         ->addViolation();
87                 } else {
88                     $this->buildViolation($this->getMessage($constraint, $constraint->type))
89                         ->setParameter('{{ value }}', $this->formatValue($value))
90                         ->setCode($code)
91                         ->addViolation();
92                 }
93             }
94
95             return;
96         }
97
98         // Try both ISBNs
99
100         // First, try ISBN-10
101         $code = $this->validateIsbn10($canonical);
102
103         // The ISBN can only be an ISBN-13 if the value was too long for ISBN-10
104         if (Isbn::TOO_LONG_ERROR === $code) {
105             // Try ISBN-13 now
106             $code = $this->validateIsbn13($canonical);
107
108             // If too short, this means we have 11 or 12 digits
109             if (Isbn::TOO_SHORT_ERROR === $code) {
110                 $code = Isbn::TYPE_NOT_RECOGNIZED_ERROR;
111             }
112         }
113
114         if (true !== $code) {
115             if ($this->context instanceof ExecutionContextInterface) {
116                 $this->context->buildViolation($this->getMessage($constraint))
117                     ->setParameter('{{ value }}', $this->formatValue($value))
118                     ->setCode($code)
119                     ->addViolation();
120             } else {
121                 $this->buildViolation($this->getMessage($constraint))
122                     ->setParameter('{{ value }}', $this->formatValue($value))
123                     ->setCode($code)
124                     ->addViolation();
125             }
126         }
127     }
128
129     protected function validateIsbn10($isbn)
130     {
131         // Choose an algorithm so that ERROR_INVALID_CHARACTERS is preferred
132         // over ERROR_TOO_SHORT/ERROR_TOO_LONG
133         // Otherwise "0-45122-5244" passes, but "0-45122_5244" reports
134         // "too long"
135
136         // Error priority:
137         // 1. ERROR_INVALID_CHARACTERS
138         // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
139         // 3. ERROR_CHECKSUM_FAILED
140
141         $checkSum = 0;
142
143         for ($i = 0; $i < 10; ++$i) {
144             // If we test the length before the loop, we get an ERROR_TOO_SHORT
145             // when actually an ERROR_INVALID_CHARACTERS is wanted, e.g. for
146             // "0-45122_5244" (typo)
147             if (!isset($isbn[$i])) {
148                 return Isbn::TOO_SHORT_ERROR;
149             }
150
151             if ('X' === $isbn[$i]) {
152                 $digit = 10;
153             } elseif (ctype_digit($isbn[$i])) {
154                 $digit = $isbn[$i];
155             } else {
156                 return Isbn::INVALID_CHARACTERS_ERROR;
157             }
158
159             $checkSum += $digit * (10 - $i);
160         }
161
162         if (isset($isbn[$i])) {
163             return Isbn::TOO_LONG_ERROR;
164         }
165
166         return 0 === $checkSum % 11 ? true : Isbn::CHECKSUM_FAILED_ERROR;
167     }
168
169     protected function validateIsbn13($isbn)
170     {
171         // Error priority:
172         // 1. ERROR_INVALID_CHARACTERS
173         // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
174         // 3. ERROR_CHECKSUM_FAILED
175
176         if (!ctype_digit($isbn)) {
177             return Isbn::INVALID_CHARACTERS_ERROR;
178         }
179
180         $length = strlen($isbn);
181
182         if ($length < 13) {
183             return Isbn::TOO_SHORT_ERROR;
184         }
185
186         if ($length > 13) {
187             return Isbn::TOO_LONG_ERROR;
188         }
189
190         $checkSum = 0;
191
192         for ($i = 0; $i < 13; $i += 2) {
193             $checkSum += $isbn[$i];
194         }
195
196         for ($i = 1; $i < 12; $i += 2) {
197             $checkSum += $isbn[$i]
198             * 3;
199         }
200
201         return 0 === $checkSum % 10 ? true : Isbn::CHECKSUM_FAILED_ERROR;
202     }
203
204     protected function getMessage($constraint, $type = null)
205     {
206         if (null !== $constraint->message) {
207             return $constraint->message;
208         } elseif ('isbn10' === $type) {
209             return $constraint->isbn10Message;
210         } elseif ('isbn13' === $type) {
211             return $constraint->isbn13Message;
212         }
213
214         return $constraint->bothIsbnMessage;
215     }
216 }