Rev 14 | Rev 16 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
3 | daniel-mar | 1 | <?php |
2 | |||
3 | /* |
||
4 | * OID-Utilities for PHP |
||
12 | daniel-mar | 5 | * Copyright 2011-2020 Daniel Marschall, ViaThinkSoft |
13 | daniel-mar | 6 | * Version 2020-09-12 |
3 | daniel-mar | 7 | * |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
9 | * you may not use this file except in compliance with the License. |
||
10 | * You may obtain a copy of the License at |
||
11 | * |
||
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
13 | * |
||
14 | * Unless required by applicable law or agreed to in writing, software |
||
15 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
17 | * See the License for the specific language governing permissions and |
||
18 | * limitations under the License. |
||
19 | */ |
||
20 | |||
21 | // All functions in this library are compatible with leading zeroes (not recommended) and leading dots |
||
22 | |||
15 | daniel-mar | 23 | // TODO: change some function names, so that they have a uniform naming schema, and rename "oid identifier" into "ASN.1 alphanumeric identifier" |
12 | daniel-mar | 24 | // oid_id_is_valid() => asn1_alpha_id_valid() |
3 | daniel-mar | 25 | |
26 | define('OID_DOT_FORBIDDEN', 0); |
||
27 | define('OID_DOT_OPTIONAL', 1); |
||
28 | define('OID_DOT_REQUIRED', 2); |
||
29 | |||
30 | /** |
||
31 | * Checks if an OID has a valid dot notation. |
||
32 | * @author Daniel Marschall, ViaThinkSoft |
||
33 | * @version 2014-12-09 |
||
34 | * @param $oid (string)<br /> |
||
35 | * An OID in dot notation. |
||
36 | * @param $allow_leading_zeroes (bool)<br /> |
||
37 | * true of leading zeroes are allowed or not. |
||
38 | * @param $allow_leading_dot (bool)<br /> |
||
39 | * true of leading dots are allowed or not. |
||
40 | * @return (bool) true if the dot notation is valid. |
||
41 | **/ |
||
42 | function oid_valid_dotnotation($oid, $allow_leading_zeroes=true, $allow_leading_dot=false, $min_len=0) { |
||
43 | $regex = oid_validation_regex($allow_leading_zeroes, $allow_leading_dot, $min_len); |
||
44 | |||
13 | daniel-mar | 45 | $m = array(); |
3 | daniel-mar | 46 | return preg_match($regex, $oid, $m) ? true : false; |
47 | } |
||
48 | |||
49 | /** |
||
50 | * Returns a full regular expression to validate an OID in dot-notation |
||
51 | * @author Daniel Marschall, ViaThinkSoft |
||
52 | * @version 2014-12-09 |
||
53 | * @param $allow_leading_zeroes (bool)<br /> |
||
54 | * true of leading zeroes are allowed or not. |
||
55 | * @param $allow_leading_dot (bool)<br /> |
||
56 | * true of leading dots are allowed or not. |
||
57 | * @return (string) The regular expression |
||
58 | **/ |
||
59 | function oid_validation_regex($allow_leading_zeroes=true, $allow_leading_dot=false, $min_len=0) { |
||
60 | $leading_dot_policy = $allow_leading_dot ? OID_DOT_OPTIONAL : OID_DOT_FORBIDDEN; |
||
61 | |||
62 | $part_regex = oid_part_regex($min_len, $allow_leading_zeroes, $leading_dot_policy); |
||
63 | |||
64 | return '@^'.$part_regex.'$@'; |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Returns a partial regular expression which matches valid OIDs in dot notation. |
||
69 | * It can be inserted into regular expressions. |
||
70 | * @author Daniel Marschall, ViaThinkSoft |
||
71 | * @version 2014-12-09 |
||
72 | * @param $min_len (int)<br /> |
||
73 | * 0="." and greater will be recognized, but not ""<br /> |
||
74 | * 1=".2" and greater will be recognized<br /> |
||
75 | * 2=".2.999" and greater will be recognized (default)<br /> |
||
76 | * etc. |
||
77 | * @param $allow_leading_zeroes (bool)<br /> |
||
78 | * true: ".2.0999" will be recognized<br /> |
||
79 | * false: ".2.0999" won't be recognized (default) |
||
80 | * @param $leading_dot_policy (int)<br /> |
||
81 | * 0 (OID_DOT_FORBIDDEN): forbidden<br /> |
||
82 | * 1 (OID_DOT_OPTIONAL) : optional (default)<br /> |
||
83 | * 2 (OID_DOT_REQUIRED) : enforced |
||
84 | * @return (string) A regular expression which matches OIDs in dot notation |
||
85 | **/ |
||
86 | function oid_part_regex($min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL) { |
||
87 | switch ($leading_dot_policy) { |
||
88 | case 0: // forbidden |
||
89 | $lead_dot = ''; |
||
90 | break; |
||
91 | case 1: // optional |
||
92 | $lead_dot = '\\.{0,1}'; |
||
93 | break; |
||
94 | case 2: // enforced |
||
95 | $lead_dot = '\\.'; |
||
96 | break; |
||
97 | default: |
||
98 | assert(false); |
||
99 | break; |
||
100 | } |
||
101 | |||
102 | $lead_zero = $allow_leading_zeroes ? '0*' : ''; |
||
103 | $zero_till_thirtynine = '(([0-9])|([1-3][0-9]))'; // second arc is limited to 0..39 if root arc is 0..1 |
||
104 | $singledot_option = ($min_len == 0) && ($leading_dot_policy != OID_DOT_FORBIDDEN) ? '|\\.' : ''; |
||
105 | $only_root_option = ($min_len <= 1) ? '|('.$lead_dot.$lead_zero.'[0-2])' : ''; |
||
106 | |||
107 | $regex = ' |
||
108 | ( |
||
109 | ( |
||
110 | ( |
||
111 | ('.$lead_dot.$lead_zero.'[0-1]) |
||
112 | \\.'.$lead_zero.$zero_till_thirtynine.' |
||
113 | (\\.'.$lead_zero.'(0|[1-9][0-9]*)){'.max(0, $min_len-2).',} |
||
114 | )|( |
||
115 | ('.$lead_dot.$lead_zero.'[2]) |
||
116 | (\\.'.$lead_zero.'(0|[1-9][0-9]*)){'.max(0, $min_len-1).',} |
||
117 | ) |
||
118 | '.$only_root_option.' |
||
119 | '.$singledot_option.' |
||
120 | ) |
||
121 | )'; |
||
122 | |||
123 | // Remove the indentations which are used to maintain this large regular expression in a human friendly way |
||
124 | $regex = str_replace("\n", '', $regex); |
||
125 | $regex = str_replace("\r", '', $regex); |
||
126 | $regex = str_replace("\t", '', $regex); |
||
127 | $regex = str_replace(' ', '', $regex); |
||
128 | |||
129 | return $regex; |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Searches all OIDs in $text and outputs them as array. |
||
134 | * @author Daniel Marschall, ViaThinkSoft |
||
135 | * @version 2014-12-09 |
||
136 | * @param $text (string)<br /> |
||
137 | * The text to be parsed |
||
138 | * @param $min_len (int)<br /> |
||
139 | * 0="." and greater will be recognized, but not ""<br /> |
||
140 | * 1=".2" and greater will be recognized<br /> |
||
141 | * 2=".2.999" and greater will be recognized (default)<br /> |
||
142 | * etc. |
||
143 | * @param $allow_leading_zeroes (bool)<br /> |
||
144 | * true: ".2.0999" will be recognized<br /> |
||
145 | * false: ".2.0999" won't be recognized (default) |
||
146 | * @param $leading_dot_policy (int)<br /> |
||
147 | * 0 (OID_DOT_FORBIDDEN): forbidden<br /> |
||
148 | * 1 (OID_DOT_OPTIONAL) : optional (default)<br /> |
||
149 | * 2 (OID_DOT_REQUIRED) : enforced |
||
150 | * @param $requires_whitespace_delimiters (bool)<br /> |
||
151 | * true: "2.999" will be recognized, as well as " 2.999 " (default)<br /> |
||
152 | * false: "2.999!" will be reconigzed, as well as "2.999.c" (this might be used in in documentations with templates) |
||
153 | * @return (array<string>) An array of OIDs in dot notation |
||
154 | **/ |
||
155 | function parse_oids($text, $min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL, $requires_whitespace_delimiters=true) { |
||
156 | $regex = oid_detection_regex($min_len, $allow_leading_zeroes, $leading_dot_policy, $requires_whitespace_delimiters); |
||
157 | |||
13 | daniel-mar | 158 | $matches = array(); |
3 | daniel-mar | 159 | preg_match_all($regex, $text, $matches); |
160 | return $matches[1]; |
||
161 | } |
||
162 | |||
163 | /** |
||
164 | * Returns a full regular expression for detecting OIDs in dot notation inside a text. |
||
165 | * @author Daniel Marschall, ViaThinkSoft |
||
166 | * @version 2014-12-09 |
||
167 | * @param $min_len (int)<br /> |
||
168 | * 0="." and greater will be recognized, but not ""<br /> |
||
169 | * 1=".2" and greater will be recognized<br /> |
||
170 | * 2=".2.999" and greater will be recognized (default)<br /> |
||
171 | * etc. |
||
172 | * @param $allow_leading_zeroes (bool)<br /> |
||
173 | * true: ".2.0999" will be recognized<br /> |
||
174 | * false: ".2.0999" won't be recognized (default) |
||
175 | * @param $leading_dot_policy (int)<br /> |
||
176 | * 0 (OID_DOT_FORBIDDEN): forbidden<br /> |
||
177 | * 1 (OID_DOT_OPTIONAL) : optional (default)<br /> |
||
178 | * 2 (OID_DOT_REQUIRED) : enforced |
||
179 | * @param $requires_whitespace_delimiters (bool)<br /> |
||
180 | * true: "2.999" will be recognized, as well as " 2.999 " (default)<br /> |
||
181 | * false: "2.999!" will be reconigzed, as well as "2.999.c" (this might be used in in documentations with templates) |
||
182 | * @return (string) The regular expression |
||
183 | **/ |
||
184 | function oid_detection_regex($min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL, $requires_whitespace_delimiters=true) { |
||
185 | if ($requires_whitespace_delimiters) { |
||
186 | // A fully qualified regular expression which can be used by preg_match() |
||
187 | $begin_condition = '(?<=^|\\s)'; |
||
188 | $end_condition = '(?=\\s|$)'; |
||
189 | } else { |
||
190 | // A partial expression which can be used inside another regular expression |
||
191 | $begin_condition = '(?<![\d])'; |
||
192 | $end_condition = '(?![\d])'; |
||
193 | } |
||
194 | |||
195 | $part_regex = oid_part_regex($min_len, $allow_leading_zeroes, $leading_dot_policy); |
||
196 | |||
197 | return '@'.$begin_condition.$part_regex.$end_condition.'@'; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Returns the parent of an OID in dot notation or the OID itself, if it is the root.<br /> |
||
202 | * Leading dots and leading zeroes are tolerated. |
||
203 | * @author Daniel Marschall, ViaThinkSoft |
||
204 | * @version 2014-12-16 |
||
205 | * @param $oid (string)<br /> |
||
206 | * An OID in dot notation. |
||
207 | * @return (string) The parent OID in dot notation. |
||
208 | **/ |
||
209 | function oid_up($oid) { |
||
210 | $oid = sanitizeOID($oid, 'auto'); |
||
211 | if ($oid === false) return false; |
||
212 | |||
213 | $p = strrpos($oid, '.'); |
||
214 | if ($p === false) return $oid; |
||
215 | if ($p == 0) return '.'; |
||
216 | |||
217 | return substr($oid, 0, $p); |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Outputs the depth of an OID. |
||
222 | * @author Daniel Marschall, ViaThinkSoft |
||
223 | * @version 2014-12-09 |
||
224 | * @param $oid (string) An OID in dot notation (with or without leading dot) |
||
225 | * @return (int) The depth of the OID, e.g. 2.999 and .2.999 has the length 2. |
||
226 | **/ |
||
227 | function oid_len($oid) { |
||
228 | if ($oid == '') return 0; |
||
229 | if ($oid[0] == '.') $oid = substr($oid, 1); |
||
230 | return substr_count($oid, '.')+1; |
||
231 | } |
||
232 | function oid_depth($oid) { |
||
233 | return oid_len($oid); |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * Lists all parents of an OID. |
||
238 | * This function tolerates leading dots. The parent of '.' stays '.'. |
||
239 | * The OID will not be checked for validity! |
||
240 | * @author Daniel Marschall, ViaThinkSoft |
||
241 | * @version 2014-12-17 |
||
242 | * @param $oid (string)<br /> |
||
243 | * An OID in dot notation. |
||
244 | * @return (array<string>) An array with all parent OIDs. |
||
245 | **/ |
||
246 | function oid_parents($oid) { |
||
247 | $parents = array(); |
||
248 | |||
249 | while (oid_len($oid) > 1) { |
||
250 | $oid = oid_up($oid); |
||
251 | $parents[] = $oid; |
||
252 | } |
||
253 | |||
254 | if (substr($oid, 0, 1) == '.') $parents[] = '.'; |
||
255 | |||
256 | return $parents; |
||
257 | } |
||
258 | |||
259 | /* |
||
260 | assert(oid_parents('.1.2.999') == array('.1.2', '.1', '.')); |
||
261 | assert(oid_parents('1.2.999') == array('1.2', '1')); |
||
262 | assert(oid_parents('.') == array('.')); |
||
263 | assert(oid_parents('') == array()); |
||
264 | */ |
||
265 | |||
266 | /** |
||
267 | * Sorts an array containing OIDs in dot notation. |
||
268 | * @author Daniel Marschall, ViaThinkSoft |
||
269 | * @version 2014-12-09 |
||
270 | * @param $ary (array<string>)<br /> |
||
271 | * An array of OIDs in dot notation.<br /> |
||
272 | * This array will be changed by this method. |
||
273 | * @param $output_with_leading_dot (bool)<br /> |
||
274 | * true: The array will be normalized to OIDs with a leading dot. |
||
275 | * false: The array will be normalized to OIDs without a leading dot. (default) |
||
276 | **/ |
||
277 | function oidSort(&$ary, $output_with_leading_dot=false) { |
||
278 | $out = array(); |
||
279 | |||
280 | $none = $output_with_leading_dot ? '.' : ''; |
||
281 | |||
282 | $d = array(); |
||
283 | foreach ($ary as &$oid) { |
||
284 | if (($oid == '') || ($oid == '.')) { |
||
285 | $out[] = $none; |
||
286 | } else { |
||
287 | $oid = sanitizeOID($oid, 'auto'); // strike leading zeroes |
||
288 | $bry = explode('.', $oid, 2); |
||
289 | $firstarc = $bry[0]; |
||
290 | $rest = (isset($bry[1])) ? $bry[1] : ''; |
||
291 | $d[$firstarc][] = $rest; |
||
292 | } |
||
293 | } |
||
294 | unset($oid); |
||
295 | ksort($d); |
||
296 | |||
297 | foreach ($d as $firstarc => &$data) { |
||
298 | oidSort($data); |
||
299 | foreach ($data as &$rest) { |
||
300 | $out[] = ($output_with_leading_dot ? '.' : '')."$firstarc" . (($rest != $none) ? ".$rest" : ''); |
||
301 | } |
||
302 | } |
||
303 | unset($data); |
||
304 | |||
305 | $ary = $out; |
||
306 | } |
||
307 | |||
308 | /** |
||
12 | daniel-mar | 309 | * Checks if two OIDs in dot-notation are equal |
310 | * @author Daniel Marschall, ViaThinkSoft |
||
311 | * @version 2020-05-27 |
||
312 | * @param $oidA (string)<br /> |
||
313 | * First OID |
||
314 | * @param $oidB (string)<br /> |
||
315 | * Second OID |
||
316 | * @return (bool) True if the OIDs are equal |
||
317 | **/ |
||
318 | function oid_dotnotation_equal($oidA, $oidB) { |
||
319 | $oidA = sanitizeOID($oidA, false); |
||
320 | if ($oidA === false) return null; |
||
321 | |||
322 | $oidB = sanitizeOID($oidB, false); |
||
323 | if ($oidB === false) return null; |
||
324 | |||
325 | return $oidA === $oidB; |
||
326 | } |
||
327 | |||
328 | /** |
||
3 | daniel-mar | 329 | * Removes leading zeroes from an OID in dot notation. |
330 | * @author Daniel Marschall, ViaThinkSoft |
||
331 | * @version 2015-08-17 |
||
332 | * @param $oid (string)<br /> |
||
333 | * An OID in dot notation. |
||
334 | * @param $leading_dot (bool)<br /> |
||
335 | * true: The OID is valid, if it contains a leading dot.<br /> |
||
336 | * false (default): The OID is valid, if it does not contain a leading dot. |
||
337 | * 'auto: Allow both |
||
338 | * @return (mixed) The OID without leading dots, or <code>false</code> if the OID is syntactically wrong. |
||
339 | **/ |
||
340 | $oid_sanitize_cache = array(); |
||
341 | function sanitizeOID($oid, $leading_dot=false) { |
||
342 | if ($leading_dot) $leading_dot = substr($oid,0,1) == '.'; |
||
343 | |||
15 | daniel-mar | 344 | // We are using a cache, since this function is used very often by OIDplus |
3 | daniel-mar | 345 | global $oid_sanitize_cache; |
346 | $v = ($leading_dot ? 'T' : 'F').$oid; |
||
347 | if (isset($oid_sanitize_cache[$v])) return $oid_sanitize_cache[$v]; |
||
348 | |||
349 | if ($leading_dot) { |
||
350 | if ($oid == '.') return ''; |
||
351 | } else { |
||
352 | if ($oid == '') return ''; |
||
353 | } |
||
354 | |||
355 | $out = ''; |
||
356 | $ary = explode('.', $oid); |
||
357 | foreach ($ary as $n => &$a) { |
||
358 | if (($leading_dot) && ($n == 0)) { |
||
359 | if ($a != '') return false; |
||
360 | continue; |
||
361 | } |
||
362 | |||
363 | if (!ctype_digit($a)) return false; // does contain something other than digits |
||
364 | |||
365 | // strike leading zeroes |
||
366 | $a = preg_replace("@^0+@", '', $a); |
||
367 | if ($a == '') $a = 0; |
||
368 | |||
369 | if (($leading_dot) || ($n != 0)) $out .= '.'; |
||
370 | $out .= $a; |
||
371 | } |
||
372 | unset($a); |
||
373 | unset($ary); |
||
374 | |||
375 | $oid_sanitize_cache[$v] = $out; |
||
376 | return $out; |
||
377 | } |
||
378 | |||
379 | /** |
||
380 | * Shows the top arc of an OID. |
||
381 | * This function tolerates leading dots. |
||
382 | * @author Daniel Marschall, ViaThinkSoft |
||
383 | * @version 2014-12-16 |
||
384 | * @param $oid (string)<br /> |
||
385 | * An OID in dot notation. |
||
386 | * @return (mixed) The top arc of the OID or empty string if it is already the root ('.') |
||
387 | **/ |
||
388 | function oid_toparc($oid) { |
||
389 | $leadingdot = substr($oid,0,1) == '.'; |
||
390 | |||
391 | $oid = sanitizeOID($oid, $leadingdot); |
||
392 | if ($oid === false) return false; |
||
393 | |||
394 | if (!$leadingdot) $oid = '.'.$oid; |
||
395 | |||
396 | $p = strrpos($oid, '.'); |
||
397 | if ($p === false) return false; |
||
398 | $r = substr($oid, $p+1); |
||
399 | |||
400 | if ($leadingdot) { |
||
401 | # if ($r == '') return '.'; |
||
402 | return $r; |
||
403 | } else { |
||
404 | return substr($r, 1); |
||
405 | } |
||
406 | } |
||
407 | |||
408 | /** |
||
409 | * Calculates the distance between two OIDs. |
||
410 | * This function tolerates leading dots and leading zeroes. |
||
411 | * @author Daniel Marschall, ViaThinkSoft |
||
412 | * @version 2014-12-20 |
||
413 | * @param $a (string)<br /> |
||
414 | * An OID. |
||
415 | * @param $b (string)<br /> |
||
416 | * An OID. |
||
417 | * @return (string) false if both OIDs do not have a child-parent or parent-child relation, e.g. oid_distance('2.999.1.2.3', '2.999.4.5') = false, or if one of the OIDs is syntactially invalid<br /> |
||
418 | * >0 if $a is more specific than $b , e.g. oid_distance('2.999.1.2', '2.999') = 2<br /> |
||
419 | * <0 if $a is more common than $b , e.g. oid_distance('2.999', '2.999.1.2') = -2 |
||
420 | **/ |
||
421 | function oid_distance($a, $b) { |
||
422 | if (substr($a,0,1) == '.') $a = substr($a,1); |
||
423 | if (substr($b,0,1) == '.') $b = substr($b,1); |
||
424 | |||
425 | $a = sanitizeOID($a, false); |
||
426 | if ($a === false) return false; |
||
427 | $b = sanitizeOID($b, false); |
||
428 | if ($b === false) return false; |
||
429 | |||
430 | $ary = explode('.', $a); |
||
431 | $bry = explode('.', $b); |
||
432 | |||
433 | $min_len = min(count($ary), count($bry)); |
||
434 | |||
435 | for ($i=0; $i<$min_len; $i++) { |
||
436 | if ($ary[$i] != $bry[$i]) return false; |
||
437 | } |
||
438 | |||
439 | return count($ary) - count($bry); |
||
440 | } |
||
441 | /* |
||
442 | assert(oid_distance('2.999.1.2.3', '2.999.4.5') === false); |
||
443 | assert(oid_distance('2.999.1.2', '2.999') === 2); |
||
444 | assert(oid_distance('2.999', '2.999.1.2') === -2); |
||
445 | */ |
||
446 | |||
447 | /** |
||
448 | * Adds a leading dot to an OID. |
||
449 | * Leading zeroes are tolerated. |
||
450 | * @author Daniel Marschall, ViaThinkSoft |
||
451 | * @version 2014-12-20 |
||
452 | * @param $oid (string)<br /> |
||
453 | * An OID. |
||
454 | * @return (string) The OID with a leading dot or false if the OID is syntactially wrong. |
||
455 | **/ |
||
456 | function oid_add_leading_dot($oid) { |
||
457 | $oid = sanitizeOID($oid, 'auto'); |
||
458 | if ($oid === false) return false; |
||
459 | |||
460 | if ($oid[0] != '.') $oid = '.'.$oid; |
||
461 | return $oid; |
||
462 | } |
||
463 | |||
464 | /** |
||
465 | * Removes a leading dot to an OID. |
||
466 | * Leading zeroes are tolerated. |
||
467 | * @author Daniel Marschall, ViaThinkSoft |
||
468 | * @version 2014-12-20 |
||
469 | * @param $oid (string)<br /> |
||
470 | * An OID. |
||
471 | * @return (string) The OID without a leading dot or false if the OID is syntactially wrong. |
||
472 | **/ |
||
473 | function oid_remove_leading_dot($oid) { |
||
474 | $oid = sanitizeOID($oid, 'auto'); |
||
475 | if ($oid === false) return false; |
||
476 | |||
477 | if (substr($oid,0,1) == '.') $oid = substr($oid, 1); |
||
478 | return $oid; |
||
479 | } |
||
480 | |||
12 | daniel-mar | 481 | /** |
482 | * Find the common ancestor of two or more OIDs |
||
483 | * @author Daniel Marschall, ViaThinkSoft |
||
484 | * @version 2020-05-27 |
||
485 | * @param $oids (array)<br /> |
||
486 | * An array of multiple OIDs, e.g. 2.999.1 and 2.999.2.3.4 |
||
487 | * @return (mixed) The common ancestor, e.g. 2.999, or false if there is no common ancestor. |
||
488 | **/ |
||
489 | function oid_common_ancestor(array $oids) { |
||
490 | $shared = array(); |
||
3 | daniel-mar | 491 | |
12 | daniel-mar | 492 | if (!is_array($oids)) return false; |
493 | if (count($oids) === 0) return false; |
||
494 | |||
495 | foreach ($oids as &$oid) { |
||
496 | $oid = sanitizeOID($oid, false); |
||
497 | if ($oid === false) return false; |
||
498 | $oid = explode('.', $oid); |
||
499 | } |
||
500 | |||
501 | $max_ok = count($oids[0]); |
||
502 | for ($i=1; $i<count($oids); $i++) { |
||
503 | for ($j=0; $j<min(count($oids[$i]),count($oids[0])); $j++) { |
||
504 | if ($oids[$i][$j] != $oids[0][$j]) { |
||
505 | if ($j < $max_ok) $max_ok = $j; |
||
506 | break; |
||
507 | } |
||
508 | } |
||
509 | if ($j < $max_ok) $max_ok = $j; |
||
510 | } |
||
511 | |||
512 | $out = array(); |
||
513 | for ($i=0; $i<$max_ok; $i++) { |
||
514 | $out[] = $oids[0][$i]; |
||
515 | } |
||
516 | return implode('.', $out); |
||
517 | } |
||
518 | /* |
||
519 | assert(oid_shared_ancestor(array('2.999.4.5.3', '2.999.4.5')) === "2.999.4.5"); |
||
520 | assert(oid_shared_ancestor(array('2.999.4.5', '2.999.4.5.3')) === "2.999.4.5"); |
||
521 | assert(oid_shared_ancestor(array('2.999.1.2.3', '2.999.4.5')) === "2.999"); |
||
522 | */ |
||
523 | |||
524 | |||
3 | daniel-mar | 525 | # === OID-IRI NOTATION FUNCTIONS === |
526 | |||
527 | if (!function_exists('mb_ord')) { |
||
528 | # http://stackoverflow.com/a/24755772/3544341 |
||
529 | function mb_ord($char, $encoding = 'UTF-8') { |
||
530 | if ($encoding === 'UCS-4BE') { |
||
531 | list(, $ord) = (strlen($char) === 4) ? @unpack('N', $char) : @unpack('n', $char); |
||
532 | return $ord; |
||
533 | } else { |
||
534 | return mb_ord(mb_convert_encoding($char, 'UCS-4BE', $encoding), 'UCS-4BE'); |
||
535 | } |
||
536 | } |
||
537 | } |
||
538 | |||
539 | function iri_char_valid($c, $firstchar, $lastchar) { |
||
540 | // see Rec. ITU-T X.660, clause 7.5 |
||
541 | |||
542 | if (($firstchar || $lastchar) && ($c == '-')) return false; |
||
543 | |||
544 | if ($c == '-') return true; |
||
545 | if ($c == '.') return true; |
||
546 | if ($c == '_') return true; |
||
547 | if ($c == '~') return true; |
||
548 | if (($c >= '0') && ($c <= '9') && (!$firstchar)) return true; |
||
549 | if (($c >= 'A') && ($c <= 'Z')) return true; |
||
550 | if (($c >= 'a') && ($c <= 'z')) return true; |
||
551 | |||
552 | $v = mb_ord($c); |
||
553 | |||
554 | if (($v >= 0x000000A0) && ($v <= 0x0000DFFE)) return true; |
||
555 | if (($v >= 0x0000F900) && ($v <= 0x0000FDCF)) return true; |
||
556 | if (($v >= 0x0000FDF0) && ($v <= 0x0000FFEF)) return true; |
||
557 | if (($v >= 0x00010000) && ($v <= 0x0001FFFD)) return true; |
||
558 | if (($v >= 0x00020000) && ($v <= 0x0002FFFD)) return true; |
||
559 | if (($v >= 0x00030000) && ($v <= 0x0003FFFD)) return true; |
||
560 | if (($v >= 0x00040000) && ($v <= 0x0004FFFD)) return true; |
||
561 | if (($v >= 0x00050000) && ($v <= 0x0005FFFD)) return true; |
||
562 | if (($v >= 0x00060000) && ($v <= 0x0006FFFD)) return true; |
||
563 | if (($v >= 0x00070000) && ($v <= 0x0007FFFD)) return true; |
||
564 | if (($v >= 0x00080000) && ($v <= 0x0008FFFD)) return true; |
||
565 | if (($v >= 0x00090000) && ($v <= 0x0009FFFD)) return true; |
||
566 | if (($v >= 0x000A0000) && ($v <= 0x000AFFFD)) return true; |
||
567 | if (($v >= 0x000B0000) && ($v <= 0x000BFFFD)) return true; |
||
568 | if (($v >= 0x000C0000) && ($v <= 0x000CFFFD)) return true; |
||
569 | if (($v >= 0x000D0000) && ($v <= 0x000DFFFD)) return true; |
||
570 | if (($v >= 0x000E1000) && ($v <= 0x000EFFFD)) return true; |
||
571 | |||
572 | // Note: Rec. ITU-T X.660, clause 7.5.3 would also forbid ranges which are marked in ISO/IEC 10646 as "(This position shall not be used)" |
||
573 | // But tool implementers should be tolerate them, since these limitations can be removed in future. |
||
574 | |||
575 | return false; |
||
576 | } |
||
577 | |||
578 | function iri_arc_valid($arc, $allow_numeric=true) { |
||
579 | if ($arc == '') return false; |
||
580 | |||
13 | daniel-mar | 581 | $m = array(); |
3 | daniel-mar | 582 | if ($allow_numeric && preg_match('@^(\\d+)$@', $arc, $m)) return true; # numeric arc |
583 | |||
584 | // Question: Should we strip RTL/LTR characters? |
||
585 | |||
586 | if (mb_substr($arc, 2, 2) == '--') return false; // see Rec. ITU-T X.660, clause 7.5.4 |
||
587 | |||
588 | $array = array(); |
||
589 | preg_match_all('/./u', $arc, $array, PREG_SET_ORDER); |
||
590 | $len = count($array); |
||
591 | foreach ($array as $i => $char) { |
||
592 | if (!iri_char_valid($char[0], $i==0, $i==$len-1)) return false; |
||
593 | } |
||
594 | |||
595 | return true; |
||
596 | } |
||
597 | |||
598 | /** |
||
599 | * Checks if an IRI identifier is valid or not. |
||
600 | * @author Daniel Marschall, ViaThinkSoft |
||
601 | * @version 2014-12-17 |
||
602 | * @param $iri (string)<br /> |
||
603 | * An OID in OID-IRI notation, e.g. /Example/test |
||
604 | * @return (bool) true if the IRI identifier is valid. |
||
605 | **/ |
||
606 | function iri_valid($iri) { |
||
607 | if ($iri == '/') return true; // OK? |
||
608 | |||
609 | if (substr($iri, 0, 1) != '/') return false; |
||
610 | |||
611 | $ary = explode('/', $iri); |
||
612 | array_shift($ary); |
||
613 | foreach ($ary as $a) { |
||
614 | if (!iri_arc_valid($a)) return false; |
||
615 | } |
||
616 | |||
617 | return true; |
||
618 | } |
||
619 | |||
620 | /* |
||
621 | assert(iri_arc_valid('ABCDEF')); |
||
622 | assert(!iri_arc_valid('-ABCDEF')); |
||
623 | assert(!iri_arc_valid('ABCDEF-')); |
||
624 | assert(!iri_arc_valid(' ABCDEF')); |
||
625 | assert(!iri_arc_valid('2 ABCDEF')); |
||
626 | assert(!iri_arc_valid('')); |
||
627 | |||
628 | assert(!iri_valid('')); |
||
629 | assert(iri_valid('/')); |
||
630 | assert(iri_valid('/hello/world')); |
||
631 | assert(iri_valid('/123/world')); |
||
632 | assert(!iri_valid('/hello/0world')); |
||
633 | assert(!iri_valid('/hello/xo--test')); |
||
634 | assert(!iri_valid('/hello/-super-/sd')); |
||
635 | */ |
||
636 | |||
637 | /** |
||
638 | * Returns an associative array in the form 'ASN.1' => '/2/1' . |
||
639 | * @author Daniel Marschall, ViaThinkSoft |
||
640 | * @version 2018-01-05 |
||
641 | * @see http://itu.int/go/X660 |
||
642 | * @return (array) An associative array in the form 'ASN.1' => '/2/1' . |
||
643 | **/ |
||
644 | function iri_get_long_arcs() { |
||
645 | $iri_long_arcs = array(); |
||
646 | $iri_long_arcs['ASN.1'] = '/2/1'; |
||
647 | $iri_long_arcs['Country'] = '/2/16'; |
||
648 | $iri_long_arcs['International-Organizations'] = '/2/23'; |
||
649 | $iri_long_arcs['UUID'] = '/2/25'; |
||
650 | $iri_long_arcs['Tag-Based'] = '/2/27'; |
||
651 | $iri_long_arcs['BIP'] = '/2/41'; |
||
652 | $iri_long_arcs['Telebiometrics'] = '/2/42'; |
||
653 | $iri_long_arcs['Cybersecurity'] = '/2/48'; |
||
654 | $iri_long_arcs['Alerting'] = '/2/49'; |
||
655 | $iri_long_arcs['OIDResolutionSystem'] = '/2/50'; |
||
656 | $iri_long_arcs['GS1'] = '/2/51'; |
||
657 | $iri_long_arcs['Example'] = '/2/999'; // English |
||
658 | $iri_long_arcs['Exemple'] = '/2/999'; // French |
||
659 | $iri_long_arcs['Ejemplo'] = '/2/999'; // Spanish |
||
660 | $iri_long_arcs["\u{0627}\u{0644}\u{0645}\u{062B}\u{0627}\u{0644}"] = '/2/999'; // Arabic |
||
661 | $iri_long_arcs["\u{8303}\u{4F8B}"] = '/2/999'; // Chinese |
||
662 | $iri_long_arcs["\u{041F}\u{0440}\u{0438}\u{043C}\u{0435}\u{0440}"] = '/2/999'; // Russian |
||
663 | $iri_long_arcs["\u{C608}\u{C81C}"] = '/2/999'; // Korean |
||
664 | $iri_long_arcs["\u{4F8B}"] = '/2/999'; // Japanese |
||
665 | $iri_long_arcs['Beispiel'] = '/2/999'; // German |
||
666 | return $iri_long_arcs; |
||
667 | } |
||
668 | |||
669 | /** |
||
670 | * Tries to shorten/simplify an IRI by applying "long arcs", e.g. /2/999/123 -> /Example/123 . |
||
671 | * @author Daniel Marschall, ViaThinkSoft |
||
12 | daniel-mar | 672 | * @version 2020-05-22 |
3 | daniel-mar | 673 | * @param $iri (string)<br /> |
674 | * An OID in OID-IRI notation, e.g. /Example/test |
||
675 | * @return (string) The modified IRI. |
||
676 | **/ |
||
677 | function iri_add_longarcs($iri) { |
||
678 | $iri_long_arcs = iri_get_long_arcs(); |
||
679 | |||
12 | daniel-mar | 680 | if (!iri_valid($iri)) return false; |
3 | daniel-mar | 681 | |
682 | $ary = explode('/', $iri); |
||
683 | |||
684 | $ary_number_iri = $ary; |
||
685 | if ($ary_number_iri[1] == 'Joint-ISO-ITU-T') $ary_number_iri[1] = '2'; |
||
12 | daniel-mar | 686 | |
3 | daniel-mar | 687 | $number_iri = implode('/', $ary_number_iri); |
688 | |||
689 | foreach ($iri_long_arcs as $cur_longarc => $cur_iri) { |
||
12 | daniel-mar | 690 | assert(iri_valid($cur_iri)); |
3 | daniel-mar | 691 | if (strpos($number_iri.'/', $cur_iri.'/') === 0) { |
692 | $cnt = substr_count($cur_iri, '/'); |
||
693 | for ($i=1; $i<$cnt; $i++) { |
||
694 | array_shift($ary); |
||
695 | } |
||
696 | $ary[0] = ''; |
||
697 | $ary[1] = $cur_longarc; |
||
698 | $iri = implode('/', $ary); |
||
699 | break; |
||
700 | } |
||
701 | } |
||
702 | |||
703 | return $iri; |
||
704 | } |
||
12 | daniel-mar | 705 | /* |
706 | assert(iri_add_longarcs('/2/999/123') === '/Example/123'); |
||
707 | */ |
||
3 | daniel-mar | 708 | |
709 | # === FUNCTIONS FOR OIDS IN ASN.1 NOTATION === |
||
710 | |||
711 | /** |
||
712 | * Checks if an ASN.1 identifier is valid. |
||
713 | * @author Daniel Marschall, ViaThinkSoft |
||
12 | daniel-mar | 714 | * @version 2020-05-22 |
3 | daniel-mar | 715 | * @param $id (string)<br /> |
716 | * An ASN.1 identifier, e.g. "example". Not "example(99)" or "99" and not a path like "{ 2 999 }" |
||
717 | * Note: Use asn1_path_valid() for validating a whole ASN.1 notation path. |
||
718 | * @return (bool) true, if the identifier is valid: It begins with an lowercase letter and contains only 0-9, a-z, A-Z and "-" |
||
719 | **/ |
||
720 | function oid_id_is_valid($id) { |
||
12 | daniel-mar | 721 | // see Rec. ITU-T X.660 | ISO/IEC 9834-1, clause 7.7 |
722 | // and Rec. ITU-T X.680 | ISO/IEC 8824-1, clause 12.3 |
||
723 | if (substr($id,-1,1) == '-') return false; |
||
724 | if (strstr($id,'--')) return false; |
||
725 | return preg_match('/^([a-z][a-zA-Z0-9-]*)$/', $id) != 0; |
||
3 | daniel-mar | 726 | } |
727 | |||
728 | /** |
||
729 | * Checks if the ASN.1 notation of an OID is valid. |
||
730 | * This function does not tolerate leading zeros. |
||
731 | * This function will fail (return false) if there are unresolved symbols, e.g. {iso test} is not valid while { iso 123 } is valid. |
||
732 | * @author Daniel Marschall, ViaThinkSoft |
||
733 | * @version 2014-12-17 |
||
734 | * @param $asn (string)<br /> |
||
735 | * An OID in ASN.1 notation. |
||
736 | * @return (bools) true if the identifier is valid. |
||
737 | **/ |
||
738 | function asn1_path_valid($asn1) { |
||
739 | return asn1_to_dot($asn1) != false; |
||
740 | } |
||
741 | |||
742 | /** |
||
743 | * Returns an array of standardized ASN.1 alphanumeric identifiers which do not require a numeric identifier, e.g. { 2 example } |
||
744 | * The array has the form '0.0.a' -> '0.0.1' |
||
745 | * @author Daniel Marschall, ViaThinkSoft |
||
746 | * @version 2019-03-25 |
||
747 | * @see http://www.oid-info.com/name-forms.htm |
||
748 | * @return (array) Associative array of standardized ASN.1 alphanumeric identifiers |
||
749 | **/ |
||
750 | function asn1_get_standardized_array() { |
||
751 | |||
752 | // Taken from oid-info.com |
||
753 | // http://www.oid-info.com/name-forms.htm |
||
754 | $standardized = array(); |
||
755 | $standardized['itu-t'] = '0'; |
||
756 | $standardized['ccitt'] = '0'; |
||
757 | $standardized['iso'] = '1'; |
||
758 | $standardized['joint-iso-itu-t'] = '2'; |
||
759 | $standardized['joint-iso-ccitt'] = '2'; |
||
760 | $standardized['0.recommendation'] = '0.0'; |
||
761 | $standardized['0.0.a'] = '0.0.1'; |
||
762 | $standardized['0.0.b'] = '0.0.2'; |
||
763 | $standardized['0.0.c'] = '0.0.3'; |
||
764 | $standardized['0.0.d'] = '0.0.4'; |
||
765 | $standardized['0.0.e'] = '0.0.5'; |
||
766 | $standardized['0.0.f'] = '0.0.6'; |
||
767 | $standardized['0.0.g'] = '0.0.7'; |
||
768 | $standardized['0.0.h'] = '0.0.8'; |
||
769 | $standardized['0.0.i'] = '0.0.9'; |
||
770 | $standardized['0.0.j'] = '0.0.10'; |
||
771 | $standardized['0.0.k'] = '0.0.11'; |
||
772 | $standardized['0.0.l'] = '0.0.12'; |
||
773 | $standardized['0.0.m'] = '0.0.13'; |
||
774 | $standardized['0.0.n'] = '0.0.14'; |
||
775 | $standardized['0.0.o'] = '0.0.15'; |
||
776 | $standardized['0.0.p'] = '0.0.16'; |
||
777 | $standardized['0.0.q'] = '0.0.17'; |
||
778 | $standardized['0.0.r'] = '0.0.18'; |
||
779 | $standardized['0.0.s'] = '0.0.19'; |
||
780 | $standardized['0.0.t'] = '0.0.20'; |
||
781 | $standardized['0.0.u'] = '0.0.21'; |
||
782 | $standardized['0.0.v'] = '0.0.22'; |
||
783 | $standardized['0.0.w'] = '0.0.23'; // actually, this OID does not exist |
||
784 | $standardized['0.0.x'] = '0.0.24'; |
||
785 | $standardized['0.0.y'] = '0.0.25'; |
||
786 | $standardized['0.0.z'] = '0.0.26'; |
||
787 | $standardized['0.question'] = '0.1'; |
||
788 | $standardized['0.administration'] = '0.2'; |
||
789 | $standardized['0.network-operator'] = '0.3'; |
||
790 | $standardized['0.identified-organization'] = '0.4'; |
||
791 | $standardized['1.standard'] = '1.0'; |
||
792 | $standardized['1.registration-authority'] = '1.1'; |
||
793 | $standardized['1.member-body'] = '1.2'; |
||
794 | $standardized['1.identified-organization'] = '1.3'; |
||
795 | return $standardized; |
||
796 | } |
||
797 | |||
798 | /** |
||
799 | * Converts an OID in ASN.1 notation into an OID in dot notation and tries to resolve well-known identifiers.<br /> |
||
800 | * e.g. {joint-iso-itu-t(2) example(999) 1 2 3} --> 2.999.1.2.3<br /> |
||
801 | * e.g. {iso 3} --> 1.3 |
||
802 | * This function does not tolerate leading zeros. |
||
803 | * This function will fail (return false) if there are unresolved symbols, e.g. {iso test} will not be resolved to 1.test |
||
804 | * @author Daniel Marschall, ViaThinkSoft |
||
805 | * @version 2014-12-17 |
||
806 | * @param $asn (string)<br /> |
||
807 | * An OID in ASN.1 notation. |
||
808 | * @return (string) An OID in dot notation without leading dot or false if the path is invalid. |
||
809 | **/ |
||
810 | function asn1_to_dot($asn) { |
||
811 | $standardized = asn1_get_standardized_array(); |
||
812 | |||
813 | // Clean up |
||
13 | daniel-mar | 814 | $count = -1; |
3 | daniel-mar | 815 | $asn = preg_replace('@^\\{(.+)\\}$@', '\\1', $asn, -1, $count); |
15 | daniel-mar | 816 | if ($count == 0) return false; // { and } are required. The ASN.1 path will NOT be trimmed by this function |
3 | daniel-mar | 817 | |
818 | // If identifier is set, apply it (no check if it overrides a standardized identifier) |
||
819 | $asn = preg_replace('|\s*([a-z][a-zA-Z0-9-]*)\s*\((\d+)\)|', ' \\2', $asn); |
||
820 | $asn = trim($asn); |
||
821 | |||
822 | // Set dots |
||
823 | $asn = preg_replace('|\s+|', '.', $asn); |
||
824 | |||
825 | // Apply standardized identifiers (case sensitive) |
||
826 | $asn .= '.'; |
||
827 | foreach ($standardized as $s => $r) { |
||
14 | daniel-mar | 828 | $asn = preg_replace("@^".preg_quote($s,"@")."@", $r, $asn); |
3 | daniel-mar | 829 | } |
830 | $asn = substr($asn, 0, strlen($asn)-1); |
||
831 | |||
832 | // Check if all numbers are OK |
||
833 | // -> every arc must be resolved |
||
834 | // -> numeric arcs must not have a leading zero |
||
835 | // -> invalid stuff will be recognized, e.g. a "(1)" without an identifier in front of it |
||
836 | $ary = explode('.', $asn); |
||
837 | foreach ($ary as $a) { |
||
13 | daniel-mar | 838 | $m = array(); |
3 | daniel-mar | 839 | if (!preg_match('@^(0|([1-9]\\d*))$@', $a, $m)) return false; |
840 | } |
||
841 | |||
842 | return $asn; |
||
843 | } |
||
844 | |||
845 | /* |
||
846 | assert(asn1_to_dot('{2 999 (1)}') == false); |
||
847 | assert(asn1_to_dot('{2 999 test}') == false); |
||
848 | assert(asn1_to_dot('{2 999 1}') == '2.999.1'); |
||
849 | assert(asn1_to_dot(' {2 999 1} ') == false); |
||
850 | assert(asn1_to_dot('2 999 1') == false); |
||
851 | assert(asn1_to_dot('{2 999 01}') == false); |
||
852 | assert(asn1_to_dot('{ 0 question 123 }') == '0.1.123'); |
||
853 | assert(asn1_to_dot('{ iso }') == '1'); |
||
854 | assert(asn1_to_dot('{ iso(1) }') == '1'); |
||
855 | assert(asn1_to_dot('{ iso(2) }') == '2'); |
||
856 | assert(asn1_to_dot('{ iso 3 }') == '1.3'); |
||
857 | */ |
||
858 | |||
859 | /** |
||
12 | daniel-mar | 860 | * Gets the last numeric identifier of an ASN.1 notation OID. |
861 | * @author Daniel Marschall, ViaThinkSoft |
||
862 | * @version 2020-06-11 |
||
863 | * @param $asn1id (string)<br /> |
||
864 | * An ASN.1 identifier string, e.g. { 2 example(999) test(1) } |
||
865 | * @return (int) The last numeric identifier arc, e.g. "1" |
||
866 | **/ |
||
867 | function asn1_last_identifier($asn1id) { |
||
868 | $asn1id = preg_replace('@\(\s*\d+\s*\)@', '', $asn1id); |
||
869 | $asn1id = trim(str_replace(array('{', '}', "\t"), ' ', $asn1id)); |
||
870 | $ary = explode(' ', $asn1id); |
||
871 | $asn1id = $ary[count($ary)-1]; |
||
872 | return preg_match('#[^0-9]#',$asn1id) ? $asn1id : false; |
||
873 | } |
||
874 | |||
875 | /** |
||
3 | daniel-mar | 876 | * "Soft corrects" an invalid ASN.1 identifier.<br /> |
877 | * Attention, by "soft correcting" the ID, it is not authoritative anymore, and might not be able to be resolved by ORS. |
||
878 | * @author Daniel Marschall, ViaThinkSoft |
||
12 | daniel-mar | 879 | * @version 2020-05-22 |
3 | daniel-mar | 880 | * @param $id (string)<br /> |
881 | * An ASN.1 identifier. |
||
882 | * @param $append_id_prefix (bool)<br /> |
||
883 | * true (default): If the identifier doesn't start with a-Z, the problem will be solved by prepending "id-" to the identifier.<br /> |
||
884 | * false: If the identifier doesn't start with a-Z, then the problem cannot be solved (method returns empty string). |
||
885 | * @return (string) The "soft corrected" ASN.1 identifier.<br /> |
||
886 | * Invalid characters will be removed.<br /> |
||
887 | * Uncorrectable start elements (0-9 or "-") will be either removed or solved by prepending "id-" (see <code>$append_id_prefix</code>)<br /> |
||
888 | * If the identifier begins with an upper case letter, the letter will be converted into lower case. |
||
889 | **/ |
||
890 | function oid_soft_correct_id($id, $append_id_prefix = true) { |
||
891 | // Convert "_" to "-" |
||
892 | $id = str_replace('_', '-', $id); |
||
893 | |||
12 | daniel-mar | 894 | // Convert "--" to "-" |
895 | $id = str_replace('--', '-', $id); |
||
896 | |||
3 | daniel-mar | 897 | // Remove invalid characters |
898 | $id = preg_replace('/[^a-zA-Z0-9-]+/', '', $id); |
||
899 | |||
900 | // Remove uncorrectable start elements (0-9 or "-") |
||
901 | if ($append_id_prefix) { |
||
902 | $id = preg_replace('/^([^a-zA-Z]+)/', 'id-$1', $id); |
||
903 | } else { |
||
904 | $id = preg_replace('/^([^a-zA-Z]+)/', '', $id); |
||
905 | } |
||
906 | |||
907 | // "Correct" upper case beginning letter by converting it to lower case |
||
908 | if (preg_match('/^[A-Z]/', $id)) { |
||
909 | $id = strtolower($id[0]) . substr($id, 1); |
||
910 | } |
||
911 | |||
912 | return $id; |
||
913 | } |