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
![]() |
|||
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 |