1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Validator; 11 12class Isbn extends AbstractValidator 13{ 14 const AUTO = 'auto'; 15 const ISBN10 = '10'; 16 const ISBN13 = '13'; 17 const INVALID = 'isbnInvalid'; 18 const NO_ISBN = 'isbnNoIsbn'; 19 20 /** 21 * Validation failure message template definitions. 22 * 23 * @var array 24 */ 25 protected $messageTemplates = array( 26 self::INVALID => "Invalid type given. String or integer expected", 27 self::NO_ISBN => "The input is not a valid ISBN number", 28 ); 29 30 protected $options = array( 31 'type' => self::AUTO, // Allowed type 32 'separator' => '', // Separator character 33 ); 34 35 /** 36 * Detect input format. 37 * 38 * @return string 39 */ 40 protected function detectFormat() 41 { 42 // prepare separator and pattern list 43 $sep = quotemeta($this->getSeparator()); 44 $patterns = array(); 45 $lengths = array(); 46 $type = $this->getType(); 47 48 // check for ISBN-10 49 if ($type == self::ISBN10 || $type == self::AUTO) { 50 if (empty($sep)) { 51 $pattern = '/^[0-9]{9}[0-9X]{1}$/'; 52 $length = 10; 53 } else { 54 $pattern = "/^[0-9]{1,7}[{$sep}]{1}[0-9]{1,7}[{$sep}]{1}[0-9]{1,7}[{$sep}]{1}[0-9X]{1}$/"; 55 $length = 13; 56 } 57 58 $patterns[$pattern] = self::ISBN10; 59 $lengths[$pattern] = $length; 60 } 61 62 // check for ISBN-13 63 if ($type == self::ISBN13 || $type == self::AUTO) { 64 if (empty($sep)) { 65 $pattern = '/^[0-9]{13}$/'; 66 $length = 13; 67 } else { 68 $pattern = "/^[0-9]{1,9}[{$sep}]{1}[0-9]{1,5}[{$sep}]{1}[0-9]{1,9}[{$sep}]{1}[0-9]{1,9}[{$sep}]{1}[0-9]{1}$/"; 69 $length = 17; 70 } 71 72 $patterns[$pattern] = self::ISBN13; 73 $lengths[$pattern] = $length; 74 } 75 76 // check pattern list 77 foreach ($patterns as $pattern => $type) { 78 if ((strlen($this->getValue()) == $lengths[$pattern]) && preg_match($pattern, $this->getValue())) { 79 return $type; 80 } 81 } 82 83 return; 84 } 85 86 /** 87 * Returns true if and only if $value is a valid ISBN. 88 * 89 * @param string $value 90 * @return bool 91 */ 92 public function isValid($value) 93 { 94 if (!is_string($value) && !is_int($value)) { 95 $this->error(self::INVALID); 96 return false; 97 } 98 99 $value = (string) $value; 100 $this->setValue($value); 101 102 switch ($this->detectFormat()) { 103 case self::ISBN10: 104 // sum 105 $isbn10 = str_replace($this->getSeparator(), '', $value); 106 $sum = 0; 107 for ($i = 0; $i < 9; $i++) { 108 $sum += (10 - $i) * $isbn10{$i}; 109 } 110 111 // checksum 112 $checksum = 11 - ($sum % 11); 113 if ($checksum == 11) { 114 $checksum = '0'; 115 } elseif ($checksum == 10) { 116 $checksum = 'X'; 117 } 118 break; 119 120 case self::ISBN13: 121 // sum 122 $isbn13 = str_replace($this->getSeparator(), '', $value); 123 $sum = 0; 124 for ($i = 0; $i < 12; $i++) { 125 if ($i % 2 == 0) { 126 $sum += $isbn13{$i}; 127 } else { 128 $sum += 3 * $isbn13{$i}; 129 } 130 } 131 // checksum 132 $checksum = 10 - ($sum % 10); 133 if ($checksum == 10) { 134 $checksum = '0'; 135 } 136 break; 137 138 default: 139 $this->error(self::NO_ISBN); 140 return false; 141 } 142 143 // validate 144 if (substr($this->getValue(), -1) != $checksum) { 145 $this->error(self::NO_ISBN); 146 return false; 147 } 148 return true; 149 } 150 151 /** 152 * Set separator characters. 153 * 154 * It is allowed only empty string, hyphen and space. 155 * 156 * @param string $separator 157 * @throws Exception\InvalidArgumentException When $separator is not valid 158 * @return Isbn Provides a fluent interface 159 */ 160 public function setSeparator($separator) 161 { 162 // check separator 163 if (!in_array($separator, array('-', ' ', ''))) { 164 throw new Exception\InvalidArgumentException('Invalid ISBN separator.'); 165 } 166 167 $this->options['separator'] = $separator; 168 return $this; 169 } 170 171 /** 172 * Get separator characters. 173 * 174 * @return string 175 */ 176 public function getSeparator() 177 { 178 return $this->options['separator']; 179 } 180 181 /** 182 * Set allowed ISBN type. 183 * 184 * @param string $type 185 * @throws Exception\InvalidArgumentException When $type is not valid 186 * @return Isbn Provides a fluent interface 187 */ 188 public function setType($type) 189 { 190 // check type 191 if (!in_array($type, array(self::AUTO, self::ISBN10, self::ISBN13))) { 192 throw new Exception\InvalidArgumentException('Invalid ISBN type'); 193 } 194 195 $this->options['type'] = $type; 196 return $this; 197 } 198 199 /** 200 * Get allowed ISBN type. 201 * 202 * @return string 203 */ 204 public function getType() 205 { 206 return $this->options['type']; 207 } 208} 209