| 1 | <?php |
||
| 2 | namespace EWW\Dpf\Services\Identifier; |
||
| 3 | |||
| 4 | /* |
||
| 5 | * This file is part of the TYPO3 CMS project. |
||
| 6 | * |
||
| 7 | * It is free software; you can redistribute it and/or modify it under |
||
| 8 | * the terms of the GNU General Public License, either version 2 |
||
| 9 | * of the License, or any later version. |
||
| 10 | * |
||
| 11 | * For the full copyright and license information, please read the |
||
| 12 | * LICENSE.txt file that was distributed with this source code. |
||
| 13 | * |
||
| 14 | * The TYPO3 project - inspiring people to share! |
||
| 15 | */ |
||
| 16 | |||
| 17 | class UrnBuilder |
||
| 18 | { |
||
| 19 | |||
| 20 | /** |
||
| 21 | * Standard prefix of the URN for DNB (Deutsche Nationalbibliothek). |
||
| 22 | * |
||
| 23 | * @var string |
||
| 24 | */ |
||
| 25 | const NBN_URN_PREFIX = 'urn:nbn:de'; |
||
| 26 | |||
| 27 | /** |
||
| 28 | * Holds generated dnb nbn urn string |
||
| 29 | * |
||
| 30 | * @var string |
||
| 31 | */ |
||
| 32 | protected $nbnUrnString; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * Use standard URN parts given to build an URN generator applicable to document identifiers. |
||
| 36 | * A complete URN might look like "urn:nbn:de:bsz:14-qucosa-87650" having the component parts set to: |
||
| 37 | * |
||
| 38 | * $snid1 = bsz |
||
| 39 | * $snid2 = 14 |
||
| 40 | * $niss = qucosa-8765 |
||
| 41 | * |
||
| 42 | * The last part of the URN above "87650" consists of a document identifier "8765" and a check digit "0". |
||
| 43 | * |
||
| 44 | * @param string $snid1 First subnamespace identifier part of the URN. |
||
| 45 | * @param string $snid2 Second subnamespace identifier part of the URN. |
||
| 46 | * @throws InvalidArgumentException Thrown if at least one of the subnamespace parts contain other |
||
| 47 | * characters than characters. |
||
| 48 | */ |
||
| 49 | public function __construct($snid1, $snid2) |
||
| 50 | { |
||
| 51 | |||
| 52 | if (0 === preg_match('/^[a-zA-Z]+$/', $snid1)) { |
||
| 53 | throw new \InvalidArgumentException('Used invalid first subnamespace identifier.'); |
||
| 54 | } |
||
| 55 | |||
| 56 | if (0 === preg_match('/^[a-zA-Z0-9]+$/', $snid2)) { |
||
| 57 | throw new \InvalidArgumentException('Used invalid second subnamespace identifier.'); |
||
| 58 | } |
||
| 59 | |||
| 60 | $this->nbnUrnString = self::NBN_URN_PREFIX . ':' . $snid1 . ':' . $snid2 . '-'; |
||
| 61 | } |
||
| 62 | |||
| 63 | /** |
||
| 64 | * Generates complete URNs given a Namespace specific string + Identifier of the Document. |
||
| 65 | * |
||
| 66 | * @param string $niss Namespace specific string + Identifier of the Document |
||
| 67 | * @throws InvalidArgumentException Thrown if the niss contains invalid characters. |
||
| 68 | * @return string The URN. |
||
| 69 | */ |
||
| 70 | public function getUrn($niss) |
||
| 71 | { |
||
| 72 | |||
| 73 | // regexp pattern for valid niss |
||
| 74 | if (0 === preg_match('/^[a-zA-Z0-9\-]+$/', $niss)) { |
||
| 75 | throw new \InvalidArgumentException('Used invalid namespace specific string.'); |
||
| 76 | } |
||
| 77 | |||
| 78 | // calculate matching check digit |
||
| 79 | $check_digit = self::getCheckDigit($niss); |
||
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
| 80 | |||
| 81 | // compose and return standard, snid1, snid2, niss and check digit |
||
| 82 | return $this->nbnUrnString . $niss . $check_digit; |
||
| 83 | } |
||
| 84 | |||
| 85 | /** |
||
| 86 | * Generates check digit for a given niss. |
||
| 87 | * |
||
| 88 | * @param string $niss of the Document |
||
| 89 | * @throws InvalidArgumentException Thrown if the niss contains invalid characters. |
||
| 90 | * @return integer Check digit. |
||
| 91 | */ |
||
| 92 | public function getCheckDigit($niss) |
||
| 93 | { |
||
| 94 | |||
| 95 | // regexp pattern for valid niss |
||
| 96 | if (0 === preg_match('/^[a-zA-Z0-9\-]+$/', $niss)) { |
||
| 97 | throw new \InvalidArgumentException('Used invalid namespace specific string.'); |
||
| 98 | } |
||
| 99 | |||
| 100 | // compose urn with niss |
||
| 101 | $nbn = $this->nbnUrnString . $niss; |
||
| 102 | |||
| 103 | // Replace characters by numbers. |
||
| 104 | $nbn_numbers = $this->replaceUrnChars($nbn); |
||
| 105 | |||
| 106 | // identify string length |
||
| 107 | $nbn_numbers_length = mb_strlen($nbn_numbers); |
||
| 108 | |||
| 109 | // convert string to array of characters |
||
| 110 | $nbn_numbers_array = preg_split('//', $nbn_numbers); |
||
| 111 | |||
| 112 | // initialize sum |
||
| 113 | $sum = 0; |
||
| 114 | |||
| 115 | // calculate product sum |
||
| 116 | for ($ii = 1; $ii <= $nbn_numbers_length; $ii++) { |
||
| 117 | $sum = ($sum + ($nbn_numbers_array[$ii] * $ii)); |
||
| 118 | } |
||
| 119 | |||
| 120 | // identify last digit |
||
| 121 | $last_digit = $nbn_numbers_array[$nbn_numbers_length]; |
||
| 122 | |||
| 123 | // calculate quotient, round down |
||
| 124 | $quotient = floor($sum / $last_digit); |
||
| 125 | |||
| 126 | // convert to string |
||
| 127 | $quotient = (string) $quotient; |
||
| 128 | |||
| 129 | // identify last digit, which is the check digit |
||
| 130 | $check_digit = ($quotient{mb_strlen($quotient) - 1}); |
||
| 131 | |||
| 132 | // return check digit |
||
| 133 | return $check_digit; |
||
| 134 | |||
| 135 | } |
||
| 136 | |||
| 137 | /** |
||
| 138 | * Do a replacement of every character by a specific number according to DNB check digit allegation. |
||
| 139 | * |
||
| 140 | * @param string $urn A partial URN with the checkdigit missing. |
||
| 141 | * @return string The given URN with all characters replaced by numbers. |
||
| 142 | */ |
||
| 143 | private function replaceUrnChars($urn) |
||
| 144 | { |
||
| 145 | // However, the preg_replace function calls itself on the result of a previos run. In order to get |
||
| 146 | // the replacement right, characters and numbers in the arrays below have got a specific order to make |
||
| 147 | // it work. Be careful when changing those numbers! Tests may help ;) |
||
| 148 | |||
| 149 | // convert to lower case |
||
| 150 | $nbn = strtolower($urn); |
||
| 151 | |||
| 152 | // array of characters to match |
||
| 153 | $search_pattern = array('/9/', '/8/', '/7/', '/6/', '/5/', '/4/', '/3/', '/2/', '/1/', '/0/', '/a/', '/b/', '/c/', |
||
| 154 | '/d/', '/e/', '/f/', '/g/', '/h/', '/i/', '/j/', '/k/', '/l/', '/m/', '/n/', '/o/', '/p/', '/q/', '/r/', '/s/', |
||
| 155 | '/t/', '/u/', '/v/', '/w/', '/x/', '/y/', '/z/', '/-/', '/:/'); |
||
| 156 | |||
| 157 | // array of corresponding replacements, '9' will be temporarily replaced with placeholder '_' to prevent |
||
| 158 | // replacement of '41' with '52' |
||
| 159 | $replacements = array('_', 9, 8, 7, 6, 5, 4, 3, 2, 1, 18, 14, 19, 15, 16, 21, 22, 23, 24, 25, 42, 26, 27, 13, 28, 29, |
||
| 160 | 31, 12, 32, 33, 11, 34, 35, 36, 37, 38, 39, 17); |
||
| 161 | |||
| 162 | // replace matching pattern in given nbn with corresponding replacement |
||
| 163 | $nbn_numbers = preg_replace($search_pattern, $replacements, $nbn); |
||
| 164 | |||
| 165 | // replace placeholder '_' with 41 |
||
| 166 | $nbn_numbers = preg_replace('/_/', 41, $nbn_numbers); |
||
| 167 | |||
| 168 | return $nbn_numbers; |
||
| 169 | } |
||
| 170 | |||
| 171 | } |
||
| 172 |