1408b9edf8eacfb890b75f728449ddc144348ae7
[racktables-incomplete-works] / wwwroot / inc / functions.php
1 <?php
2
3 # This file is a part of RackTables, a datacenter and server room management
4 # framework. See accompanying file "COPYING" for the full copyright and
5 # licensing information.
6
7 /*
8 *
9 * This file is a library of computational functions for RackTables.
10 *
11 */
12
13 $loclist[0] = 'front';
14 $loclist[1] = 'interior';
15 $loclist[2] = 'rear';
16 $loclist['front'] = 0;
17 $loclist['interior'] = 1;
18 $loclist['rear'] = 2;
19 $template[0] = array (TRUE, TRUE, TRUE);
20 $template[1] = array (TRUE, TRUE, FALSE);
21 $template[2] = array (FALSE, TRUE, TRUE);
22 $template[3] = array (TRUE, FALSE, FALSE);
23 $template[4] = array (FALSE, TRUE, FALSE);
24 $template[5] = array (FALSE, FALSE, TRUE);
25 $templateWidth[0] = 3;
26 $templateWidth[1] = 2;
27 $templateWidth[2] = 2;
28 $templateWidth[3] = 1;
29 $templateWidth[4] = 1;
30 $templateWidth[5] = 1;
31
32 define ('CHAP_OBJTYPE', 1);
33 define ('CHAP_PORTTYPE', 2);
34 // The latter matches both SunOS and Linux-styled formats.
35 define ('RE_L2_IFCFG', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){5}$/i');
36 define ('RE_L2_CISCO', '/^[0-9a-f]{4}(\.[0-9a-f]{4}){2}$/i');
37 define ('RE_L2_HUAWEI', '/^[0-9a-f]{4}(-[0-9a-f]{4}){2}$/i');
38 define ('RE_L2_SOLID', '/^[0-9a-f]{12}$/i');
39 define ('RE_L2_IPCFG', '/^[0-9a-f]{2}(-[0-9a-f]{2}){5}$/i');
40 define ('RE_L2_WWN_COLON', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){7}$/i');
41 define ('RE_L2_WWN_HYPHEN', '/^[0-9a-f]{2}(-[0-9a-f]{2}){7}$/i');
42 define ('RE_L2_WWN_SOLID', '/^[0-9a-f]{16}$/i');
43 define ('RE_IP4_ADDR', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}$#');
44 define ('RE_IP4_NET', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$#');
45 define ('E_8021Q_NOERROR', 0);
46 define ('E_8021Q_VERSION_CONFLICT', 101);
47 define ('E_8021Q_PULL_REMOTE_ERROR', 102);
48 define ('E_8021Q_PUSH_REMOTE_ERROR', 103);
49 define ('E_8021Q_SYNC_DISABLED', 104);
50 define ('VLAN_MIN_ID', 1);
51 define ('VLAN_MAX_ID', 4094);
52 define ('VLAN_DFL_ID', 1);
53 define ('TAB_REMEMBER_TIMEOUT', 300);
54
55 // Entity type by page number mapping is 1:1 atm, but may change later.
56 $etype_by_pageno = array
57 (
58 'ipv4net' => 'ipv4net',
59 'ipv6net' => 'ipv6net',
60 'ipv4rspool' => 'ipv4rspool',
61 'ipv4vs' => 'ipv4vs',
62 'object' => 'object',
63 'rack' => 'rack',
64 'row' => 'row',
65 'location' => 'location',
66 'user' => 'user',
67 'file' => 'file',
68 'vst' => 'vst',
69 );
70 $pageno_by_etype = array_flip ($etype_by_pageno);
71
72 // Rack thumbnail image width summands: "front", "interior" and "rear" elements w/o surrounding border.
73 $rtwidth = array
74 (
75 0 => 9,
76 1 => 21,
77 2 => 9
78 );
79
80 $location_obj_types = array
81 (
82 1560,
83 1561,
84 1562
85 );
86
87 // 802.1Q deploy queue titles
88 $dqtitle = array
89 (
90 'sync_aging' => 'Normal, aging',
91 'resync_aging' => 'Failed, aging',
92 'sync_ready' => 'Normal, ready for sync',
93 'resync_ready' => 'Failed, ready for retry',
94 'disabled' => 'Sync disabled',
95 'done' => 'Up to date',
96 );
97
98 $wdm_packs = array
99 (
100 '1000cwdm80' => array
101 (
102 'title' => '1000Base-CWDM80 (8 channels)',
103 'iif_ids' => array (3, 4),
104 'oif_ids' => array (1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216),
105 ),
106 '1000dwdm80' => array // ITU channels 20~61
107 (
108 'title' => '1000Base-DWDM80 (42 channels)',
109 'iif_ids' => array (3, 4),
110 'oif_ids' => array
111 (
112 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226,
113 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236,
114 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246,
115 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256,
116 1257, 1258
117 ),
118 ),
119 '10000dwdm80' => array // same channels for 10GE
120 (
121 'title' => '10GBase-ZR-DWDM80 (42 channels)',
122 'iif_ids' => array (9, 6, 5, 8, 7),
123 'oif_ids' => array
124 (
125 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268,
126 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278,
127 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288,
128 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298,
129 1299, 1300
130 ),
131 ),
132 '10000dwdm40' => array
133 (
134 'title' => '10GBase-ER-DWDM40 (42 channels)',
135 'iif_ids' => array (9, 6, 5, 8, 7),
136 'oif_ids' => array
137 (
138 1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434,
139 1435, 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444,
140 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454,
141 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, 1463, 1464,
142 1465, 1466
143 ),
144 ),
145 );
146
147 $log_messages = array(); // messages waiting for displaying
148
149 // This function assures that specified argument was passed
150 // and is a number greater than zero.
151 function assertUIntArg ($argname, $allow_zero = FALSE)
152 {
153 if (!isset ($_REQUEST[$argname]))
154 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
155 if (!is_numeric ($_REQUEST[$argname]))
156 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a number');
157 if ($_REQUEST[$argname] < 0)
158 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is less than zero');
159 if (!$allow_zero and $_REQUEST[$argname] == 0)
160 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is zero');
161 return $_REQUEST[$argname];
162 }
163
164 function isInteger ($arg, $allow_zero = FALSE)
165 {
166 if (! is_numeric ($arg))
167 return FALSE;
168 if (! $allow_zero and ! $arg)
169 return FALSE;
170 return TRUE;
171 }
172
173 # Make sure the arg is a parsable date, return its UNIX timestamp equivalent
174 # (or empty string for empty input, when allowed).
175 function assertDateArg ($argname, $ok_if_empty = FALSE)
176 {
177 if ('' == $arg = assertStringArg ($argname, $ok_if_empty))
178 return '';
179 try
180 {
181 return timestampFromDatetimestr ($arg);
182 }
183 catch (InvalidArgException $e)
184 {
185 throw convertToIRAE ($e, $argname);
186 }
187 }
188
189 // This function assures that specified argument was passed
190 // and is a non-empty string.
191 function assertStringArg ($argname, $ok_if_empty = FALSE)
192 {
193 global $sic;
194 if (!isset ($_REQUEST[$argname]))
195 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
196 if (!is_string ($_REQUEST[$argname]))
197 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
198 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
199 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
200 return $sic[$argname];
201 }
202
203 function assertBoolArg ($argname, $ok_if_empty = FALSE)
204 {
205 if (!isset ($_REQUEST[$argname]))
206 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
207 if (!is_string ($_REQUEST[$argname]) or $_REQUEST[$argname] != 'on')
208 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
209 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
210 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
211 return $_REQUEST[$argname] == TRUE;
212 }
213
214 // function returns binary IP address, or throws an exception
215 function assertIPArg ($argname)
216 {
217 try
218 {
219 return ip_parse (assertStringArg ($argname));
220 }
221 catch (InvalidArgException $e)
222 {
223 throw convertToIRAE ($e, $argname);
224 }
225 }
226
227 // function returns binary IPv4 address, or throws an exception
228 function assertIPv4Arg ($argname)
229 {
230 try
231 {
232 return ip4_parse (assertStringArg ($argname));
233 }
234 catch (InvalidArgException $e)
235 {
236 throw convertToIRAE ($e, $argname);
237 }
238 }
239
240 // function returns binary IPv6 address, or throws an exception
241 function assertIPv6Arg ($argname)
242 {
243 try
244 {
245 return ip6_parse (assertStringArg ($argname));
246 }
247 catch (InvalidArgException $e)
248 {
249 throw convertToIRAE ($e, $argname);
250 }
251 }
252
253 function assertPCREArg ($argname)
254 {
255 $arg = assertStringArg ($argname, TRUE); // empty pattern is Ok
256 if (FALSE === @preg_match ($arg, 'test'))
257 throw new InvalidRequestArgException($argname, $arg, 'PCRE validation failed');
258 return $arg;
259 }
260
261 function isPCRE ($arg)
262 {
263 if (! isset ($arg) or FALSE === @preg_match ($arg, 'test'))
264 return FALSE;
265 return TRUE;
266 }
267
268 function genericAssertion ($argname, $argtype)
269 {
270 global $sic;
271 switch ($argtype)
272 {
273 case 'string':
274 return assertStringArg ($argname);
275 case 'string0':
276 return assertStringArg ($argname, TRUE);
277 case 'uint':
278 return assertUIntArg ($argname);
279 case 'uint-uint':
280 if (! preg_match ('/^([1-9][0-9]*)-([1-9][0-9]*)$/', assertStringArg ($argname), $m))
281 throw new InvalidRequestArgException ($argname, $sic[$argname], 'illegal format');
282 return $m;
283 case 'uint0':
284 return assertUIntArg ($argname, TRUE);
285 case 'inet':
286 return assertIPArg ($argname);
287 case 'inet4':
288 return assertIPv4Arg ($argname);
289 case 'inet6':
290 return assertIPv6Arg ($argname);
291 case 'l2address':
292 return assertStringArg ($argname);
293 case 'l2address0':
294 assertStringArg ($argname, TRUE);
295 try
296 {
297 l2addressForDatabase ($sic[$argname]);
298 }
299 catch (InvalidArgException $e)
300 {
301 throw new InvalidRequestArgException ($argname, $sic[$argname], 'malformed MAC/WWN address');
302 }
303 return $sic[$argname];
304 break;
305 case 'tag':
306 if (!validTagName (assertStringArg ($argname)))
307 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Invalid tag name');
308 return $sic[$argname];
309 case 'pcre':
310 return assertPCREArg ($argname);
311 case 'json':
312 if (NULL === ($ret = json_decode (assertStringArg ($argname), TRUE)))
313 throw new InvalidRequestArgException ($argname, '(omitted)', 'Invalid JSON code received from client');
314 return $ret;
315 case 'array':
316 if (! array_key_exists ($argname, $_REQUEST))
317 throw new InvalidRequestArgException ($argname, '(missing argument)');
318 if (! is_array ($_REQUEST[$argname]))
319 throw new InvalidRequestArgException ($argname, '(omitted)', 'argument is not an array');
320 return $_REQUEST[$argname];
321 case 'enum/attr_type':
322 assertStringArg ($argname);
323 if (!in_array ($sic[$argname], array ('uint', 'float', 'string', 'dict','date')))
324 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
325 return $sic[$argname];
326 case 'enum/vlan_type':
327 assertStringArg ($argname);
328 // "Alien" type is not valid until the logic is fixed to implement it in full.
329 if (!in_array ($sic[$argname], array ('ondemand', 'compulsory')))
330 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
331 return $sic[$argname];
332 case 'enum/wdmstd':
333 assertStringArg ($argname);
334 global $wdm_packs;
335 if (! array_key_exists ($sic[$argname], $wdm_packs))
336 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
337 return $sic[$argname];
338 case 'enum/ipproto':
339 assertStringArg ($argname);
340 global $vs_proto;
341 if (!array_key_exists ($sic[$argname], $vs_proto))
342 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
343 return $sic[$argname];
344 case 'enum/alloc_type':
345 assertStringArg ($argname);
346 if (!in_array ($sic[$argname], array ('regular', 'shared', 'virtual', 'router')))
347 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
348 return $sic[$argname];
349 case 'enum/dqcode':
350 assertStringArg ($argname);
351 global $dqtitle;
352 if (! array_key_exists ($sic[$argname], $dqtitle))
353 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
354 return $sic[$argname];
355 case 'enum/yesno':
356 if (! in_array ($sic[$argname], array ('yes', 'no')))
357 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
358 return $sic[$argname];
359 case 'iif':
360 assertUIntArg ($argname);
361 if (!array_key_exists ($sic[$argname], getPortIIFOptions()))
362 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
363 return $sic[$argname];
364 case 'vlan':
365 case 'vlan1':
366 assertUIntArg ($argname);
367 if ($argtype == 'vlan' and $sic[$argname] == VLAN_DFL_ID)
368 throw new InvalidRequestArgException ($argname, $sic[$argname], 'default VLAN cannot be changed');
369 if ($sic[$argname] > VLAN_MAX_ID or $sic[$argname] < VLAN_MIN_ID)
370 throw new InvalidRequestArgException ($argname, $sic[$argname], 'out of valid range');
371 return $sic[$argname];
372 case 'rackcode/expr':
373 if ('' == assertStringArg ($argname, TRUE))
374 return array();
375 $parse = spotPayload ($sic[$argname], 'SYNT_EXPR');
376 if ($parse['result'] != 'ACK')
377 throw new InvalidRequestArgException ($argname, $sic[$argname], 'RackCode parsing error');
378 return $parse['load'];
379 default:
380 throw new InvalidArgException ('argtype', $argtype); // comes not from user's input
381 }
382 }
383
384 // return HTML form checkbox value (TRUE of FALSE) by name of its input control
385 function isCheckSet ($input_name, $mode = 'bool')
386 {
387 $value = isset ($_REQUEST[$input_name]) && $_REQUEST[$input_name] == 'on';
388 switch ($mode)
389 {
390 case 'bool' : return $value;
391 case 'yesno': return $value ? 'yes' : 'no';
392 default: throw new InvalidArgException ('mode', $mode);
393 }
394 }
395
396 // Validate and return "bypass" value for the current context, if one is
397 // defined for it, or NULL otherwise.
398 function getBypassValue()
399 {
400 global $page, $pageno, $sic;
401 if (!array_key_exists ('bypass', $page[$pageno]))
402 return NULL;
403 if (!array_key_exists ('bypass_type', $page[$pageno]))
404 throw new RackTablesError ("Internal structure error at node '${pageno}' (bypass_type is not set)", RackTablesError::INTERNAL);
405 return genericAssertion ($page[$pageno]['bypass'], $page[$pageno]['bypass_type']);
406 }
407
408 // fills $args array with the bypass values of specified $pageno which are provided in $_REQUEST
409 function fillBypassValues ($pageno, &$args)
410 {
411 global $page, $sic;
412 if (isset ($page[$pageno]['bypass']))
413 {
414 $param_name = $page[$pageno]['bypass'];
415 if (! array_key_exists ($param_name, $args) && isset ($sic[$param_name]))
416 $args[$param_name] = $sic[$param_name];
417 }
418 if (isset ($page[$pageno]['bypass_tabs']))
419 foreach ($page[$pageno]['bypass_tabs'] as $param_name)
420 if (! array_key_exists ($param_name, $args) && isset ($sic[$param_name]))
421 $args[$param_name] = $sic[$param_name];
422 }
423
424 // Objects of some types should be explicitly shown as
425 // anonymous (labelless). This function is a single place where the
426 // decision about displayed name is made.
427 function setDisplayedName (&$cell)
428 {
429 if ($cell['realm'] == 'object')
430 {
431 if ($cell['name'] != '')
432 $cell['dname'] = $cell['name'];
433 else
434 $cell['dname'] = '[' . decodeObjectType ($cell['objtype_id'], 'o') . ']';
435 // If the object has a container, apply the same logic to the container name
436 $cell['container_dname'] = NULL;
437 if ($cell['container_id'])
438 {
439 if ($cell['container_name'] != '')
440 $cell['container_dname'] = $cell['container_name'];
441 else
442 $cell['container_dname'] = '[' . decodeObjectType ($cell['container_objtype_id'], 'o') . ']';
443 }
444 }
445 elseif ($cell['realm'] == 'ipv4vs')
446 {
447 if ($cell['proto'] == 'MARK')
448 $cell['dname'] = "fwmark: " . implode ('', unpack('N', substr ($cell['vip_bin'], 0, 4)));
449 else
450 $cell['dname'] = $cell['vip'] . ':' . $cell['vport'] . '/' . $cell['proto'];
451 }
452 }
453
454 // This function finds height of solid rectangle of atoms, which are all
455 // assigned to the same object. Rectangle base is defined by specified
456 // template.
457 function rectHeight ($rackData, $startRow, $template_idx)
458 {
459 $height = 0;
460 // The first met object_id is used to match all the folowing IDs.
461 $object_id = 0;
462 global $template;
463 do
464 {
465 for ($locidx = 0; $locidx < 3; $locidx++)
466 {
467 // At least one value in template is TRUE, but the following block
468 // can meet 'skipped' atoms. Let's ensure we have something after processing
469 // the first row.
470 if ($template[$template_idx][$locidx])
471 {
472 if (isset ($rackData[$startRow - $height][$locidx]['skipped']))
473 break 2;
474 if (isset ($rackData[$startRow - $height][$locidx]['rowspan']))
475 break 2;
476 if (isset ($rackData[$startRow - $height][$locidx]['colspan']))
477 break 2;
478 if ($rackData[$startRow - $height][$locidx]['state'] != 'T')
479 break 2;
480 if ($object_id == 0)
481 $object_id = $rackData[$startRow - $height][$locidx]['object_id'];
482 if ($object_id != $rackData[$startRow - $height][$locidx]['object_id'])
483 break 2;
484 }
485 }
486 // If the first row can't offer anything, bail out.
487 if ($height == 0 and $object_id == 0)
488 break;
489 $height++;
490 }
491 while ($startRow - $height > 0);
492 # echo "for startRow==${startRow} and template==(" . ($template[$template_idx][0] ? 'T' : 'F');
493 # echo ', ' . ($template[$template_idx][1] ? 'T' : 'F') . ', ' . ($template[$template_idx][2] ? 'T' : 'F');
494 # echo ") height==${height}<br>\n";
495 return $height;
496 }
497
498 // This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan
499 // attributes.
500 function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
501 {
502 global $template, $templateWidth;
503 $colspan = 0;
504 for ($height = 0; $height < $maxheight; $height++)
505 {
506 for ($locidx = 0; $locidx < 3; $locidx++)
507 {
508 if ($template[$template_idx][$locidx])
509 {
510 // Add colspan/rowspan to the first row met and mark the following ones to skip.
511 // Explicitly show even single-cell spanned atoms, because rectHeight()
512 // is expeciting this data for correct calculation.
513 if ($colspan != 0)
514 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
515 else
516 {
517 $colspan = $templateWidth[$template_idx];
518 if ($colspan >= 1)
519 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
520 if ($maxheight >= 1)
521 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
522 }
523 }
524 }
525 }
526 return;
527 }
528
529 // This function sets rowspan/solspan/skipped atom attributes for renderRack()
530 // What we actually have to do is to find _all_ possible rectangles for each unit
531 // and then select the widest of those with the maximal square.
532 function markAllSpans (&$rackData)
533 {
534 for ($i = $rackData['height']; $i > 0; $i--)
535 while (markBestSpan ($rackData, $i));
536 }
537
538 // Calculate height of 6 possible span templates (array is presorted by width
539 // descending) and mark the best (if any).
540 function markBestSpan (&$rackData, $i)
541 {
542 global $template, $templateWidth;
543 for ($j = 0; $j < 6; $j++)
544 {
545 $height[$j] = rectHeight ($rackData, $i, $j);
546 $square[$j] = $height[$j] * $templateWidth[$j];
547 }
548 // find the widest rectangle of those with maximal height
549 $maxsquare = max ($square);
550 if (!$maxsquare)
551 return FALSE;
552 $best_template_index = 0;
553 for ($j = 0; $j < 6; $j++)
554 if ($square[$j] == $maxsquare)
555 {
556 $best_template_index = $j;
557 $bestheight = $height[$j];
558 break;
559 }
560 // distribute span marks
561 markSpan ($rackData, $i, $bestheight, $best_template_index);
562 return TRUE;
563 }
564
565 // We can mount 'F' atoms and unmount our own 'T' atoms.
566 function applyObjectMountMask (&$rackData, $object_id)
567 {
568 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
569 for ($locidx = 0; $locidx < 3; $locidx++)
570 switch ($rackData[$unit_no][$locidx]['state'])
571 {
572 case 'F':
573 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
574 break;
575 case 'T':
576 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id);
577 break;
578 default:
579 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
580 }
581 }
582
583 // Design change means transition between 'F' and 'A' and back.
584 function applyRackDesignMask (&$rackData)
585 {
586 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
587 for ($locidx = 0; $locidx < 3; $locidx++)
588 switch ($rackData[$unit_no][$locidx]['state'])
589 {
590 case 'F':
591 case 'A':
592 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
593 break;
594 default:
595 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
596 }
597 }
598
599 // The same for 'F' and 'U'.
600 function applyRackProblemMask (&$rackData)
601 {
602 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
603 for ($locidx = 0; $locidx < 3; $locidx++)
604 switch ($rackData[$unit_no][$locidx]['state'])
605 {
606 case 'F':
607 case 'U':
608 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
609 break;
610 default:
611 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
612 }
613 }
614
615 // This function highlights specified object (and removes previous highlight).
616 function highlightObject (&$rackData, $object_id)
617 {
618 // Also highlight parent objects
619 $parents = getEntityRelatives ('parents', 'object', $object_id);
620 $parent_ids = array();
621 foreach ($parents as $parent)
622 $parent_ids[] = $parent['entity_id'];
623
624 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
625 for ($locidx = 0; $locidx < 3; $locidx++)
626 if
627 (
628 $rackData[$unit_no][$locidx]['state'] == 'T' and
629 ($rackData[$unit_no][$locidx]['object_id'] == $object_id or in_array($rackData[$unit_no][$locidx]['object_id'], $parent_ids))
630 )
631 $rackData[$unit_no][$locidx]['hl'] = 'h';
632 else
633 unset ($rackData[$unit_no][$locidx]['hl']);
634 }
635
636 // This function marks atoms to selected or not depending on their current state.
637 function markupAtomGrid (&$data, $checked_state)
638 {
639 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
640 for ($locidx = 0; $locidx < 3; $locidx++)
641 {
642 if (!($data[$unit_no][$locidx]['enabled'] === TRUE))
643 continue;
644 if ($data[$unit_no][$locidx]['state'] == $checked_state)
645 $data[$unit_no][$locidx]['checked'] = ' checked';
646 else
647 $data[$unit_no][$locidx]['checked'] = '';
648 }
649 }
650
651 // This function is almost a clone of processGridForm(), but doesn't save anything to database
652 // Return value is the changed rack data.
653 // Here we assume that correct filter has already been applied, so we just
654 // set or unset checkbox inputs w/o changing atom state.
655 function mergeGridFormToRack (&$rackData)
656 {
657 $rack_id = $rackData['id'];
658 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
659 for ($locidx = 0; $locidx < 3; $locidx++)
660 {
661 if ($rackData[$unit_no][$locidx]['enabled'] != TRUE)
662 continue;
663 $inputname = "atom_${rack_id}_${unit_no}_${locidx}";
664 if (isset ($_REQUEST[$inputname]) and $_REQUEST[$inputname] == 'on')
665 $rackData[$unit_no][$locidx]['checked'] = ' checked';
666 else
667 $rackData[$unit_no][$locidx]['checked'] = '';
668 }
669 }
670
671 // wrapper around ip4_mask and ip6_mask
672 // netmask conversion from length to binary string
673 // v4/v6 mode is toggled by $is_ipv6 parameter
674 // Throws exception if $prefix_len is invalid
675 function ip_mask ($prefix_len, $is_ipv6)
676 {
677 if ($is_ipv6)
678 return ip6_mask ($prefix_len);
679 else
680 return ip4_mask ($prefix_len);
681 }
682
683 // netmask conversion from length to binary string
684 // Throws exception if $prefix_len is invalid
685 function ip4_mask ($prefix_len)
686 {
687 static $mask = array
688 (
689 "\x00\x00\x00\x00", // 0
690 "\x80\x00\x00\x00", // 1
691 "\xC0\x00\x00\x00", // 2
692 "\xE0\x00\x00\x00", // 3
693 "\xF0\x00\x00\x00", // 4
694 "\xF8\x00\x00\x00", // 5
695 "\xFC\x00\x00\x00", // 6
696 "\xFE\x00\x00\x00", // 7
697 "\xFF\x00\x00\x00", // 8
698 "\xFF\x80\x00\x00", // 9
699 "\xFF\xC0\x00\x00", // 10
700 "\xFF\xE0\x00\x00", // 11
701 "\xFF\xF0\x00\x00", // 12
702 "\xFF\xF8\x00\x00", // 13
703 "\xFF\xFC\x00\x00", // 14
704 "\xFF\xFE\x00\x00", // 15
705 "\xFF\xFF\x00\x00", // 16
706 "\xFF\xFF\x80\x00", // 17
707 "\xFF\xFF\xC0\x00", // 18
708 "\xFF\xFF\xE0\x00", // 19
709 "\xFF\xFF\xF0\x00", // 20
710 "\xFF\xFF\xF8\x00", // 21
711 "\xFF\xFF\xFC\x00", // 22
712 "\xFF\xFF\xFE\x00", // 23
713 "\xFF\xFF\xFF\x00", // 24
714 "\xFF\xFF\xFF\x80", // 25
715 "\xFF\xFF\xFF\xC0", // 26
716 "\xFF\xFF\xFF\xE0", // 27
717 "\xFF\xFF\xFF\xF0", // 28
718 "\xFF\xFF\xFF\xF8", // 29
719 "\xFF\xFF\xFF\xFC", // 30
720 "\xFF\xFF\xFF\xFE", // 31
721 "\xFF\xFF\xFF\xFF", // 32
722 );
723
724 if ($prefix_len >= 0 and $prefix_len <= 32)
725 return $mask[$prefix_len];
726 else
727 throw new InvalidArgException ('prefix_len', $prefix_len);
728 }
729
730 // netmask conversion from length to binary string
731 // Throws exception if $prefix_len is invalid
732 function ip6_mask ($prefix_len)
733 {
734 static $mask = array
735 (
736 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 0
737 "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 1
738 "\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 2
739 "\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 3
740 "\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 4
741 "\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 5
742 "\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 6
743 "\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 7
744 "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 8
745 "\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 9
746 "\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 10
747 "\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 11
748 "\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 12
749 "\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 13
750 "\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 14
751 "\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 15
752 "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 16
753 "\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 17
754 "\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 18
755 "\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 19
756 "\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 20
757 "\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 21
758 "\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 22
759 "\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 23
760 "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 24
761 "\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 25
762 "\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 26
763 "\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 27
764 "\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 28
765 "\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 29
766 "\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 30
767 "\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 31
768 "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 32
769 "\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 33
770 "\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 34
771 "\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 35
772 "\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 36
773 "\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 37
774 "\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 38
775 "\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 39
776 "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 40
777 "\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 41
778 "\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 42
779 "\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 43
780 "\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 44
781 "\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 45
782 "\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 46
783 "\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 47
784 "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 48
785 "\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 49
786 "\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 50
787 "\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 51
788 "\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 52
789 "\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 53
790 "\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 54
791 "\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 55
792 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 56
793 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00", // 57
794 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00", // 58
795 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00", // 59
796 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00", // 60
797 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00", // 61
798 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00", // 62
799 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00", // 63
800 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00", // 64
801 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00", // 65
802 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00", // 66
803 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00", // 67
804 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00", // 68
805 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00", // 69
806 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00", // 70
807 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00", // 71
808 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00", // 72
809 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00", // 73
810 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00", // 74
811 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00", // 75
812 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00", // 76
813 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00", // 77
814 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00", // 78
815 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00", // 79
816 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00", // 80
817 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00", // 81
818 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00", // 82
819 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00", // 83
820 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00", // 84
821 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00", // 85
822 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00", // 86
823 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00", // 87
824 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00", // 88
825 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00", // 89
826 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00", // 90
827 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00", // 91
828 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00", // 92
829 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00", // 93
830 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00", // 94
831 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00", // 95
832 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00", // 96
833 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00", // 97
834 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00", // 98
835 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00", // 99
836 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00", // 100
837 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00", // 101
838 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00", // 102
839 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00", // 103
840 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00", // 104
841 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00", // 105
842 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00", // 106
843 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00", // 107
844 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00", // 108
845 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00", // 109
846 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00", // 110
847 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00", // 111
848 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", // 112
849 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00", // 113
850 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00", // 114
851 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00", // 115
852 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00", // 116
853 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00", // 117
854 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00", // 118
855 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00", // 119
856 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", // 120
857 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80", // 121
858 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0", // 122
859 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0", // 123
860 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0", // 124
861 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8", // 125
862 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC", // 126
863 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE", // 127
864 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", // 128
865 );
866
867 if ($prefix_len >= 0 and $prefix_len <= 128)
868 return $mask[$prefix_len];
869 else
870 throw new InvalidArgException ('prefix_len', $prefix_len);
871 }
872
873 // This function looks up 'has_problems' flag for 'T' atoms
874 // and modifies 'hl' key. May be, this should be better done
875 // in amplifyCell(). We don't honour 'skipped' key, because
876 // the function is also used for thumb creation.
877 function markupObjectProblems (&$rackData)
878 {
879 for ($i = $rackData['height']; $i > 0; $i--)
880 for ($locidx = 0; $locidx < 3; $locidx++)
881 if ($rackData[$i][$locidx]['state'] == 'T')
882 {
883 $object = spotEntity ('object', $rackData[$i][$locidx]['object_id']);
884 if ($object['has_problems'] == 'yes')
885 {
886 // Object can be already highlighted.
887 if (isset ($rackData[$i][$locidx]['hl']))
888 $rackData[$i][$locidx]['hl'] = $rackData[$i][$locidx]['hl'] . 'w';
889 else
890 $rackData[$i][$locidx]['hl'] = 'w';
891 }
892 }
893 }
894
895 // Return a uniformly (010203040506 or 0102030405060708) formatted address, if it is present
896 // in the provided string, an empty string for an empty string or raise an exception.
897 function l2addressForDatabase ($string)
898 {
899 $string = strtoupper ($string);
900 switch (TRUE)
901 {
902 case ($string == '' or preg_match (RE_L2_SOLID, $string) or preg_match (RE_L2_WWN_SOLID, $string)):
903 return $string;
904 case (preg_match (RE_L2_IFCFG, $string) or preg_match (RE_L2_WWN_COLON, $string)):
905 // reformat output of SunOS ifconfig
906 $ret = '';
907 foreach (explode (':', $string) as $byte)
908 $ret .= (strlen ($byte) == 1 ? '0' : '') . $byte;
909 return $ret;
910 case (preg_match (RE_L2_CISCO, $string)):
911 return str_replace ('.', '', $string);
912 case (preg_match (RE_L2_HUAWEI, $string)):
913 return str_replace ('-', '', $string);
914 case (preg_match (RE_L2_IPCFG, $string) or preg_match (RE_L2_WWN_HYPHEN, $string)):
915 return str_replace ('-', '', $string);
916 default:
917 throw new InvalidArgException ('$string', $string, 'malformed MAC/WWN address');
918 }
919 }
920
921 function l2addressFromDatabase ($string)
922 {
923 switch (strlen ($string))
924 {
925 case 12: // Ethernet
926 case 16: // FireWire/Fibre Channel
927 $ret = implode (':', str_split ($string, 2));
928 break;
929 default:
930 $ret = $string;
931 break;
932 }
933 return $ret;
934 }
935
936 // The following 2 functions return previous and next rack IDs for
937 // a given rack ID. The order of racks is the same as in renderRackspace()
938 // or renderRow().
939 function getPrevIDforRack ($row_id, $rack_id)
940 {
941 $rackList = listCells ('rack', $row_id);
942 doubleLink ($rackList);
943 if (isset ($rackList[$rack_id]['prev_key']))
944 return $rackList[$rack_id]['prev_key'];
945 return NULL;
946 }
947
948 function getNextIDforRack ($row_id, $rack_id)
949 {
950 $rackList = listCells ('rack', $row_id);
951 doubleLink ($rackList);
952 if (isset ($rackList[$rack_id]['next_key']))
953 return $rackList[$rack_id]['next_key'];
954 return NULL;
955 }
956
957 // This function finds previous and next array keys for each array key and
958 // modifies its argument accordingly.
959 function doubleLink (&$array)
960 {
961 $prev_key = NULL;
962 foreach (array_keys ($array) as $key)
963 {
964 if ($prev_key)
965 {
966 $array[$key]['prev_key'] = $prev_key;
967 $array[$prev_key]['next_key'] = $key;
968 }
969 $prev_key = $key;
970 }
971 }
972
973 function sortTokenize ($a, $b)
974 {
975 $aold='';
976 while ($a != $aold)
977 {
978 $aold=$a;
979 $a = preg_replace('/[^a-zA-Z0-9]/',' ',$a);
980 $a = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$a);
981 $a = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$a);
982 }
983
984 $bold='';
985 while ($b != $bold)
986 {
987 $bold=$b;
988 $b = preg_replace('/[^a-zA-Z0-9]/',' ',$b);
989 $b = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$b);
990 $b = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$b);
991 }
992
993
994
995 $ar = explode(' ', $a);
996 $br = explode(' ', $b);
997 for ($i=0; $i<count($ar) && $i<count($br); $i++)
998 {
999 $ret = 0;
1000 if (is_numeric($ar[$i]) and is_numeric($br[$i]))
1001 $ret = ($ar[$i]==$br[$i])?0:($ar[$i]<$br[$i]?-1:1);
1002 else
1003 $ret = strcasecmp($ar[$i], $br[$i]);
1004 if ($ret != 0)
1005 return $ret;
1006 }
1007 if ($i<count($ar))
1008 return 1;
1009 if ($i<count($br))
1010 return -1;
1011 return 0;
1012 }
1013
1014 // This function returns an array of single element of object's FQDN attribute,
1015 // if FQDN is set. The next choice is object's common name, if it looks like a
1016 // hostname. Otherwise an array of all 'regular' IP addresses of the
1017 // object is returned (which may appear 0 and more elements long).
1018 function findAllEndpoints ($object_id, $fallback = '')
1019 {
1020 foreach (getAttrValues ($object_id) as $record)
1021 if ($record['id'] == 3 && strlen ($record['value'])) // FQDN
1022 return array ($record['value']);
1023 $regular = array();
1024 foreach (getObjectIPv4AllocationList ($object_id) as $ip_bin => $alloc)
1025 if ($alloc['type'] == 'regular')
1026 $regular[] = ip4_format ($ip_bin);
1027 // FIXME: add IPv6 allocations to this list
1028 if (!count ($regular) && strlen ($fallback))
1029 return array ($fallback);
1030 return $regular;
1031 }
1032
1033 // Some records in the dictionary may be written as plain text or as Wiki
1034 // link in the following syntax:
1035 // 1. word
1036 // 2. [[word URL]] // FIXME: this isn't working
1037 // 3. [[word word word | URL]]
1038 // This function parses the line in $record['value'] and modifies $record:
1039 // $record['o_value'] is set to be the first part of link (word word word)
1040 // $record['a_value'] is the same, but with %GPASS and %GSKIP macros applied
1041 // $record['href'] is set to URL if it is specified in the input value
1042 function parseWikiLink (&$record)
1043 {
1044 if (! preg_match ('/^\[\[(.+)\]\]$/', $record['value'], $matches))
1045 $record['o_value'] = $record['value'];
1046 else
1047 {
1048 $s = explode ('|', $matches[1]);
1049 if (isset ($s[1]))
1050 $record['href'] = trim ($s[1]);
1051 $record['o_value'] = trim ($s[0]);
1052 }
1053 $record['a_value'] = execGMarker ($record['o_value']);
1054 }
1055
1056 // FIXME: should this be saved as "P-data"?
1057 function execGMarker ($line)
1058 {
1059 return preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $line));
1060 }
1061
1062 // rackspace usage for a single rack
1063 // (T + W + U) / (height * 3 - A)
1064 function getRSUforRack ($data)
1065 {
1066 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
1067 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1068 for ($locidx = 0; $locidx < 3; $locidx++)
1069 $counter[$data[$unit_no][$locidx]['state']]++;
1070 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
1071 }
1072
1073 // Same for row.
1074 function getRSUforRow ($rowData)
1075 {
1076 if (!count ($rowData))
1077 return 0;
1078 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
1079 $total_height = 0;
1080 foreach (array_keys ($rowData) as $rack_id)
1081 {
1082 $data = spotEntity ('rack', $rack_id);
1083 amplifyCell ($data);
1084 $total_height += $data['height'];
1085 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1086 for ($locidx = 0; $locidx < 3; $locidx++)
1087 $counter[$data[$unit_no][$locidx]['state']]++;
1088 }
1089 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
1090 }
1091
1092 // Find any URL in a string and replace it with a clickable link
1093 // Adopted from UrlLinker: https://bitbucket.org/kwi/urllinker/src
1094 function string_insert_hrefs ($s)
1095 {
1096 if (getConfigVar ('DETECT_URLS') != 'yes')
1097 return $s;
1098
1099 $rexProtocol = '(https?://)?';
1100 $rexDomain = '(?:[-a-zA-Z0-9]{1,63}\.)+[a-zA-Z][-a-zA-Z0-9]{1,62}';
1101 $rexIp = '(?:[1-9][0-9]{0,2}\.|0\.){3}(?:[1-9][0-9]{0,2}|0)'; // doesn't support IPv6 addresses
1102 $rexPort = '(:[0-9]{1,5})?';
1103 $rexPath = '(/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?';
1104 $rexQuery = '(\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';
1105 $rexFragment = '(#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';
1106 $rexUsername = '[^]\\\\\x00-\x20\"(),:-<>[\x7f-\xff]{1,64}';
1107 $rexPassword = $rexUsername; // allow the same characters as in the username
1108 $rexUrl = "$rexProtocol(?:($rexUsername)(:$rexPassword)?@)?($rexDomain|$rexIp)($rexPort$rexPath$rexQuery$rexFragment)";
1109 $rexUrlLinker = "{\\b$rexUrl(?=[?.!,;:\"]?(\s|$))}";
1110
1111 $html = '';
1112 $position = 0;
1113 while (preg_match($rexUrlLinker, $s, $match, PREG_OFFSET_CAPTURE, $position))
1114 {
1115 list($url, $urlPosition) = $match[0];
1116
1117 // Add the text leading up to the URL.
1118 $html .= substr($s, $position, $urlPosition - $position);
1119
1120 $protocol = $match[1][0];
1121 $username = $match[2][0];
1122 $password = $match[3][0];
1123 $domain = $match[4][0];
1124 $afterDomain = $match[5][0]; // everything following the domain
1125 $port = $match[6][0];
1126 $path = $match[7][0];
1127
1128 // Do not permit implicit protocol if a password is specified, as
1129 // this causes too many errors (e.g. "my email:foo@example.org").
1130 if (!$protocol && $password)
1131 {
1132 $html .= htmlspecialchars($username);
1133
1134 // Continue text parsing at the ':' following the "username".
1135 $position = $urlPosition + strlen($username);
1136 continue;
1137 }
1138
1139 if (!$protocol && $username && !$password && !$afterDomain)
1140 {
1141 // Looks like an email address.
1142 $emailUrl = TRUE;
1143 $completeUrl = "mailto:$url";
1144 $linkText = $url;
1145 }
1146 else
1147 {
1148 // Prepend http:// if no protocol specified
1149 $completeUrl = $protocol ? $url : "http://$url";
1150 $linkText = "$protocol$domain$port$path";
1151 }
1152
1153 $linkHtml = '<a href="' . htmlspecialchars($completeUrl) . '">'
1154 . htmlspecialchars($linkText)
1155 . '</a>';
1156
1157 // It's not an e-mail address, provide an additional link with a new window/tab as the target
1158 if (!isset($emailUrl))
1159 $linkHtml .= ' [<a href="' . htmlspecialchars($completeUrl) . '" target="_blank">^</a>]';
1160 unset($emailUrl);
1161
1162 // Add the hyperlink.
1163 $html .= $linkHtml;
1164
1165 // Continue text parsing from after the URL.
1166 $position = $urlPosition + strlen($url);
1167 }
1168
1169 // Add the remainder of the text.
1170 $html .= substr($s, $position);
1171 return $html;
1172 }
1173
1174 // Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
1175 // for the requested object_type_id.
1176 function getAutoPorts ($type_id)
1177 {
1178 $ret = array();
1179 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
1180 foreach ($typemap as $equation)
1181 {
1182 $tmp = explode ('=', $equation);
1183 if (count ($tmp) != 2)
1184 continue;
1185 $objtype_id = $tmp[0];
1186 if ($objtype_id != $type_id)
1187 continue;
1188 $portlist = $tmp[1];
1189 foreach (explode ('+', $portlist) as $product)
1190 {
1191 $tmp = explode ('*', $product);
1192 if (count ($tmp) > 4 || count ($tmp) < 3)
1193 continue;
1194 # format: <number of ports>*<port_type_id>[*<sprintf_name>*<startnumber>]
1195 $nports = $tmp[0];
1196 $port_type = $tmp[1];
1197 $format = $tmp[2];
1198 $startnum = isset ($tmp[3]) ? $tmp[3] : 0;
1199 for ($i = 0; $i < $nports; $i++)
1200 $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i + $startnum));
1201 }
1202 }
1203 return $ret;
1204 }
1205
1206 // Use pre-served trace to traverse the tree, then place given node where it belongs.
1207 function pokeNode (&$tree, $trace, $key, $value, $threshold = 0)
1208 {
1209 // This function needs the trace to be followed FIFO-way. The fastest
1210 // way to do so is to use array_push() for putting values into the
1211 // list and array_shift() for getting them out. This exposed up to 11%
1212 // performance gain compared to other patterns of array_push/array_unshift/
1213 // array_reverse/array_pop/array_shift conjunction.
1214 $myid = array_shift ($trace);
1215 if (!count ($trace)) // reached the target
1216 {
1217 if (!$threshold or ($threshold and $tree[$myid]['kidc'] + 1 < $threshold))
1218 $tree[$myid]['kids'][$key] = $value;
1219 // Reset accumulated records once, when the limit is reached, not each time
1220 // after that.
1221 if (++$tree[$myid]['kidc'] == $threshold)
1222 $tree[$myid]['kids'] = array();
1223 }
1224 else // not yet
1225 {
1226 $self = __FUNCTION__;
1227 $self ($tree[$myid]['kids'], $trace, $key, $value, $threshold);
1228 }
1229 }
1230
1231 // Likewise traverse the tree with the trace and return the final node.
1232 function peekNode ($tree, $trace, $target_id)
1233 {
1234 $self = __FUNCTION__;
1235 if (NULL === ($next = array_shift ($trace))) // warm
1236 {
1237 foreach ($tree as $node)
1238 if (array_key_exists ('id', $node) and $node['id'] == $target_id) // hot
1239 return $node;
1240 }
1241 else // cold
1242 {
1243 foreach ($tree as $node)
1244 if (array_key_exists ('id', $node) and $node['id'] == $next) // warmer
1245 return $self ($node['kids'], $trace, $target_id);
1246 }
1247 throw new RackTablesError ('inconsistent tree data', RackTablesError::INTERNAL);
1248 }
1249
1250 function treeItemCmp ($a, $b)
1251 {
1252 return $a['__tree_index'] - $b['__tree_index'];
1253 }
1254
1255 // Build a tree from the item list and return it. Input and output data is
1256 // indexed by item id (nested items in output are recursively stored in 'kids'
1257 // key, which is in turn indexed by id. Functions, which are ready to handle
1258 // tree collapsion/expansion themselves, may request non-zero threshold value
1259 // for smaller resulting tree.
1260 function treeFromList (&$orig_nodelist, $threshold = 0, $return_main_payload = TRUE)
1261 {
1262 $tree = array();
1263 $nodelist = $orig_nodelist;
1264
1265 // index the tree items by their order in $orig_nodelist
1266 $ti = 0;
1267 foreach ($nodelist as &$node_ref)
1268 {
1269 $node_ref['__tree_index'] = $ti++;
1270 $node_ref['kidc'] = 0;
1271 $node_ref['kids'] = array();
1272 }
1273
1274 // Array equivalent of traceEntity() function.
1275 $trace = array();
1276 do
1277 {
1278 $nextpass = FALSE;
1279 foreach (array_keys ($nodelist) as $nodeid)
1280 {
1281 $node = $nodelist[$nodeid];
1282 $parentid = $node['parent_id'];
1283 // When adding a node to the working tree, book another
1284 // iteration, because the new item could make a way for
1285 // others onto the tree. Also remove any item added from
1286 // the input list, so iteration base shrinks.
1287 // First check if we can assign directly.
1288 if ($parentid == NULL)
1289 {
1290 $tree[$nodeid] = $node;
1291 $trace[$nodeid] = array(); // Trace to root node is empty
1292 unset ($nodelist[$nodeid]);
1293 $nextpass = TRUE;
1294 }
1295 // Now look if it fits somewhere on already built tree.
1296 elseif (isset ($trace[$parentid]))
1297 {
1298 // Trace to a node is a trace to its parent plus parent id.
1299 $trace[$nodeid] = $trace[$parentid];
1300 $trace[$nodeid][] = $parentid;
1301 pokeNode ($tree, $trace[$nodeid], $nodeid, $node, $threshold);
1302 // path to any other node is made of all parent nodes plus the added node itself
1303 unset ($nodelist[$nodeid]);
1304 $nextpass = TRUE;
1305 }
1306 }
1307 }
1308 while ($nextpass);
1309 if (!$return_main_payload)
1310 return $nodelist;
1311 // update each input node with its backtrace route
1312 foreach ($trace as $nodeid => $route)
1313 $orig_nodelist[$nodeid]['trace'] = $route;
1314 sortTree ($tree, 'treeItemCmp'); // sort the resulting tree by the order in original list
1315 return $tree;
1316 }
1317
1318 // Build a tree from the tag list and return everything _except_ the tree.
1319 // IOW, return taginfo items, which have parent_id set and pointing outside
1320 // of the "normal" tree, which originates from the root.
1321 function getOrphanedTags ()
1322 {
1323 global $taglist;
1324 return treeFromList ($taglist, 0, FALSE);
1325 }
1326
1327 // removes implicit tags from ['etags'] array and fills ['itags'] array
1328 // Replaces call sequence "getExplicitTagsOnly, getImplicitTags"
1329 function sortEntityTags (&$cell)
1330 {
1331 global $taglist;
1332 if (! is_array ($cell['etags']))
1333 throw new InvalidArgException ('$cell[etags]', $cell['etags']);
1334 $cell['itags'] = array();
1335 foreach ($cell['etags'] as $tag_id => $taginfo)
1336 foreach ($taglist[$tag_id]['trace'] as $parent_id)
1337 {
1338 $cell['itags'][$parent_id] = $taglist[$parent_id];
1339 unset ($cell['etags'][$parent_id]);
1340 }
1341 }
1342
1343 // Return the list of missing implicit tags.
1344 function getImplicitTags ($oldtags)
1345 {
1346 global $taglist;
1347 $tmp = array();
1348 foreach ($oldtags as $taginfo)
1349 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']);
1350 // don't call array_unique here, it is in the function we will call now
1351 return buildTagChainFromIds ($tmp);
1352 }
1353
1354 // Minimize the chain: exclude all implicit tags and return the result.
1355 // This function makes use of an external cache with a miss/hit ratio
1356 // about 3/7 (ticket:255).
1357 function getExplicitTagsOnly ($chain)
1358 {
1359 global $taglist, $tagRelCache;
1360 $ret = array();
1361 foreach (array_keys ($chain) as $keyA) // check each A
1362 {
1363 $tagidA = $chain[$keyA]['id'];
1364 // do not include A in result, if A is seen on the trace of any B!=A
1365 foreach (array_keys ($chain) as $keyB)
1366 {
1367 $tagidB = $chain[$keyB]['id'];
1368 if ($tagidA == $tagidB)
1369 continue;
1370 if (!isset ($tagRelCache[$tagidA][$tagidB]))
1371 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']);
1372 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B
1373 continue 2; // skip this A
1374 }
1375 $ret[] = $chain[$keyA];
1376 }
1377 return $ret;
1378 }
1379
1380 // Check, if the given tag is present on the chain (will only work
1381 // for regular tags with tag ID set.
1382 function tagOnChain ($taginfo, $tagchain)
1383 {
1384 if (!isset ($taginfo['id']))
1385 return FALSE;
1386 foreach ($tagchain as $test)
1387 if ($test['id'] == $taginfo['id'])
1388 return TRUE;
1389 return FALSE;
1390 }
1391
1392 function tagNameOnChain ($tagname, $tagchain)
1393 {
1394 foreach ($tagchain as $test)
1395 if ($test['tag'] == $tagname)
1396 return TRUE;
1397 return FALSE;
1398 }
1399
1400 // Return TRUE, if two tags chains differ (order of tags doesn't matter).
1401 // Assume, that neither of the lists contains duplicates.
1402 // FIXME: a faster, than O(x^2) method is possible for this calculation.
1403 function tagChainCmp ($chain1, $chain2)
1404 {
1405 if (count ($chain1) != count ($chain2))
1406 return TRUE;
1407 foreach ($chain1 as $taginfo1)
1408 if (!tagOnChain ($taginfo1, $chain2))
1409 return TRUE;
1410 return FALSE;
1411 }
1412
1413
1414 // returns the subtree of $tagtree representing child tags of $tagid
1415 // returns NULL if error occured
1416 function getTagSubtree ($tagid)
1417 {
1418 global $tagtree, $taglist;
1419
1420 $subtree = array ('kids' => $tagtree);
1421 $trace = $taglist[$tagid]['trace'];
1422 $trace[] = $tagid;
1423 while (count ($trace))
1424 {
1425 $search_for = array_shift ($trace);
1426 foreach ($subtree['kids'] as $subtag)
1427 if ($subtag['id'] == $search_for)
1428 {
1429 $subtree = $subtag;
1430 continue 2;
1431 }
1432 return NULL;
1433 }
1434 return $subtree;
1435 }
1436
1437 // returns an array of tag ids which have $tagid as its parent (all levels)
1438 function getTagDescendents ($tagid)
1439 {
1440 $ret = array();
1441 if ($subtree = getTagSubtree ($tagid))
1442 {
1443 $stack = array ($subtree);
1444 while (count ($stack))
1445 {
1446 $subtree = array_pop ($stack);
1447 foreach ($subtree['kids'] as $subtag)
1448 {
1449 $ret[] = $subtag['id'];
1450 array_push ($stack, $subtag);
1451 }
1452 }
1453 }
1454 return $ret;
1455 }
1456
1457 function redirectIfNecessary ()
1458 {
1459 global
1460 $trigger,
1461 $pageno,
1462 $tabno;
1463 @session_start();
1464 if
1465 (
1466 ! isset ($_REQUEST['tab']) and
1467 isset ($_SESSION['RTLT'][$pageno]) and
1468 getConfigVar ('SHOW_LAST_TAB') == 'yes' and
1469 permitted ($pageno, $_SESSION['RTLT'][$pageno]['tabname']) and
1470 time() - $_SESSION['RTLT'][$pageno]['time'] <= TAB_REMEMBER_TIMEOUT
1471 )
1472 redirectUser (buildRedirectURL ($pageno, $_SESSION['RTLT'][$pageno]['tabname']));
1473
1474 // check if we accidentaly got on a dynamic tab that shouldn't be shown for this object
1475 if
1476 (
1477 isset ($trigger[$pageno][$tabno]) and
1478 !strlen (call_user_func ($trigger[$pageno][$tabno]))
1479 )
1480 {
1481 $_SESSION['RTLT'][$pageno]['dont_remember'] = 1;
1482 redirectUser (buildRedirectURL ($pageno, 'default'));
1483 }
1484 if (is_array (@$_SESSION['RTLT'][$pageno]) && isset ($_SESSION['RTLT'][$pageno]['dont_remember']))
1485 unset ($_SESSION['RTLT'][$pageno]['dont_remember']);
1486 // store the last visited tab name
1487 if (isset ($_REQUEST['tab']))
1488 $_SESSION['RTLT'][$pageno] = array ('tabname' => $tabno, 'time' => time());
1489 session_commit(); // if we are continuing to run, unlock session data
1490 }
1491
1492 function prepareNavigation()
1493 {
1494 global
1495 $pageno,
1496 $tabno;
1497 $pageno = (isset ($_REQUEST['page'])) ? $_REQUEST['page'] : 'index';
1498
1499 if (isset ($_REQUEST['tab']))
1500 $tabno = $_REQUEST['tab'];
1501 else
1502 $tabno = 'default';
1503 }
1504
1505 function fixContext ($target = NULL)
1506 {
1507 global
1508 $pageno,
1509 $auto_tags,
1510 $expl_tags,
1511 $impl_tags,
1512 $target_given_tags,
1513 $user_given_tags,
1514 $etype_by_pageno,
1515 $page;
1516
1517 if ($target !== NULL)
1518 {
1519 $target_given_tags = $target['etags'];
1520 // Don't reset autochain, because auth procedures could push stuff there in.
1521 // Another important point is to ignore 'user' realm, so we don't infuse effective
1522 // context with autotags of the displayed account.
1523 if ($target['realm'] != 'user')
1524 $auto_tags = array_merge ($auto_tags, $target['atags']);
1525 }
1526 elseif (array_key_exists ($pageno, $etype_by_pageno))
1527 {
1528 // Each page listed in the map above requires one uint argument.
1529 $target = spotEntity ($etype_by_pageno[$pageno], getBypassValue());
1530 $target_given_tags = $target['etags'];
1531 if ($target['realm'] != 'user')
1532 $auto_tags = array_merge ($auto_tags, $target['atags']);
1533 }
1534 elseif ($pageno == 'ipaddress' && $net = spotNetworkByIP (getBypassValue()))
1535 {
1536 // IP addresses inherit context tags from their parent networks
1537 $target_given_tags = $net['etags'];
1538 $auto_tags = array_merge ($auto_tags, $net['atags']);
1539 }
1540 // Explicit and implicit chains should be normally empty at this point, so
1541 // overwrite the contents anyway.
1542 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1543 $impl_tags = getImplicitTags ($expl_tags);
1544 }
1545
1546 # Merge e/i/a-tags of the given cell structures into current context, when
1547 # these aren't there yet.
1548 function spreadContext ($extracell)
1549 {
1550 global
1551 $auto_tags,
1552 $expl_tags,
1553 $impl_tags,
1554 $target_given_tags,
1555 $user_given_tags;
1556 foreach ($extracell['atags'] as $taginfo)
1557 if (! tagNameOnChain ($taginfo['tag'], $auto_tags))
1558 $auto_tags[] = $taginfo;
1559 $target_given_tags = mergeTagChains ($target_given_tags, $extracell['etags']);
1560 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1561 $impl_tags = getImplicitTags ($expl_tags);
1562 }
1563
1564 # return a structure suitable for feeding into restoreContext()
1565 function getContext()
1566 {
1567 global
1568 $auto_tags,
1569 $expl_tags,
1570 $impl_tags,
1571 $target_given_tags;
1572 return array
1573 (
1574 'auto_tags' => $auto_tags,
1575 'expl_tags' => $expl_tags,
1576 'impl_tags' => $impl_tags,
1577 'target_given_tags' => $target_given_tags,
1578 );
1579 }
1580
1581 function restoreContext ($ctx)
1582 {
1583 global
1584 $auto_tags,
1585 $expl_tags,
1586 $impl_tags,
1587 $target_given_tags;
1588 $auto_tags = $ctx['auto_tags'];
1589 $expl_tags = $ctx['expl_tags'];
1590 $impl_tags = $ctx['impl_tags'];
1591 $target_given_tags = $ctx['target_given_tags'];
1592 }
1593
1594 // Take a list of user-supplied tag IDs to build a list of valid taginfo
1595 // records indexed by tag IDs (tag chain).
1596 function buildTagChainFromIds ($tagidlist)
1597 {
1598 global $taglist;
1599 $ret = array();
1600 foreach (array_unique ($tagidlist) as $tag_id)
1601 if (isset ($taglist[$tag_id]))
1602 $ret[] = $taglist[$tag_id];
1603 return $ret;
1604 }
1605
1606 function buildTagIdsFromChain ($tagchain)
1607 {
1608 $ret = array();
1609 foreach ($tagchain as $taginfo)
1610 $ret[] = $taginfo['id'];
1611 return array_unique ($ret);
1612 }
1613
1614 // Process a given tag tree and return only meaningful branches. The resulting
1615 // (sub)tree will have refcnt leaves on every last branch.
1616 function getObjectiveTagTree ($tree, $realm, $preselect)
1617 {
1618 $self = __FUNCTION__;
1619 $ret = array();
1620 foreach ($tree as $taginfo)
1621 {
1622 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1623 // If the current node addresses something, add it to the result
1624 // regardless of how many sub-nodes it features.
1625 if
1626 (
1627 isset ($taginfo['refcnt'][$realm]) or
1628 count ($subsearch) > 1 or
1629 in_array ($taginfo['id'], $preselect)
1630 )
1631 $ret[] = array
1632 (
1633 'id' => $taginfo['id'],
1634 'tag' => $taginfo['tag'],
1635 'parent_id' => $taginfo['parent_id'],
1636 'refcnt' => $taginfo['refcnt'],
1637 'kids' => $subsearch
1638 );
1639 else
1640 $ret = array_merge ($ret, $subsearch);
1641 }
1642 return $ret;
1643 }
1644
1645 // Preprocess tag tree to get only tags which can effectively reduce given filter result,
1646 // than passes shrinked tag tree to getObjectiveTagTree and return its result.
1647 // This makes sense only if andor mode is 'and', otherwise function does not modify tree.
1648 // 'Given filter' is a pair of $entity_list(filter result) and $preselect(filter data).
1649 // 'Effectively' means reduce to non-empty result.
1650 function getShrinkedTagTree ($entity_list, $realm, $preselect)
1651 {
1652 global $tagtree;
1653 if ($preselect['andor'] != 'and' || empty($entity_list) && $preselect['is_empty'])
1654 return getObjectiveTagTree($tagtree, $realm, $preselect['tagidlist']);
1655
1656 $used_tags = array(); //associative, keys - tag ids, values - taginfos
1657 foreach ($entity_list as $entity)
1658 {
1659 foreach ($entity['etags'] as $etag)
1660 if (! array_key_exists($etag['id'], $used_tags))
1661 $used_tags[$etag['id']] = 1;
1662 else
1663 $used_tags[$etag['id']]++;
1664
1665 foreach ($entity['itags'] as $itag)
1666 if (! array_key_exists($itag['id'], $used_tags))
1667 $used_tags[$itag['id']] = 0;
1668 }
1669
1670 $shrinked_tree = shrinkSubtree($tagtree, $used_tags, $preselect, $realm);
1671 return getObjectiveTagTree($shrinked_tree, $realm, $preselect['tagidlist']);
1672 }
1673
1674 // deletes item from tag subtree unless it exists in $used_tags and not preselected
1675 function shrinkSubtree ($tree, $used_tags, $preselect, $realm)
1676 {
1677 $self = __FUNCTION__;
1678
1679 foreach ($tree as $i => &$item)
1680 {
1681 $item['kids'] = $self($item['kids'], $used_tags, $preselect, $realm);
1682 $item['kidc'] = count($item['kids']);
1683 if
1684 (
1685 ! array_key_exists($item['id'], $used_tags) &&
1686 ! in_array($item['id'], $preselect['tagidlist']) &&
1687 ! $item['kidc']
1688 )
1689 unset($tree[$i]);
1690 else {
1691 if (isset ($used_tags[$item['id']]) && $used_tags[$item['id']])
1692 $item['refcnt'][$realm] = $used_tags[$item['id']];
1693 else
1694 unset($item['refcnt'][$realm]);
1695 }
1696 }
1697 return $tree;
1698 }
1699
1700 // Get taginfo record by tag name, return NULL, if record doesn't exist.
1701 function getTagByName ($target_name)
1702 {
1703 global $taglist;
1704 foreach ($taglist as $taginfo)
1705 if ($taginfo['tag'] == $target_name)
1706 return $taginfo;
1707 return NULL;
1708 }
1709
1710 // Merge two chains, filtering dupes out. Return the resulting superset.
1711 function mergeTagChains ($chainA, $chainB)
1712 {
1713 // $ret = $chainA;
1714 // Reindex by tag id in any case.
1715 $ret = array();
1716 foreach ($chainA as $tag)
1717 $ret[$tag['id']] = $tag;
1718 foreach ($chainB as $tag)
1719 if (!isset ($ret[$tag['id']]))
1720 $ret[$tag['id']] = $tag;
1721 return $ret;
1722 }
1723
1724 # Return a list consisting of tag ID of the given tree node and IDs of all
1725 # nodes it contains.
1726 function getTagIDListForNode ($treenode)
1727 {
1728 $self = __FUNCTION__;
1729 $ret = array ($treenode['id']);
1730 foreach ($treenode['kids'] as $item)
1731 $ret = array_merge ($ret, $self ($item));
1732 return $ret;
1733 }
1734
1735 function getCellFilter ()
1736 {
1737 global $sic;
1738 global $pageno;
1739 $andor_used = FALSE;
1740 @session_start();
1741 // if the page is submitted we get an andor value so we know they are trying to start a new filter or clearing the existing one.
1742 if (isset($_REQUEST['andor']))
1743 $andor_used = TRUE;
1744 if ($andor_used || array_key_exists ('clear-cf', $_REQUEST))
1745 unset($_SESSION[$pageno]); // delete saved filter
1746
1747 // otherwise inject saved filter to the $_REQUEST and $sic vars
1748 elseif (isset ($_SESSION[$pageno]['filter']) and is_array ($_SESSION[$pageno]['filter']) and getConfigVar ('STATIC_FILTER') == 'yes')
1749 foreach (array('andor', 'cfe', 'cft[]', 'cfp[]', 'nft[]', 'nfp[]') as $param)
1750 {
1751 $param = str_replace ('[]', '', $param, $is_array);
1752 if (! isset ($_REQUEST[$param]) and isset ($_SESSION[$pageno]['filter'][$param]) and (!$is_array or is_array ($_SESSION[$pageno]['filter'][$param])))
1753 {
1754 $_REQUEST[$param] = $_SESSION[$pageno]['filter'][$param];
1755 if (! $is_array)
1756 $sic[$param] = $_REQUEST[$param];
1757 }
1758 }
1759
1760 $ret = array
1761 (
1762 'tagidlist' => array(),
1763 'tnamelist' => array(),
1764 'pnamelist' => array(),
1765 'negatedlist' => array(),
1766 'andor' => '',
1767 'text' => '',
1768 'extratext' => '',
1769 'expression' => array(),
1770 'urlextra' => '', // Just put text here and let makeHref call urlencode().
1771 'is_empty' => TRUE,
1772 );
1773 switch (TRUE)
1774 {
1775 case (!isset ($_REQUEST['andor'])):
1776 $andor = getConfigVar ('FILTER_DEFAULT_ANDOR');
1777 break;
1778 case ($_REQUEST['andor'] == 'and'):
1779 case ($_REQUEST['andor'] == 'or'):
1780 $_SESSION[$pageno]['filter']['andor'] = $_REQUEST['andor'];
1781 $ret['andor'] = $andor = $_REQUEST['andor'];
1782 break;
1783 default:
1784 showWarning ('Invalid and/or switch value in submitted form');
1785 return NULL;
1786 }
1787 // Both tags and predicates, which don't exist, should be
1788 // handled somehow. Discard them silently for now.
1789 global $taglist, $pTable;
1790 foreach (array ('cft', 'cfp', 'nft', 'nfp') as $param)
1791 if (isset ($_REQUEST[$param]) and is_array ($_REQUEST[$param]))
1792 {
1793 $_SESSION[$pageno]['filter'][$param] = $_REQUEST[$param];
1794 foreach ($_REQUEST[$param] as $req_key)
1795 {
1796 if (strpos ($param, 'ft') !== FALSE)
1797 {
1798 // param is a taglist
1799 if (! isset ($taglist[$req_key]))
1800 continue;
1801 $ret['tagidlist'][] = $req_key;
1802 $ret['tnamelist'][] = $taglist[$req_key]['tag'];
1803 $text = '{' . $taglist[$req_key]['tag'] . '}';
1804 }
1805 else
1806 {
1807 // param is a predicate list
1808 if (! isset ($pTable[$req_key]))
1809 continue;
1810 $ret['pnamelist'][] = $req_key;
1811 $text = '[' . $req_key . ']';
1812 }
1813 if (strpos ($param, 'nf') === 0)
1814 {
1815 $text = "not $text";
1816 $ret['negatedlist'][] = $req_key;
1817 }
1818 if (! empty ($ret['text']))
1819 {
1820 $andor_used = TRUE;
1821 $ret['text'] .= " $andor ";
1822 }
1823 $ret['text'] .= $text;
1824 $ret['urlextra'] .= '&' . $param . '[]=' . $req_key;
1825 }
1826 }
1827 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1828 if (isset ($sic['cfe']))
1829 {
1830 $_SESSION[$pageno]['filter']['cfe'] = $sic['cfe'];
1831 // Only consider extra text, when it is a correct RackCode expression.
1832 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1833 if ($parse['result'] == 'ACK')
1834 {
1835 $ret['extratext'] = trim ($sic['cfe']);
1836 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1837 }
1838 }
1839 $finaltext = array();
1840 if (strlen ($ret['text']))
1841 $finaltext[] = '(' . $ret['text'] . ')';
1842 if (strlen ($ret['extratext']))
1843 $finaltext[] = '(' . $ret['extratext'] . ')';
1844 $andor_used = $andor_used || (count($finaltext) > 1);
1845 $finaltext = implode (' ' . $andor . ' ', $finaltext);
1846 if (strlen ($finaltext))
1847 {
1848 $ret['is_empty'] = FALSE;
1849 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1850 $ret['expression'] = $parse['result'] == 'ACK' ? $parse['load'] : NULL;
1851 // It's not quite fair enough to put the blame of the whole text onto
1852 // non-empty "extra" portion of it, but it's the only user-generated portion
1853 // of it, thus the most probable cause of parse error.
1854 if (strlen ($ret['extratext']))
1855 $ret['extraclass'] = $parse['result'] == 'ACK' ? 'validation-success' : 'validation-error';
1856 }
1857 if (! $andor_used)
1858 $ret['andor'] = getConfigVar ('FILTER_DEFAULT_ANDOR');
1859 else
1860 $ret['urlextra'] .= '&andor=' . $ret['andor'];
1861 return $ret;
1862 }
1863
1864 function buildRedirectURL ($nextpage = NULL, $nexttab = NULL, $moreArgs = array())
1865 {
1866 global $page, $pageno, $tabno;
1867 if ($nextpage === NULL)
1868 $nextpage = $pageno;
1869 if ($nexttab === NULL)
1870 $nexttab = $tabno;
1871 $url = "index.php?page=${nextpage}&tab=${nexttab}";
1872
1873 if ($nextpage === $pageno)
1874 fillBypassValues ($nextpage, $moreArgs);
1875 foreach ($moreArgs as $arg => $value)
1876 if (is_array ($value))
1877 foreach ($value as $v)
1878 $url .= '&' . urlencode ($arg . '[]') . '=' . urlencode ($v);
1879 elseif ($arg != 'module')
1880 $url .= '&' . urlencode ($arg) . '=' . urlencode ($value);
1881 return $url;
1882 }
1883
1884 // store the accumulated message list into he $SESSION array to display them later
1885 function backupLogMessages()
1886 {
1887 global $log_messages;
1888 if (! empty ($log_messages))
1889 {
1890 @session_start();
1891 $_SESSION['log'] = $log_messages;
1892 }
1893 }
1894
1895 function redirectUser ($url)
1896 {
1897 backupLogMessages();
1898 header ("Location: " . $url);
1899 die;
1900 }
1901
1902 function getRackCodeStats ()
1903 {
1904 global $rackCode;
1905 $defc = $grantc = $modc = 0;
1906 foreach ($rackCode as $s)
1907 switch ($s['type'])
1908 {
1909 case 'SYNT_DEFINITION':
1910 $defc++;
1911 break;
1912 case 'SYNT_GRANT':
1913 $grantc++;
1914 break;
1915 case 'SYNT_CTXMOD':
1916 $modc++;
1917 break;
1918 default:
1919 break;
1920 }
1921 $ret = array
1922 (
1923 'Definition sentences' => $defc,
1924 'Grant sentences' => $grantc,
1925 'Context mod sentences' => $modc
1926 );
1927 return $ret;
1928 }
1929
1930 function getRackImageWidth ()
1931 {
1932 global $rtwidth;
1933 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
1934 }
1935
1936 function getRackImageHeight ($units)
1937 {
1938 return 3 + 3 + $units * 2;
1939 }
1940
1941 // Indicate occupation state of each IP address: none, ordinary or problematic.
1942 function markupIPAddrList (&$addrlist)
1943 {
1944 foreach (array_keys ($addrlist) as $ip_bin)
1945 {
1946 $refc = array
1947 (
1948 'shared' => 0, // virtual
1949 'virtual' => 0, // loopback
1950 'regular' => 0, // connected host
1951 'router' => 0 // connected gateway
1952 );
1953 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1954 $refc[$a['type']]++;
1955 $nvirtloopback = ($refc['shared'] + $refc['virtual'] + $refc['router'] > 0) ? 1 : 0; // modulus of virtual + shared + router
1956 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
1957 $nrealms = $nreserved + $nvirtloopback + $refc['regular']; // last is connected allocation
1958
1959 if ($nrealms == 1)
1960 $addrlist[$ip_bin]['class'] = 'trbusy';
1961 elseif ($nrealms > 1)
1962 $addrlist[$ip_bin]['class'] = 'trerror';
1963 elseif (! isIPAddressEmpty ($addrlist[$ip_bin], array ('name', 'comment', 'reserved', 'allocs', 'inpf', 'outpf')))
1964 $addrlist[$ip_bin]['class'] = 'trbusy';
1965 else
1966 $addrlist[$ip_bin]['class'] = '';
1967 }
1968 }
1969
1970 // Scan the given address list (returned by scanIPv4Space/scanIPv6Space) and return a list of all routers found.
1971 function findRouters ($addrlist)
1972 {
1973 $ret = array();
1974 foreach ($addrlist as $addr)
1975 foreach ($addr['allocs'] as $alloc)
1976 if ($alloc['type'] == 'router')
1977 $ret[] = array
1978 (
1979 'id' => $alloc['object_id'],
1980 'iface' => $alloc['name'],
1981 'dname' => $alloc['object_name'],
1982 'ip_bin' => $addr['ip_bin'],
1983 );
1984 return $ret;
1985 }
1986
1987 // compare binary IPs (IPv4 are less than IPv6)
1988 // valid return values are: 1, 0, -1
1989 function IPCmp ($ip_binA, $ip_binB)
1990 {
1991 if (strlen ($ip_binA) !== strlen ($ip_binB))
1992 return strlen ($ip_binA) < strlen ($ip_binB) ? -1 : 1;
1993 $ret = strcmp ($ip_binA, $ip_binB);
1994 $ret = ($ret > 0 ? 1 : ($ret < 0 ? -1 : 0));
1995 return $ret;
1996 }
1997
1998 // Compare networks. When sorting a tree, the records on the list will have
1999 // distinct base IP addresses.
2000 // valid return values are: 1, 0, -1, -2
2001 // -2 has special meaning: $netA includes $netB
2002 // "The comparison function must return an integer less than, equal to, or greater
2003 // than zero if the first argument is considered to be respectively less than,
2004 // equal to, or greater than the second." (c) PHP manual
2005 function IPNetworkCmp ($netA, $netB)
2006 {
2007 $ret = IPCmp ($netA['ip_bin'], $netB['ip_bin']);
2008 if ($ret == 0)
2009 $ret = $netA['mask'] < $netB['mask'] ? -1 : ($netA['mask'] > $netB['mask'] ? 1 : 0);
2010 if ($ret == -1 and $netA['ip_bin'] === ($netB['ip_bin'] & $netA['mask_bin']))
2011 $ret = -2;
2012 return $ret;
2013 }
2014
2015 function IPNetContainsOrEqual ($netA, $netB)
2016 {
2017 $res = IPNetworkCmp ($netA, $netB);
2018 return ($res == -2 || $res == 0);
2019 }
2020
2021 function IPNetContains ($netA, $netB)
2022 {
2023 return (-2 == IPNetworkCmp ($netA, $netB));
2024 }
2025
2026 function IPNetsIntersect ($netA, $netB)
2027 {
2028 return ($netA['ip_bin'] & $netB['mask_bin']) === $netB['ip_bin'] ||
2029 ($netB['ip_bin'] & $netA['mask_bin']) === $netA['ip_bin'];
2030 }
2031
2032 function ip_in_range ($ip_bin, $range)
2033 {
2034 return ($ip_bin & $range['mask_bin']) === $range['ip_bin'];
2035 }
2036
2037 // Modify the given tag tree so, that each level's items are sorted alphabetically.
2038 function sortTree (&$tree, $sortfunc = '')
2039 {
2040 if (!strlen ($sortfunc))
2041 return;
2042 $self = __FUNCTION__;
2043 usort ($tree, $sortfunc);
2044 // Don't make a mistake of directly iterating over the items of current level, because this way
2045 // the sorting will be performed on a _copy_ if each item, not the item itself.
2046 foreach (array_keys ($tree) as $tagid)
2047 $self ($tree[$tagid]['kids'], $sortfunc);
2048 }
2049
2050 function iptree_fill (&$netdata)
2051 {
2052 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
2053 return;
2054 // If we really have nested prefixes, they must fit into the tree.
2055 $worktree = $netdata;
2056 foreach ($netdata['kids'] as $pfx)
2057 iptree_embed ($worktree, $pfx);
2058 $netdata['kids'] = iptree_construct ($worktree);
2059 $netdata['kidc'] = count ($netdata['kids']);
2060 }
2061
2062 function iptree_construct ($node)
2063 {
2064 $self = __FUNCTION__;
2065
2066 if (!isset ($node['right']))
2067 {
2068 if (!isset ($node['kids']))
2069 {
2070 $node['kids'] = array();
2071 $node['kidc'] = 0;
2072 $node['name'] = '';
2073 }
2074 return array ($node);
2075 }
2076 else
2077 return array_merge ($self ($node['left']), $self ($node['right']));
2078 }
2079
2080 // returns TRUE if inet_ntop and inet_pton functions exist and support IPv6
2081 function is_inet_avail()
2082 {
2083 static $ret = NULL;
2084 if (! isset ($ret))
2085 $ret = is_callable ('inet_pton') && ! is_callable ('inet_ntop') && defined ('AF_INET6');
2086 return $ret;
2087 }
2088
2089 function ip_format ($ip_bin)
2090 {
2091 switch (strlen ($ip_bin))
2092 {
2093 case 4: return ip4_format ($ip_bin);
2094 case 16: return ip6_format ($ip_bin);
2095 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2096 }
2097 }
2098
2099 function ip4_format ($ip_bin)
2100 {
2101 if (4 == strlen ($ip_bin))
2102 {
2103 if (is_inet_avail())
2104 {
2105 $ret = @inet_ntop ($ip_bin);
2106 if ($ret !== FALSE)
2107 return $ret;
2108 }
2109 else
2110 return implode ('.', unpack ('C*', $ip_bin));
2111 }
2112 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2113 }
2114
2115 function ip6_format ($ip_bin)
2116 {
2117 do {
2118 if (16 != strlen ($ip_bin))
2119 break;
2120
2121 if (is_inet_avail())
2122 {
2123 $ret = @inet_ntop ($ip_bin);
2124 if ($ret !== FALSE)
2125 return $ret;
2126 break;
2127 }
2128
2129 // maybe this is IPv6-to-IPv4 address?
2130 if (substr ($ip_bin, 0, 12) == "\0\0\0\0\0\0\0\0\0\0\xff\xff")
2131 return '::ffff:' . implode ('.', unpack ('C*', substr ($ip_bin, 12, 4)));
2132
2133 $result = array();
2134 $hole_index = NULL;
2135 $max_hole_index = NULL;
2136 $hole_length = 0;
2137 $max_hole_length = 0;
2138
2139 for ($i = 0; $i < 8; $i++)
2140 {
2141 $unpacked = unpack ('n', substr ($ip_bin, $i * 2, 2));
2142 $value = array_shift ($unpacked);
2143 $result[] = dechex ($value & 0xffff);
2144 if ($value != 0)
2145 {
2146 unset ($hole_index);
2147 $hole_length = 0;
2148 }
2149 else
2150 {
2151 if (! isset ($hole_index))
2152 $hole_index = $i;
2153 if (++$hole_length >= $max_hole_length)
2154 {
2155 $max_hole_index = $hole_index;
2156 $max_hole_length = $hole_length;
2157 }
2158 }
2159 }
2160 if (isset ($max_hole_index))
2161 {
2162 array_splice ($result, $max_hole_index, $max_hole_length, array (''));
2163 if ($max_hole_index == 0 && $max_hole_length == 8)
2164 return '::';
2165 elseif ($max_hole_index == 0)
2166 return ':' . implode (':', $result);
2167 elseif ($max_hole_index + $max_hole_length == 8)
2168 return implode (':', $result) . ':';
2169 }
2170 return implode (':', $result);
2171 } while (FALSE);
2172
2173 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2174 }
2175
2176 function ip_parse ($ip)
2177 {
2178 if (is_inet_avail())
2179 {
2180 if (FALSE !== ($ret = @inet_pton ($ip)))
2181 return $ret;
2182 }
2183 elseif (FALSE !== strpos ($ip, ':'))
2184 return ip6_parse ($ip);
2185 else
2186 return ip4_parse ($ip);
2187
2188 throw new InvalidArgException ('ip', $ip, "Invalid IP address");
2189 }
2190
2191 function ip4_parse ($ip)
2192 {
2193 if (is_inet_avail())
2194 {
2195 if (FALSE !== ($ret = @inet_pton ($ip)))
2196 return $ret;
2197 }
2198 elseif (FALSE !== ($int = ip2long ($ip)))
2199 return pack ('N', $int);
2200
2201 throw new InvalidArgException ('ip', $ip, "Invalid IPv4 address");
2202 }
2203
2204 // returns 16-byte string ip_bin
2205 // throws exception if unable to parse
2206 function ip6_parse ($ip)
2207 {
2208 do {
2209 if (is_inet_avail())
2210 {
2211 if (FALSE !== ($ret = @inet_pton ($ip)))
2212 return $ret;
2213 break;
2214 }
2215
2216 if (empty ($ip))
2217 break;
2218
2219 $result = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // 16 bytes
2220 // remove one of double beginning/tailing colons
2221 if (substr ($ip, 0, 2) == '::')
2222 $ip = substr ($ip, 1);
2223 elseif (substr ($ip, -2, 2) == '::')
2224 $ip = substr ($ip, 0, strlen ($ip) - 1);
2225
2226 $tokens = explode (':', $ip);
2227 $last_token = $tokens[count ($tokens) - 1];
2228 $split = explode ('.', $last_token);
2229 if (count ($split) == 4)
2230 {
2231 $hex_tokens = array();
2232 $hex_tokens[] = dechex ($split[0] * 256 + $split[1]);
2233 $hex_tokens[] = dechex ($split[2] * 256 + $split[3]);
2234 array_splice ($tokens, -1, 1, $hex_tokens);
2235 }
2236 if (count ($tokens) > 8)
2237 break;
2238 for ($i = 0; $i < count ($tokens); $i++)
2239 {
2240 if ($tokens[$i] != '')
2241 {
2242 if (! set_word_value ($result, $i, $tokens[$i]))
2243 break;
2244 }
2245 else
2246 {
2247 $k = 8; //index in result string (last word)
2248 for ($j = count ($tokens) - 1; $j > $i; $j--) // $j is an index in $tokens for reverse walk
2249 if ($tokens[$j] == '')
2250 break;
2251 elseif (! set_word_value ($result, --$k, $tokens[$j]))
2252 break;
2253 if ($i != $j)
2254 break; //error, more than 1 '::' range
2255 break;
2256 }
2257 }
2258 if (! isset ($k) && count ($tokens) != 8)
2259 break;
2260 return $result;
2261 } while (FALSE);
2262
2263 throw new InvalidArgException ('ip', $ip, "Invalid IPv6 address");
2264 }
2265
2266 function ip_get_arpa ($ip_bin)
2267 {
2268 switch (strlen ($ip_bin))
2269 {
2270 case 4: return ip4_get_arpa ($ip_bin);
2271 case 16: return ip6_get_arpa ($ip_bin);
2272 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2273 }
2274 }
2275
2276 function ip4_get_arpa ($ip_bin)
2277 {
2278 $ret = '';
2279 for ($i = 3; $i >= 0; $i--)
2280 $ret .= ord($ip_bin[$i]) . '.';
2281 return $ret . 'in-addr.arpa';
2282 }
2283
2284 function ip6_get_arpa ($ip_bin)
2285 {
2286 $ret = '';
2287 $hex = implode ('', unpack('H32', $ip_bin));
2288 for ($i = 31; $i >= 0; $i--)
2289 $ret .= $hex[$i] . '.';
2290 return $ret . 'ip6.arpa';
2291 }
2292
2293 function set_word_value (&$haystack, $nword, $hexvalue)
2294 {
2295 // check that $hexvalue is like /^[0-9a-fA-F]*$/
2296 for ($i = 0; $i < strlen ($hexvalue); $i++)
2297 {
2298 $char = ord ($hexvalue[$i]);
2299 if (! ($char >= 0x30 && $char <= 0x39 || $char >= 0x41 && $char <= 0x46 || $char >=0x61 && $char <= 0x66))
2300 return FALSE;
2301 }
2302 $haystack = substr_replace ($haystack, pack ('n', hexdec ($hexvalue)), $nword * 2, 2);
2303 return TRUE;
2304 }
2305
2306 // returns binary IP or FALSE
2307 function ip_checkparse ($ip)
2308 {
2309 try
2310 {
2311 return ip_parse ($ip);
2312 }
2313 catch (InvalidArgException $e)
2314 {
2315 return FALSE;
2316 }
2317 }
2318
2319 // returns binary IP or FALSE
2320 function ip4_checkparse ($ip)
2321 {
2322 try
2323 {
2324 return ip4_parse ($ip);
2325 }
2326 catch (InvalidArgException $e)
2327 {
2328 return FALSE;
2329 }
2330 }
2331
2332 // returns binary IP or FALSE
2333 function ip6_checkparse ($ip)
2334 {
2335 try
2336 {
2337 return ip6_parse ($ip);
2338 }
2339 catch (InvalidArgException $e)
2340 {
2341 return FALSE;
2342 }
2343 }
2344
2345 function ip4_int2bin ($ip_int)
2346 {
2347 return pack ('N', $ip_int + 0);
2348 }
2349
2350 function ip4_bin2int ($ip_bin)
2351 {
2352 if (4 != strlen ($ip_bin))
2353 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2354 $ret = array_first (unpack ('N', $ip_bin));
2355 if (PHP_INT_SIZE > 4 && $ret < 0)
2356 $ret = $ret & 0xffffffff;
2357 return $ret;
2358 }
2359
2360 // Use this function only when you need to export binary ip out of PHP running context (e.g., DB)
2361 // !DO NOT perform arithmetic and bitwise operations with the result of this function!
2362 function ip4_bin2db ($ip_bin)
2363 {
2364 $ip_int = ip4_bin2int ($ip_bin);
2365 if ($ip_int < 0)
2366 return sprintf ('%u', 0x00000000 + $ip_int);
2367 else
2368 return $ip_int;
2369 }
2370
2371 function ip_last ($net)
2372 {
2373 return $net['ip_bin'] | ~$net['mask_bin'];
2374 }
2375
2376 function ip_next ($ip_bin)
2377 {
2378 $ret = $ip_bin;
2379 $p = 1;
2380 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2381 {
2382 $oct = $p + ord ($ret[$i]);
2383 $ret[$i] = chr ($oct & 0xff);
2384 if ($oct <= 255 and $oct >= 0)
2385 break;
2386 }
2387 return $ret;
2388 }
2389
2390 function ip_prev ($ip_bin)
2391 {
2392 $ret = $ip_bin;
2393 $p = -1;
2394 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2395 {
2396 $oct = $p + ord ($ret[$i]);
2397 $ret[$i] = chr ($oct & 0xff);
2398 if ($oct <= 255 and $oct >= 0)
2399 break;
2400 }
2401 return $ret;
2402 }
2403
2404 function ip4_range_size ($range)
2405 {
2406 return ip4_mask_size ($range['mask']);
2407 }
2408
2409 function ip4_mask_size ($mask)
2410 {
2411 switch (TRUE)
2412 {
2413 case ($mask > 1 && $mask <= 32):
2414 return (0x7fffffff >> ($mask - 1)) + 1;
2415 // constants below are not representable in 32-bit PHP's int type,
2416 // so they are literally hardcoded and returned as strings on 32-bit architecture.
2417 case ($mask == 1):
2418 return 2147483648;
2419 case ($mask == 0):
2420 return 4294967296;
2421 default:
2422 throw new InvalidArgException ('mask', $mask, 'Invalid IPv4 prefix length');
2423 }
2424 }
2425
2426 // returns array with keys 'ip', 'ip_bin', 'mask', 'mask_bin'
2427 function constructIPRange ($ip_bin, $mask)
2428 {
2429 $node = array();
2430 switch (strlen ($ip_bin))
2431 {
2432 case 4: // IPv4
2433 if ($mask < 0 || $mask > 32)
2434 throw new InvalidArgException ('mask', $mask, "Invalid v4 prefix length");
2435 $node['mask_bin'] = ip4_mask ($mask);
2436 $node['mask'] = $mask;
2437 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2438 $node['ip'] = ip4_format ($node['ip_bin']);
2439 break;
2440 case 16: // IPv6
2441 if ($mask < 0 || $mask > 128)
2442 throw new InvalidArgException ('mask', $mask, "Invalid v6 prefix length");
2443 $node['mask_bin'] = ip6_mask ($mask);
2444 $node['mask'] = $mask;
2445 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2446 $node['ip'] = ip6_format ($node['ip_bin']);
2447 break;
2448 default:
2449 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2450 }
2451 return $node;
2452 }
2453
2454 // Return minimal IP address structure
2455 function constructIPAddress ($ip_bin)
2456 {
2457 // common v4/v6 part
2458 $ret = array
2459 (
2460 'ip' => ip_format ($ip_bin),
2461 'ip_bin' => $ip_bin,
2462 'name' => '',
2463 'comment' => '',
2464 'reserved' => 'no',
2465 'allocs' => array(),
2466 'vslist' => array(),
2467 'rsplist' => array(),
2468 );
2469
2470 // specific v4 part
2471 if (strlen ($ip_bin) == 4)
2472 $ret = array_merge
2473 (
2474 $ret,
2475 array
2476 (
2477 'outpf' => array(),
2478 'inpf' => array(),
2479 )
2480 );
2481 return $ret;
2482 }
2483
2484 function iptree_embed (&$node, $pfx)
2485 {
2486 $self = __FUNCTION__;
2487
2488 // hit?
2489 if (0 == IPNetworkCmp ($node, $pfx))
2490 {
2491 $node = $pfx;
2492 return;
2493 }
2494 if ($node['mask'] == $pfx['mask'])
2495 throw new RackTablesError ('the recurring loop lost control', RackTablesError::INTERNAL);
2496
2497 // split?
2498 if (!isset ($node['right']))
2499 {
2500 $node['left'] = constructIPRange ($node['ip_bin'], $node['mask'] + 1);
2501 $node['right'] = constructIPRange (ip_last ($node), $node['mask'] + 1);
2502 }
2503
2504 if (IPNetContainsOrEqual ($node['left'], $pfx))
2505 $self ($node['left'], $pfx);
2506 elseif (IPNetContainsOrEqual ($node['right'], $pfx))
2507 $self ($node['right'], $pfx);
2508 else
2509 throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
2510 }
2511
2512 function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
2513 {
2514 if (!strlen ($func))
2515 return;
2516 $self = __FUNCTION__;
2517 foreach (array_keys ($tree) as $key)
2518 {
2519 $func ($tree[$key]);
2520 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
2521 continue;
2522 $self ($tree[$key]['kids'], $func);
2523 }
2524 }
2525
2526 function nodeIsCollapsed ($node)
2527 {
2528 return $node['symbol'] == 'node-collapsed';
2529 }
2530
2531 // sets 'addrlist', 'own_addrlist', 'addrc', 'own_addrc' keys of $node
2532 // 'addrc' and 'own_addrc' are sizes of 'addrlist' and 'own_addrlist', respectively
2533 function loadIPAddrList (&$node)
2534 {
2535 $node['addrlist'] = scanIPSpace (array (array ('first' => $node['ip_bin'], 'last' => ip_last ($node))));
2536
2537 if (! isset ($node['id']))
2538 $node['own_addrlist'] = $node['addrlist'];
2539 else
2540 {
2541 if ($node['kidc'] == 0)
2542 $node['own_addrlist'] = $node['addrlist'];
2543 //$node['own_addrlist'] = array();
2544 else
2545 {
2546 $node['own_addrlist'] = array();
2547 // node has childs
2548 foreach ($node['spare_ranges'] as $mask => $spare_list)
2549 foreach ($spare_list as $spare_ip)
2550 {
2551 $spare_range = constructIPRange ($spare_ip, $mask);
2552 foreach ($node['addrlist'] as $bin_ip => $addr)
2553 if (($bin_ip & $spare_range['mask_bin']) == $spare_range['ip_bin'])
2554 $node['own_addrlist'][$bin_ip] = $addr;
2555 }
2556 }
2557 }
2558 $node['addrc'] = count ($node['addrlist']);
2559 $node['own_addrc'] = count ($node['own_addrlist']);
2560 }
2561
2562 // returns the array of structure described by constructIPAddress
2563 function getIPAddress ($ip_bin)
2564 {
2565 $scanres = scanIPSpace (array (array ('first' => $ip_bin, 'last' => $ip_bin)));
2566 if (empty ($scanres))
2567 return constructIPAddress ($ip_bin);
2568 markupIPAddrList ($scanres);
2569 return $scanres[$ip_bin];
2570 }
2571
2572 function getIPv4Address ($ip_bin)
2573 {
2574 if (strlen ($ip_bin) != 4)
2575 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2576 return getIPAddress ($ip_bin);
2577 }
2578
2579 function getIPv6Address ($ip_bin)
2580 {
2581 if (strlen ($ip_bin) != 16)
2582 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2583 return getIPAddress ($ip_bin);
2584 }
2585
2586 function makeIPTree ($netlist)
2587 {
2588 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
2589 // so perform necessary pre-processing to calculate parent_id of each item of $netlist.
2590 $stack = array();
2591 foreach ($netlist as $net_id => &$net)
2592 {
2593 while (! empty ($stack))
2594 {
2595 $top_id = $stack[count ($stack) - 1];
2596 if (! IPNetContains ($netlist[$top_id], $net)) // unless $net is a child of stack top
2597 array_pop ($stack);
2598 else
2599 {
2600 $net['parent_id'] = $top_id;
2601 break;
2602 }
2603 }
2604 if (empty ($stack))
2605 $net['parent_id'] = NULL;
2606 array_push ($stack, $net_id);
2607 }
2608 unset ($stack);
2609
2610 $tree = treeFromList ($netlist); // medium call
2611 return $tree;
2612 }
2613
2614 function prepareIPTree ($netlist, $expanded_id = 0)
2615 {
2616 $tree = makeIPTree ($netlist);
2617 // complement the tree before markup to make the spare networks have "symbol" set
2618 treeApplyFunc ($tree, 'iptree_fill');
2619 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
2620 return $tree;
2621 }
2622
2623 # Traverse IPv4/IPv6 tree and return a list of all networks, which
2624 # exist in DB and don't have any sub-networks.
2625 function getTerminalNetworks ($tree)
2626 {
2627 $self = __FUNCTION__;
2628 $ret = array();
2629 foreach ($tree as $node)
2630 if ($node['kidc'] == 0 and isset ($node['realm']))
2631 $ret[] = $node;
2632 else
2633 $ret = array_merge ($ret, $self ($node['kids']));
2634 return $ret;
2635 }
2636
2637 // Check all items of the tree recursively, until the requested target id is
2638 // found. Mark all items leading to this item as "expanded", collapsing all
2639 // the rest, which exceed the given threshold (if the threshold is given).
2640 function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
2641 {
2642 $self = __FUNCTION__;
2643 $ret = FALSE;
2644 foreach (array_keys ($tree) as $key)
2645 {
2646 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
2647 $below = $self ($tree[$key]['kids'], $threshold, $target);
2648 $expand_enabled = ($target !== 'NONE');
2649 if (!$tree[$key]['kidc']) // terminal node
2650 $tree[$key]['symbol'] = 'spacer';
2651 elseif ($expand_enabled and $tree[$key]['kidc'] < $threshold)
2652 $tree[$key]['symbol'] = 'node-expanded-static';
2653 elseif ($expand_enabled and ($here or $below))
2654 $tree[$key]['symbol'] = 'node-expanded';
2655 else
2656 $tree[$key]['symbol'] = 'node-collapsed';
2657 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
2658 }
2659 return $ret;
2660 }
2661
2662 // Convert entity name to human-readable value
2663 function formatEntityName ($name)
2664 {
2665 switch ($name)
2666 {
2667 case 'ipv4net':
2668 return 'IPv4 Network';
2669 case 'ipv6net':
2670 return 'IPv6 Network';
2671 case 'ipv4rspool':
2672 return 'IPv4 RS Pool';
2673 case 'ipv4vs':
2674 return 'IPv4 Virtual Service';
2675 case 'object':
2676 return 'Object';
2677 case 'rack':
2678 return 'Rack';
2679 case 'row':
2680 return 'Row';
2681 case 'location':
2682 return 'Location';
2683 case 'user':
2684 return 'User';
2685 }
2686 return 'invalid';
2687 }
2688
2689 // Convert filesize to appropriate unit and make it human-readable
2690 function formatFileSize ($bytes)
2691 {
2692 // bytes
2693 if($bytes < 1024) // bytes
2694 return "${bytes} bytes";
2695
2696 // kilobytes
2697 if ($bytes < 1024000)
2698 return sprintf ("%.1fk", round (($bytes / 1024), 1));
2699
2700 // megabytes
2701 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
2702 }
2703
2704 // Reverse of formatFileSize, it converts human-readable value to bytes
2705 function convertToBytes ($value)
2706 {
2707 $value = trim($value);
2708 $last = strtolower($value[strlen($value)-1]);
2709 switch ($last)
2710 {
2711 case 'g':
2712 $value *= 1024;
2713 case 'm':
2714 $value *= 1024;
2715 case 'k':
2716 $value *= 1024;
2717 }
2718
2719 return $value;
2720 }
2721
2722 // make "A" HTML element
2723 function mkA ($text, $nextpage, $bypass = NULL, $nexttab = NULL)
2724 {
2725 global $page, $tab;
2726 if ($text == '')
2727 throw new InvalidArgException ('text', $text);
2728 if (! array_key_exists ($nextpage, $page))
2729 throw new InvalidArgException ('nextpage', $nextpage, 'not found');
2730 $args = array ('page' => $nextpage);
2731 if ($nexttab !== NULL)
2732 {
2733 if (! array_key_exists ($nexttab, $tab[$nextpage]))
2734 throw new InvalidArgException ('nexttab', $nexttab, 'not found');
2735 $args['tab'] = $nexttab;
2736 }
2737 if (array_key_exists ('bypass', $page[$nextpage]))
2738 {
2739 if ($bypass === NULL)
2740 throw new InvalidArgException ('bypass', '(NULL)');
2741 $args[$page[$nextpage]['bypass']] = $bypass;
2742 }
2743 return '<a href="' . makeHref ($args) . '">' . $text . '</a>';
2744 }
2745
2746 // make "HREF" HTML attribute
2747 function makeHref ($params = array())
2748 {
2749 $tmp = array();
2750 foreach ($params as $key => $value)
2751 {
2752 if (is_array ($value))
2753 $key .= "[]";
2754 else
2755 $value = array ($value);
2756 if (!count ($value))
2757 $tmp[] = urlencode ($key) . '=';
2758 else
2759 foreach ($value as $sub_value)
2760 $tmp[] = urlencode ($key) . '=' . urlencode ($sub_value);
2761 }
2762 return 'index.php?' . implode ('&', $tmp);
2763 }
2764
2765 function makeHrefProcess ($params = array())
2766 {
2767 global $pageno, $tabno, $page;
2768 $tmp = array();
2769 if (! array_key_exists ('page', $params))
2770 $params['page'] = $pageno;
2771 if (! array_key_exists ('tab', $params))
2772 $params['tab'] = $tabno;
2773 if ($params['page'] === $pageno)
2774 fillBypassValues ($pageno, $params);
2775 foreach ($params as $key => $value)
2776 $tmp[] = urlencode ($key) . '=' . urlencode ($value);
2777 return '?module=redirect&' . implode ('&', $tmp);
2778 }
2779
2780 function makeHrefForHelper ($helper_name, $params = array())
2781 {
2782 $ret = '?module=popup&helper=' . $helper_name;
2783 foreach($params as $key=>$value)
2784 $ret .= '&'.urlencode($key).'='.urlencode($value);
2785 return $ret;
2786 }
2787
2788 // Process the given list of records to build data suitable for printNiftySelect()
2789 // (like it was formerly executed by printSelect()). Screen out vendors according
2790 // to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
2791 // selected OPTION is protected from being screened.
2792 function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
2793 {
2794 $ret = array();
2795 $therest = array();
2796 foreach ($recordList as $dict_key => $dict_value)
2797 if (preg_match ('/^(.*)%(GPASS|GSKIP)%/', $dict_value, $m))
2798 $ret[$m[1]][$dict_key] = execGMarker ($dict_value);
2799 else
2800 $therest[$dict_key] = $dict_value;
2801
2802 // Always keep "other" OPTGROUP at the SELECT bottom.
2803 $ret['other'] = $therest;
2804
2805 if ($object_type_id != 0)
2806 {
2807 $screenlist = array();
2808 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
2809 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
2810 $screenlist[] = $regs[1];
2811 }
2812 foreach (array_keys ($ret) as $vendor)
2813 if (in_array ($vendor, $screenlist))
2814 {
2815 $ok_to_screen = TRUE;
2816 if ($existing_value)
2817 foreach (array_keys ($ret[$vendor]) as $recordkey)
2818 if ($recordkey == $existing_value)
2819 {
2820 $ok_to_screen = FALSE;
2821 break;
2822 }
2823 if ($ok_to_screen)
2824 unset ($ret[$vendor]);
2825 }
2826 }
2827 return $ret;
2828 }
2829
2830 function dos2unix ($text)
2831 {
2832 return str_replace ("\r\n", "\n", $text);
2833 }
2834
2835 function unix2dos ($text)
2836 {
2837 return str_replace ("\n", "\r\n", $text);
2838 }
2839
2840 function buildPredicateTable ($parsetree)
2841 {
2842 $ret = array();
2843 foreach ($parsetree as $sentence)
2844 if ($sentence['type'] == 'SYNT_DEFINITION')
2845 $ret[$sentence['term']] = $sentence['definition'];
2846 // Now we have predicate table filled in with the latest definitions of each
2847 // particular predicate met. This isn't as chik, as on-the-fly predicate
2848 // overloading during allow/deny scan, but quite sufficient for this task.
2849 return $ret;
2850 }
2851
2852 // Take a list of records and filter against given RackCode expression. Return
2853 // the original list intact, if there was no filter requested, but return an
2854 // empty list, if there was an error.
2855 function filterCellList ($list_in, $expression = array())
2856 {
2857 if ($expression === NULL)
2858 return array();
2859 if (!count ($expression))
2860 return $list_in;
2861 $list_out = array();
2862 foreach ($list_in as $item_key => $item_value)
2863 if (TRUE === judgeCell ($item_value, $expression))
2864 $list_out[$item_key] = $item_value;
2865 return $list_out;
2866 }
2867
2868 function eval_expression ($expr, $tagchain, $ptable, $silent = FALSE)
2869 {
2870 $self = __FUNCTION__;
2871 switch ($expr['type'])
2872 {
2873 // Return true, if given tag is present on the tag chain.
2874 case 'LEX_TAG':
2875 case 'LEX_AUTOTAG':
2876 foreach ($tagchain as $tagInfo)
2877 if ($expr['load'] == $tagInfo['tag'])
2878 return TRUE;
2879 return FALSE;
2880 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it.
2881 $pname = $expr['load'];
2882 if (!isset ($ptable[$pname]))
2883 {
2884 if (!$silent)
2885 showWarning ("Predicate '${pname}' is referenced before declaration");
2886 return NULL;
2887 }
2888 return $self ($ptable[$pname], $tagchain, $ptable);
2889 case 'LEX_TRUE':
2890 return TRUE;
2891 case 'LEX_FALSE':
2892 return FALSE;
2893 case 'SYNT_NOT_EXPR':
2894 $tmp = $self ($expr['load'], $tagchain, $ptable);
2895 if ($tmp === TRUE)
2896 return FALSE;
2897 elseif ($tmp === FALSE)
2898 return TRUE;
2899 else
2900 return $tmp;
2901 case 'SYNT_AND_EXPR': // binary AND
2902 if (FALSE == $self ($expr['left'], $tagchain, $ptable))
2903 return FALSE; // early failure
2904 return $self ($expr['right'], $tagchain, $ptable);
2905 case 'SYNT_EXPR': // binary OR
2906 if (TRUE == $self ($expr['left'], $tagchain, $ptable))
2907 return TRUE; // early success
2908 return $self ($expr['right'], $tagchain, $ptable);
2909 default:
2910 if (!$silent)
2911 showWarning ("Evaluation error, cannot process expression type '${expr['type']}'");
2912 return NULL;
2913 break;
2914 }
2915 }
2916
2917 // Tell, if the given expression is true for the given entity. Take complete record on input.
2918 function judgeCell ($cell, $expression)
2919 {
2920 global $pTable;
2921 return eval_expression
2922 (
2923 $expression,
2924 array_merge
2925 (
2926 $cell['etags'],
2927 $cell['itags'],
2928 $cell['atags']
2929 ),
2930 $pTable,
2931 TRUE
2932 );
2933 }
2934
2935 function judgeContext ($expression)
2936 {
2937 global $pTable, $expl_tags, $impl_tags, $auto_tags;
2938 return eval_expression
2939 (
2940 $expression,
2941 array_merge
2942 (
2943 $expl_tags,
2944 $impl_tags,
2945 $auto_tags
2946 ),
2947 $pTable,
2948 TRUE
2949 );
2950 }
2951
2952 // Tell, if a constraint from config option permits given record.
2953 // An undefined $cell means current context.
2954 function considerConfiguredConstraint ($cell, $varname)
2955 {
2956 if (!strlen (getConfigVar ($varname)))
2957 return TRUE; // no restriction
2958 global $parseCache;
2959 if (!isset ($parseCache[$varname]))
2960 // getConfigVar() doesn't re-read the value from DB because of its
2961 // own cache, so there is no race condition here between two calls.
2962 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2963 if ($parseCache[$varname]['result'] != 'ACK')
2964 return FALSE; // constraint set, but cannot be used due to compilation error
2965 if (isset ($cell))
2966 return judgeCell ($cell, $parseCache[$varname]['load']);
2967 else
2968 return judgeContext ($parseCache[$varname]['load']);
2969 }
2970
2971 // Tell, if the given arbitrary RackCode text addresses the given record
2972 // (an empty text matches any record).
2973 // An undefined $cell means current context.
2974 function considerGivenConstraint ($cell, $filtertext)
2975 {
2976 if ($filtertext == '')
2977 return TRUE;
2978 $parse = spotPayload ($filtertext, 'SYNT_EXPR');
2979 if ($parse['result'] != 'ACK')
2980 throw new InvalidRequestArgException ('filtertext', $filtertext, 'RackCode parsing error');
2981 if (isset ($cell))
2982 return judgeCell ($cell, $parse['load']);
2983 else
2984 return judgeContext ($parse['load']);
2985 }
2986
2987 // Return list of records in the given realm, which conform to
2988 // the given RackCode expression. If the realm is unknown or text
2989 // doesn't validate as a RackCode expression, return NULL.
2990 // Otherwise (successful scan) return a list of all matched
2991 // records, even if the list is empty (array() !== NULL). If the
2992 // text is an empty string, return all found records in the given
2993 // realm.
2994 function scanRealmByText ($realm, $ftext = '')
2995 {
2996 if (!strlen ($ftext = trim ($ftext)))
2997 $fexpr = array();
2998 else
2999 {
3000 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
3001 if ($fparse['result'] != 'ACK')
3002 return NULL;
3003 $fexpr = $fparse['load'];
3004 }
3005 return filterCellList (listCells ($realm), $fexpr);
3006 }
3007
3008 function getVSTOptions()
3009 {
3010 $ret = array();
3011 foreach (listCells