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