Updated to Drupal 8.5. Core Media not yet in use.
[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\Constraint;
15 use Symfony\Component\Validator\ConstraintValidator;
16 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17
18 /**
19  * Validates whether the value is a valid ISBN-10 or ISBN-13.
20  *
21  * @author The Whole Life To Learn <thewholelifetolearn@gmail.com>
22  * @author Manuel Reinhard <manu@sprain.ch>
23  * @author Bernhard Schussek <bschussek@gmail.com>
24  *
25  * @see https://en.wikipedia.org/wiki/Isbn
26  */
27 class IsbnValidator extends ConstraintValidator
28 {
29     /**
30      * {@inheritdoc}
31      */
32     public function validate($value, Constraint $constraint)
33     {
34         if (!$constraint instanceof Isbn) {
35             throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Isbn');
36         }
37
38         if (null === $value || '' === $value) {
39             return;
40         }
41
42         if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
43             throw new UnexpectedTypeException($value, 'string');
44         }
45
46         $value = (string) $value;
47         $canonical = str_replace('-', '', $value);
48
49         // Explicitly validate against ISBN-10
50         if ('isbn10' === $constraint->type) {
51             if (true !== ($code = $this->validateIsbn10($canonical))) {
52                 $this->context->buildViolation($this->getMessage($constraint, $constraint->type))
53                     ->setParameter('{{ value }}', $this->formatValue($value))
54                     ->setCode($code)
55                     ->addViolation();
56             }
57
58             return;
59         }
60
61         // Explicitly validate against ISBN-13
62         if ('isbn13' === $constraint->type) {
63             if (true !== ($code = $this->validateIsbn13($canonical))) {
64                 $this->context->buildViolation($this->getMessage($constraint, $constraint->type))
65                     ->setParameter('{{ value }}', $this->formatValue($value))
66                     ->setCode($code)
67                     ->addViolation();
68             }
69
70             return;
71         }
72
73         // Try both ISBNs
74
75         // First, try ISBN-10
76         $code = $this->validateIsbn10($canonical);
77
78         // The ISBN can only be an ISBN-13 if the value was too long for ISBN-10
79         if (Isbn::TOO_LONG_ERROR === $code) {
80             // Try ISBN-13 now
81             $code = $this->validateIsbn13($canonical);
82
83             // If too short, this means we have 11 or 12 digits
84             if (Isbn::TOO_SHORT_ERROR === $code) {
85                 $code = Isbn::TYPE_NOT_RECOGNIZED_ERROR;
86             }
87         }
88
89         if (true !== $code) {
90             $this->context->buildViolation($this->getMessage($constraint))
91                 ->setParameter('{{ value }}', $this->formatValue($value))
92                 ->setCode($code)
93                 ->addViolation();
94         }
95     }
96
97     protected function validateIsbn10($isbn)
98     {
99         // Choose an algorithm so that ERROR_INVALID_CHARACTERS is preferred
100         // over ERROR_TOO_SHORT/ERROR_TOO_LONG
101         // Otherwise "0-45122-5244" passes, but "0-45122_5244" reports
102         // "too long"
103
104         // Error priority:
105         // 1. ERROR_INVALID_CHARACTERS
106         // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
107         // 3. ERROR_CHECKSUM_FAILED
108
109         $checkSum = 0;
110
111         for ($i = 0; $i < 10; ++$i) {
112             // If we test the length before the loop, we get an ERROR_TOO_SHORT
113             // when actually an ERROR_INVALID_CHARACTERS is wanted, e.g. for
114             // "0-45122_5244" (typo)
115             if (!isset($isbn[$i])) {
116                 return Isbn::TOO_SHORT_ERROR;
117             }
118
119             if ('X' === $isbn[$i]) {
120                 $digit = 10;
121             } elseif (ctype_digit($isbn[$i])) {
122                 $digit = $isbn[$i];
123             } else {
124                 return Isbn::INVALID_CHARACTERS_ERROR;
125             }
126
127             $checkSum += $digit * (10 - $i);
128         }
129
130         if (isset($isbn[$i])) {
131             return Isbn::TOO_LONG_ERROR;
132         }
133
134         return 0 === $checkSum % 11 ? true : Isbn::CHECKSUM_FAILED_ERROR;
135     }
136
137     protected function validateIsbn13($isbn)
138     {
139         // Error priority:
140         // 1. ERROR_INVALID_CHARACTERS
141         // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
142         // 3. ERROR_CHECKSUM_FAILED
143
144         if (!ctype_digit($isbn)) {
145             return Isbn::INVALID_CHARACTERS_ERROR;
146         }
147
148         $length = strlen($isbn);
149
150         if ($length < 13) {
151             return Isbn::TOO_SHORT_ERROR;
152         }
153
154         if ($length > 13) {
155             return Isbn::TOO_LONG_ERROR;
156         }
157
158         $checkSum = 0;
159
160         for ($i = 0; $i < 13; $i += 2) {
161             $checkSum += $isbn[$i];
162         }
163
164         for ($i = 1; $i < 12; $i += 2) {
165             $checkSum += $isbn[$i]
166             * 3;
167         }
168
169         return 0 === $checkSum % 10 ? true : Isbn::CHECKSUM_FAILED_ERROR;
170     }
171
172     protected function getMessage($constraint, $type = null)
173     {
174         if (null !== $constraint->message) {
175             return $constraint->message;
176         } elseif ('isbn10' === $type) {
177             return $constraint->isbn10Message;
178         } elseif ('isbn13' === $type) {
179             return $constraint->isbn13Message;
180         }
181
182         return $constraint->bothIsbnMessage;
183     }
184 }