bugfix: IPv4 space exceptions (#591)
[racktables] / wwwroot / inc / functions.php
CommitLineData
b325120a 1<?php
cddbb9fd
DO
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
e673ee24
DO
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
408b9915
DO
32define ('CHAP_OBJTYPE', 1);
33define ('CHAP_PORTTYPE', 2);
408b9915
DO
34// The latter matches both SunOS and Linux-styled formats.
35define ('RE_L2_IFCFG', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){5}$/i');
36define ('RE_L2_CISCO', '/^[0-9a-f]{4}(\.[0-9a-f]{4}){2}$/i');
37define ('RE_L2_HUAWEI', '/^[0-9a-f]{4}(-[0-9a-f]{4}){2}$/i');
38define ('RE_L2_SOLID', '/^[0-9a-f]{12}$/i');
39define ('RE_L2_IPCFG', '/^[0-9a-f]{2}(-[0-9a-f]{2}){5}$/i');
40define ('RE_L2_WWN_COLON', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){7}$/i');
41define ('RE_L2_WWN_HYPHEN', '/^[0-9a-f]{2}(-[0-9a-f]{2}){7}$/i');
42define ('RE_L2_WWN_SOLID', '/^[0-9a-f]{16}$/i');
43define ('RE_IP4_ADDR', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}$#');
44define ('RE_IP4_NET', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$#');
45define ('E_8021Q_NOERROR', 0);
46define ('E_8021Q_VERSION_CONFLICT', 101);
47define ('E_8021Q_PULL_REMOTE_ERROR', 102);
48define ('E_8021Q_PUSH_REMOTE_ERROR', 103);
49define ('E_8021Q_SYNC_DISABLED', 104);
50define ('VLAN_MIN_ID', 1);
51define ('VLAN_MAX_ID', 4094);
52define ('VLAN_DFL_ID', 1);
fa5b2764
AA
53define ('TAB_REMEMBER_TIMEOUT', 300);
54
482c7f35
DO
55// Entity type by page number mapping is 1:1 atm, but may change later.
56$etype_by_pageno = array
57(
58 'ipv4net' => 'ipv4net',
21ee3351 59 'ipv6net' => 'ipv6net',
482c7f35
DO
60 'ipv4rspool' => 'ipv4rspool',
61 'ipv4vs' => 'ipv4vs',
62 'object' => 'object',
63 'rack' => 'rack',
e1add254 64 'location' => 'location',
482c7f35
DO
65 'user' => 'user',
66 'file' => 'file',
cc3d6915 67 'vst' => 'vst',
482c7f35
DO
68);
69
ef4b16fb
DO
70// Rack thumbnail image width summands: "front", "interior" and "rear" elements w/o surrounding border.
71$rtwidth = array
72(
73 0 => 9,
74 1 => 21,
75 2 => 9
76);
77
9b8174d7
AD
78$location_obj_types = array
79(
80 1560,
81 1561,
82 1562
83);
84
8a97919e
DO
85// 802.1Q deploy queue titles
86$dqtitle = array
87(
88 'sync_aging' => 'Normal, aging',
89 'resync_aging' => 'Version conflict, aging',
90 'sync_ready' => 'Normal, ready for sync',
91 'resync_ready' => 'Version conflict, ready for retry',
92 'failed' => 'Failed',
93 'disabled' => 'Sync disabled',
94 'done' => 'Up to date',
95);
96
e820929d
DO
97$wdm_packs = array
98(
99 '1000cwdm80' => array
100 (
101 'title' => '1000Base-CWDM80 (8 channels)',
102 'iif_ids' => array (3, 4),
103 'oif_ids' => array (1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216),
104 ),
105 '1000dwdm80' => array // ITU channels 20~61
106 (
107 'title' => '1000Base-DWDM80 (42 channels)',
108 'iif_ids' => array (3, 4),
109 'oif_ids' => array
110 (
111 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226,
112 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236,
113 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246,
114 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256,
115 1257, 1258
116 ),
117 ),
118 '10000dwdm80' => array // same channels for 10GE
119 (
120 'title' => '10GBase-ZR-DWDM80 (42 channels)',
121 'iif_ids' => array (9, 6, 5, 8, 7),
122 'oif_ids' => array
123 (
124 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268,
125 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278,
126 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288,
127 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298,
128 1299, 1300
129 ),
130 ),
52d31972
DO
131 '10000dwdm40' => array
132 (
133 'title' => '10GBase-ER-DWDM40 (42 channels)',
134 'iif_ids' => array (9, 6, 5, 8, 7),
135 'oif_ids' => array
136 (
137 1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434,
138 1435, 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444,
139 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454,
140 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, 1463, 1464,
141 1465, 1466
142 ),
143 ),
e820929d
DO
144);
145
a2b16202
AA
146$log_messages = array(); // messages waiting for displaying
147
f77323f1
DO
148// This function assures that specified argument was passed
149// and is a number greater than zero.
0cc24e9a 150function assertUIntArg ($argname, $allow_zero = FALSE)
f77323f1
DO
151{
152 if (!isset ($_REQUEST[$argname]))
d986edc2 153 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
f77323f1 154 if (!is_numeric ($_REQUEST[$argname]))
5847d944 155 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a number');
f77323f1 156 if ($_REQUEST[$argname] < 0)
5847d944 157 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is less than zero');
ca92dc40 158 if (!$allow_zero and $_REQUEST[$argname] == 0)
5847d944 159 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is zero');
f77323f1
DO
160}
161
09ec2e59
AA
162function isInteger ($arg, $allow_zero = FALSE)
163{
164 if (! is_numeric ($arg))
165 return FALSE;
166 if (! $allow_zero and ! $arg)
167 return FALSE;
168 return TRUE;
169}
170
86ff26ae
DO
171// make sure the arg is a parsable date
172function assertDateArg ($argname, $ok_if_empty = FALSE)
173{
2dddfaca 174 assertStringArg ($argname, $ok_if_empty);
86ff26ae 175 // different versions of PHP return false/-1
2dddfaca 176 if ($_REQUEST[$argname] != '' and strtotime ($_REQUEST[$argname]) <= 0)
86ff26ae
DO
177 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a parsable date');
178}
179
180
f77323f1
DO
181// This function assures that specified argument was passed
182// and is a non-empty string.
0cc24e9a 183function assertStringArg ($argname, $ok_if_empty = FALSE)
f77323f1
DO
184{
185 if (!isset ($_REQUEST[$argname]))
d986edc2 186 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
f77323f1 187 if (!is_string ($_REQUEST[$argname]))
5847d944 188 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
f77323f1 189 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
5847d944 190 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
f77323f1
DO
191}
192
0cc24e9a 193function assertBoolArg ($argname, $ok_if_empty = FALSE)
f77323f1
DO
194{
195 if (!isset ($_REQUEST[$argname]))
d986edc2 196 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
f77323f1 197 if (!is_string ($_REQUEST[$argname]) or $_REQUEST[$argname] != 'on')
5847d944 198 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
f77323f1 199 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
5847d944 200 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
f77323f1
DO
201}
202
4318ced5
AA
203// function returns binary IP address, or throws an exception
204function assertIPArg ($argname)
21ee3351 205{
4318ced5
AA
206 assertStringArg ($argname, FALSE);
207 try
21ee3351 208 {
4318ced5 209 return ip_parse ($_REQUEST[$argname]);
21ee3351 210 }
4318ced5 211 catch (InvalidArgException $e)
21ee3351 212 {
4318ced5 213 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], $e->getMessage());
21ee3351 214 }
21ee3351
AA
215}
216
4318ced5
AA
217// function returns binary IPv4 address, or throws an exception
218function assertIPv4Arg ($argname)
f77323f1 219{
4318ced5
AA
220 try
221 {
222 return ip4_parse ($_REQUEST[$argname]);
223 }
224 catch (InvalidArgException $e)
225 {
226 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], $e->getMessage());
227 }
f77323f1
DO
228}
229
4318ced5
AA
230// function returns binary IPv6 address, or throws an exception
231function assertIPv6Arg ($argname)
21ee3351 232{
4318ced5
AA
233 try
234 {
235 return ip6_parse ($_REQUEST[$argname]);
236 }
237 catch (InvalidArgException $e)
238 {
239 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], $e->getMessage());
240 }
21ee3351
AA
241}
242
e0d188ef
DO
243function assertPCREArg ($argname)
244{
245 assertStringArg ($argname, TRUE); // empty pattern is Ok
664ec705 246 if (FALSE === preg_match ($_REQUEST[$argname], 'test'))
e0d188ef
DO
247 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'PCRE validation failed');
248}
249
09ec2e59
AA
250function isPCRE ($arg)
251{
35906898 252 if (! isset ($arg) or FALSE === @preg_match ($arg, 'test'))
09ec2e59
AA
253 return FALSE;
254 return TRUE;
255}
256
cde28cf0
DO
257function genericAssertion ($argname, $argtype)
258{
04b1804f 259 global $sic;
cde28cf0
DO
260 switch ($argtype)
261 {
262 case 'string':
263 assertStringArg ($argname);
264 break;
4eba4f82
DO
265 case 'string0':
266 assertStringArg ($argname, TRUE);
267 break;
cde28cf0
DO
268 case 'uint':
269 assertUIntArg ($argname);
270 break;
e268ff45
DO
271 case 'uint-uint':
272 assertStringArg ($argname);
273 if (1 != preg_match ('/^[1-9][0-9]*-[1-9][0-9]*$/', $_REQUEST[$argname]))
274 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], 'illegal format');
275 break;
cde28cf0
DO
276 case 'uint0':
277 assertUIntArg ($argname, TRUE);
278 break;
4318ced5
AA
279 case 'inet':
280 assertIPArg ($argname);
281 break;
cde28cf0
DO
282 case 'inet4':
283 assertIPv4Arg ($argname);
284 break;
9b6e7bd1
DO
285 case 'inet6':
286 assertIPv6Arg ($argname);
287 break;
288 case 'l2address':
289 assertStringArg ($argname);
290 case 'l2address0':
291 assertStringArg ($argname, TRUE);
292 try
293 {
294 l2addressForDatabase ($sic[$argname]);
295 }
296 catch (InvalidArgException $e)
297 {
298 throw new InvalidRequestArgException ($argname, $sic[$argname], 'malformed MAC/WWN address');
299 }
300 break;
04b1804f
DO
301 case 'tag':
302 assertStringArg ($argname);
303 if (!validTagName ($sic[$argname]))
304 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Invalid tag name');
305 break;
9b6e7bd1
DO
306 case 'pcre':
307 assertPCREArg ($argname);
308 break;
1d84140d
DO
309 case 'json':
310 assertStringArg ($argname);
405ac32c 311 if (NULL === json_decode ($sic[$argname], TRUE))
1d84140d
DO
312 throw new InvalidRequestArgException ($argname, '(omitted)', 'Invalid JSON code received from client');
313 break;
fab31c45
DO
314 case 'array':
315 if (! array_key_exists ($argname, $_REQUEST))
316 throw new InvalidRequestArgException ($argname, '(missing argument)');
317 if (! is_array ($_REQUEST[$argname]))
318 throw new InvalidRequestArgException ($argname, '(omitted)', 'argument is not an array');
319 break;
7299b9f7
DO
320 case 'enum/attr_type':
321 assertStringArg ($argname);
86ff26ae 322 if (!in_array ($sic[$argname], array ('uint', 'float', 'string', 'dict','date')))
7299b9f7
DO
323 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
324 break;
10005279
DO
325 case 'enum/vlan_type':
326 assertStringArg ($argname);
327 // "Alien" type is not valid until the logic is fixed to implement it in full.
328 if (!in_array ($sic[$argname], array ('ondemand', 'compulsory')))
329 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
330 break;
1d768108
DO
331 case 'enum/wdmstd':
332 assertStringArg ($argname);
e820929d
DO
333 global $wdm_packs;
334 if (! array_key_exists ($sic[$argname], $wdm_packs))
1d768108
DO
335 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
336 break;
a9ec91cf
DO
337 case 'enum/ipproto':
338 assertStringArg ($argname);
ed38c73c
AA
339 global $vs_proto;
340 if (!array_key_exists ($sic[$argname], $vs_proto))
a9ec91cf
DO
341 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
342 break;
4318ced5 343 case 'enum/alloc_type':
96597d19
DO
344 assertStringArg ($argname);
345 if (!in_array ($sic[$argname], array ('regular', 'shared', 'virtual', 'router')))
346 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
347 break;
77c80ac3
DO
348 case 'enum/dqcode':
349 assertStringArg ($argname);
350 global $dqtitle;
351 if (! array_key_exists ($sic[$argname], $dqtitle))
352 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
353 break;
1d768108
DO
354 case 'iif':
355 if (!array_key_exists ($sic[$argname], getPortIIFOptions()))
356 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
357 break;
ccc0acff
DO
358 case 'vlan':
359 case 'vlan1':
360 genericAssertion ($argname, 'uint');
361 if ($argtype == 'vlan' and $sic[$argname] == VLAN_DFL_ID)
362 throw new InvalidRequestArgException ($argname, $sic[$argname], 'default VLAN cannot be changed');
363 if ($sic[$argname] > VLAN_MAX_ID or $sic[$argname] < VLAN_MIN_ID)
364 throw new InvalidRequestArgException ($argname, $sic[$argname], 'out of valid range');
365 break;
77c80ac3
DO
366 case 'rackcode/expr':
367 genericAssertion ($argname, 'string0');
368 if ($sic[$argname] == '')
369 return;
370 $parse = spotPayload ($sic[$argname], 'SYNT_EXPR');
371 if ($parse['result'] != 'ACK')
372 throw new InvalidRequestArgException ($argname, $sic[$argname], 'RackCode parsing error');
373 break;
cde28cf0
DO
374 default:
375 throw new InvalidArgException ('argtype', $argtype); // comes not from user's input
376 }
377}
378
4318ced5
AA
379// return HTML form checkbox value (TRUE of FALSE) by name of its input control
380function isCheckSet ($input_name, $mode = 'bool')
381{
382 $value = isset ($_REQUEST[$input_name]) && $_REQUEST[$input_name] == 'on';
383 switch ($mode)
384 {
385 case 'bool' : return $value;
386 case 'yesno': return $value ? 'yes' : 'no';
387 default: throw new InvalidArgException ('mode', $mode);
388 }
389}
390
cde28cf0
DO
391// Validate and return "bypass" value for the current context, if one is
392// defined for it, or NULL otherwise.
393function getBypassValue()
394{
395 global $page, $pageno, $sic;
396 if (!array_key_exists ('bypass', $page[$pageno]))
397 return NULL;
398 if (!array_key_exists ('bypass_type', $page[$pageno]))
399 throw new RackTablesError ("Internal structure error at node '${pageno}' (bypass_type is not set)", RackTablesError::INTERNAL);
400 genericAssertion ($page[$pageno]['bypass'], $page[$pageno]['bypass_type']);
401 return $sic[$page[$pageno]['bypass']];
402}
403
e673ee24
DO
404// Objects of some types should be explicitly shown as
405// anonymous (labelless). This function is a single place where the
406// decision about displayed name is made.
cfa8f3cf 407function setDisplayedName (&$cell)
e673ee24 408{
71066ef1
AA
409 if ($cell['realm'] == 'object')
410 {
411 if ($cell['name'] != '')
412 $cell['dname'] = $cell['name'];
413 else
414 $cell['dname'] = '[' . decodeObjectType ($cell['objtype_id'], 'o') . ']';
415 }
416 elseif ($cell['realm'] == 'ipv4vs')
417 {
418 if ($cell['proto'] == 'MARK')
419 $cell['dname'] = "fwmark: " . implode ('', unpack('N', substr ($cell['vip_bin'], 0, 4)));
420 else
421 $cell['dname'] = $cell['vip'] . ':' . $cell['vport'] . '/' . $cell['proto'];
422 }
e673ee24
DO
423}
424
425// This function finds height of solid rectangle of atoms, which are all
426// assigned to the same object. Rectangle base is defined by specified
427// template.
428function rectHeight ($rackData, $startRow, $template_idx)
429{
430 $height = 0;
431 // The first met object_id is used to match all the folowing IDs.
432 $object_id = 0;
433 global $template;
434 do
435 {
436 for ($locidx = 0; $locidx < 3; $locidx++)
437 {
438 // At least one value in template is TRUE, but the following block
439 // can meet 'skipped' atoms. Let's ensure we have something after processing
440 // the first row.
441 if ($template[$template_idx][$locidx])
442 {
443 if (isset ($rackData[$startRow - $height][$locidx]['skipped']))
444 break 2;
93e02204
DO
445 if (isset ($rackData[$startRow - $height][$locidx]['rowspan']))
446 break 2;
447 if (isset ($rackData[$startRow - $height][$locidx]['colspan']))
448 break 2;
e673ee24
DO
449 if ($rackData[$startRow - $height][$locidx]['state'] != 'T')
450 break 2;
451 if ($object_id == 0)
452 $object_id = $rackData[$startRow - $height][$locidx]['object_id'];
453 if ($object_id != $rackData[$startRow - $height][$locidx]['object_id'])
454 break 2;
455 }
456 }
457 // If the first row can't offer anything, bail out.
458 if ($height == 0 and $object_id == 0)
459 break;
460 $height++;
461 }
462 while ($startRow - $height > 0);
93e02204
DO
463# echo "for startRow==${startRow} and template==(" . ($template[$template_idx][0] ? 'T' : 'F');
464# echo ', ' . ($template[$template_idx][1] ? 'T' : 'F') . ', ' . ($template[$template_idx][2] ? 'T' : 'F');
465# echo ") height==${height}<br>\n";
e673ee24
DO
466 return $height;
467}
468
469// This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan
470// attributes.
471function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
472{
473 global $template, $templateWidth;
474 $colspan = 0;
475 for ($height = 0; $height < $maxheight; $height++)
476 {
477 for ($locidx = 0; $locidx < 3; $locidx++)
478 {
479 if ($template[$template_idx][$locidx])
480 {
481 // Add colspan/rowspan to the first row met and mark the following ones to skip.
93e02204
DO
482 // Explicitly show even single-cell spanned atoms, because rectHeight()
483 // is expeciting this data for correct calculation.
e673ee24
DO
484 if ($colspan != 0)
485 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
486 else
487 {
488 $colspan = $templateWidth[$template_idx];
93e02204 489 if ($colspan >= 1)
e673ee24 490 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
93e02204 491 if ($maxheight >= 1)
e673ee24
DO
492 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
493 }
494 }
495 }
496 }
497 return;
498}
499
93e02204
DO
500// This function sets rowspan/solspan/skipped atom attributes for renderRack()
501// What we actually have to do is to find _all_ possible rectangles for each unit
502// and then select the widest of those with the maximal square.
04d16213 503function markAllSpans (&$rackData)
e673ee24 504{
e673ee24 505 for ($i = $rackData['height']; $i > 0; $i--)
93e02204
DO
506 while (markBestSpan ($rackData, $i));
507}
508
509// Calculate height of 6 possible span templates (array is presorted by width
510// descending) and mark the best (if any).
511function markBestSpan (&$rackData, $i)
512{
513 global $template, $templateWidth;
514 for ($j = 0; $j < 6; $j++)
e673ee24 515 {
93e02204
DO
516 $height[$j] = rectHeight ($rackData, $i, $j);
517 $square[$j] = $height[$j] * $templateWidth[$j];
518 }
519 // find the widest rectangle of those with maximal height
520 $maxsquare = max ($square);
521 if (!$maxsquare)
522 return FALSE;
523 $best_template_index = 0;
524 for ($j = 0; $j < 6; $j++)
525 if ($square[$j] == $maxsquare)
e673ee24 526 {
93e02204
DO
527 $best_template_index = $j;
528 $bestheight = $height[$j];
529 break;
e673ee24 530 }
93e02204
DO
531 // distribute span marks
532 markSpan ($rackData, $i, $bestheight, $best_template_index);
533 return TRUE;
e673ee24
DO
534}
535
e673ee24
DO
536// We can mount 'F' atoms and unmount our own 'T' atoms.
537function applyObjectMountMask (&$rackData, $object_id)
538{
539 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
540 for ($locidx = 0; $locidx < 3; $locidx++)
541 switch ($rackData[$unit_no][$locidx]['state'])
542 {
543 case 'F':
544 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
545 break;
546 case 'T':
547 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id);
548 break;
549 default:
550 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
551 }
552}
553
554// Design change means transition between 'F' and 'A' and back.
555function applyRackDesignMask (&$rackData)
556{
557 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
558 for ($locidx = 0; $locidx < 3; $locidx++)
559 switch ($rackData[$unit_no][$locidx]['state'])
560 {
561 case 'F':
562 case 'A':
563 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
564 break;
565 default:
566 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
567 }
568}
569
570// The same for 'F' and 'U'.
571function applyRackProblemMask (&$rackData)
572{
573 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
574 for ($locidx = 0; $locidx < 3; $locidx++)
575 switch ($rackData[$unit_no][$locidx]['state'])
576 {
577 case 'F':
578 case 'U':
579 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
580 break;
581 default:
582 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
583 }
584}
585
e673ee24
DO
586// This function highlights specified object (and removes previous highlight).
587function highlightObject (&$rackData, $object_id)
588{
453cce7e
AD
589 // Also highlight parent objects
590 $parents = getEntityRelatives ('parents', 'object', $object_id);
591 $parent_ids = array();
592 foreach ($parents as $parent)
593 $parent_ids[] = $parent['entity_id'];
594
e673ee24
DO
595 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
596 for ($locidx = 0; $locidx < 3; $locidx++)
597 if
598 (
599 $rackData[$unit_no][$locidx]['state'] == 'T' and
453cce7e 600 ($rackData[$unit_no][$locidx]['object_id'] == $object_id or in_array($rackData[$unit_no][$locidx]['object_id'], $parent_ids))
e673ee24
DO
601 )
602 $rackData[$unit_no][$locidx]['hl'] = 'h';
603 else
604 unset ($rackData[$unit_no][$locidx]['hl']);
605}
606
607// This function marks atoms to selected or not depending on their current state.
608function markupAtomGrid (&$data, $checked_state)
609{
610 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
611 for ($locidx = 0; $locidx < 3; $locidx++)
612 {
613 if (!($data[$unit_no][$locidx]['enabled'] === TRUE))
614 continue;
615 if ($data[$unit_no][$locidx]['state'] == $checked_state)
616 $data[$unit_no][$locidx]['checked'] = ' checked';
617 else
618 $data[$unit_no][$locidx]['checked'] = '';
619 }
620}
621
622// This function is almost a clone of processGridForm(), but doesn't save anything to database
623// Return value is the changed rack data.
624// Here we assume that correct filter has already been applied, so we just
625// set or unset checkbox inputs w/o changing atom state.
626function mergeGridFormToRack (&$rackData)
627{
628 $rack_id = $rackData['id'];
629 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
630 for ($locidx = 0; $locidx < 3; $locidx++)
631 {
632 if ($rackData[$unit_no][$locidx]['enabled'] != TRUE)
633 continue;
634 $inputname = "atom_${rack_id}_${unit_no}_${locidx}";
635 if (isset ($_REQUEST[$inputname]) and $_REQUEST[$inputname] == 'on')
636 $rackData[$unit_no][$locidx]['checked'] = ' checked';
637 else
638 $rackData[$unit_no][$locidx]['checked'] = '';
639 }
640}
641
4318ced5
AA
642// wrapper around ip4_mask and ip6_mask
643// netmask conversion from length to binary string
644// v4/v6 mode is toggled by $is_ipv6 parameter
645// Throws exception if $prefix_len is invalid
646function ip_mask ($prefix_len, $is_ipv6)
647{
648 if ($is_ipv6)
649 return ip6_mask ($prefix_len);
650 else
651 return ip4_mask ($prefix_len);
652}
653
654// netmask conversion from length to binary string
655// Throws exception if $prefix_len is invalid
656function ip4_mask ($prefix_len)
657{
658 static $mask = array
659 (
660 "\x00\x00\x00\x00", // 0
661 "\x80\x00\x00\x00", // 1
662 "\xC0\x00\x00\x00", // 2
663 "\xE0\x00\x00\x00", // 3
664 "\xF0\x00\x00\x00", // 4
665 "\xF8\x00\x00\x00", // 5
666 "\xFC\x00\x00\x00", // 6
667 "\xFE\x00\x00\x00", // 7
668 "\xFF\x00\x00\x00", // 8
669 "\xFF\x80\x00\x00", // 9
670 "\xFF\xC0\x00\x00", // 10
671 "\xFF\xE0\x00\x00", // 11
672 "\xFF\xF0\x00\x00", // 12
673 "\xFF\xF8\x00\x00", // 13
674 "\xFF\xFC\x00\x00", // 14
675 "\xFF\xFE\x00\x00", // 15
676 "\xFF\xFF\x00\x00", // 16
677 "\xFF\xFF\x80\x00", // 17
678 "\xFF\xFF\xC0\x00", // 18
679 "\xFF\xFF\xE0\x00", // 19
680 "\xFF\xFF\xF0\x00", // 20
681 "\xFF\xFF\xF8\x00", // 21
682 "\xFF\xFF\xFC\x00", // 22
683 "\xFF\xFF\xFE\x00", // 23
684 "\xFF\xFF\xFF\x00", // 24
685 "\xFF\xFF\xFF\x80", // 25
686 "\xFF\xFF\xFF\xC0", // 26
687 "\xFF\xFF\xFF\xE0", // 27
688 "\xFF\xFF\xFF\xF0", // 28
689 "\xFF\xFF\xFF\xF8", // 29
690 "\xFF\xFF\xFF\xFC", // 30
691 "\xFF\xFF\xFF\xFE", // 31
692 "\xFF\xFF\xFF\xFF", // 32
bb0a44e9 693 );
4318ced5
AA
694
695 if ($prefix_len >= 0 and $prefix_len <= 32)
696 return $mask[$prefix_len];
697 else
698 throw new InvalidArgException ('prefix_len', $prefix_len);
699}
700
701// netmask conversion from length to binary string
702// Throws exception if $prefix_len is invalid
703function ip6_mask ($prefix_len)
704{
705 static $mask = array
706 (
707 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 0
708 "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 1
709 "\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 2
710 "\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 3
711 "\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 4
712 "\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 5
713 "\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 6
714 "\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 7
715 "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 8
716 "\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 9
717 "\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 10
718 "\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 11
719 "\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 12
720 "\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 13
721 "\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 14
722 "\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 15
723 "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 16
724 "\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 17
725 "\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 18
726 "\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 19
727 "\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 20
728 "\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 21
729 "\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 22
730 "\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 23
731 "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 24
732 "\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 25
733 "\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 26
734 "\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 27
735 "\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 28
736 "\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 29
737 "\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 30
738 "\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 31
739 "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 32
740 "\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 33
741 "\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 34
742 "\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 35
743 "\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 36
744 "\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 37
745 "\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 38
746 "\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 39
747 "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 40
748 "\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 41
749 "\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 42
750 "\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 43
751 "\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 44
752 "\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 45
753 "\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 46
754 "\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 47
755 "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 48
756 "\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 49
757 "\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 50
758 "\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 51
759 "\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 52
760 "\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 53
761 "\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 54
762 "\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 55
763 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 56
764 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00", // 57
765 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00", // 58
766 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00", // 59
767 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00", // 60
768 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00", // 61
769 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00", // 62
770 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00", // 63
771 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00", // 64
772 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00", // 65
773 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00", // 66
774 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00", // 67
775 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00", // 68
776 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00", // 69
777 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00", // 70
778 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00", // 71
779 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00", // 72
780 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00", // 73
781 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00", // 74
782 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00", // 75
783 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00", // 76
784 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00", // 77
785 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00", // 78
786 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00", // 79
787 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00", // 80
788 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00", // 81
789 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00", // 82
790 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00", // 83
791 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00", // 84
792 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00", // 85
793 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00", // 86
794 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00", // 87
795 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00", // 88
796 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00", // 89
797 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00", // 90
798 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00", // 91
799 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00", // 92
800 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00", // 93
801 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00", // 94
802 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00", // 95
803 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00", // 96
804 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00", // 97
805 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00", // 98
806 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00", // 99
807 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00", // 100
808 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00", // 101
809 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00", // 102
810 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00", // 103
811 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00", // 104
812 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00", // 105
813 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00", // 106
814 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00", // 107
815 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00", // 108
816 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00", // 109
817 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00", // 110
818 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00", // 111
819 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", // 112
820 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00", // 113
821 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00", // 114
822 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00", // 115
823 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00", // 116
824 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00", // 117
825 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00", // 118
826 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00", // 119
827 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", // 120
828 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80", // 121
829 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0", // 122
830 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0", // 123
831 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0", // 124
832 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8", // 125
833 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC", // 126
834 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE", // 127
835 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", // 128
bb0a44e9 836 );
4318ced5
AA
837
838 if ($prefix_len >= 0 and $prefix_len <= 128)
839 return $mask[$prefix_len];
840 else
841 throw new InvalidArgException ('prefix_len', $prefix_len);
e673ee24
DO
842}
843
e673ee24
DO
844// This function looks up 'has_problems' flag for 'T' atoms
845// and modifies 'hl' key. May be, this should be better done
61a1d996 846// in amplifyCell(). We don't honour 'skipped' key, because
e673ee24
DO
847// the function is also used for thumb creation.
848function markupObjectProblems (&$rackData)
849{
850 for ($i = $rackData['height']; $i > 0; $i--)
851 for ($locidx = 0; $locidx < 3; $locidx++)
852 if ($rackData[$i][$locidx]['state'] == 'T')
853 {
6297d584 854 $object = spotEntity ('object', $rackData[$i][$locidx]['object_id']);
e673ee24
DO
855 if ($object['has_problems'] == 'yes')
856 {
857 // Object can be already highlighted.
858 if (isset ($rackData[$i][$locidx]['hl']))
859 $rackData[$i][$locidx]['hl'] = $rackData[$i][$locidx]['hl'] . 'w';
860 else
861 $rackData[$i][$locidx]['hl'] = 'w';
862 }
863 }
864}
865
d516d719 866// Return a uniformly (010203040506 or 0102030405060708) formatted address, if it is present
9b6e7bd1 867// in the provided string, an empty string for an empty string or raise an exception.
e673ee24
DO
868function l2addressForDatabase ($string)
869{
e673ee24 870 $string = strtoupper ($string);
05771508
DO
871 switch (TRUE)
872 {
d516d719 873 case ($string == '' or preg_match (RE_L2_SOLID, $string) or preg_match (RE_L2_WWN_SOLID, $string)):
05771508 874 return $string;
d516d719
DO
875 case (preg_match (RE_L2_IFCFG, $string) or preg_match (RE_L2_WWN_COLON, $string)):
876 // reformat output of SunOS ifconfig
877 $ret = '';
878 foreach (explode (':', $string) as $byte)
879 $ret .= (strlen ($byte) == 1 ? '0' : '') . $byte;
880 return $ret;
05771508 881 case (preg_match (RE_L2_CISCO, $string)):
d516d719 882 return str_replace ('.', '', $string);
c5553818
DO
883 case (preg_match (RE_L2_HUAWEI, $string)):
884 return str_replace ('-', '', $string);
d516d719
DO
885 case (preg_match (RE_L2_IPCFG, $string) or preg_match (RE_L2_WWN_HYPHEN, $string)):
886 return str_replace ('-', '', $string);
05771508 887 default:
9b6e7bd1 888 throw new InvalidArgException ('$string', $string, 'malformed MAC/WWN address');
05771508 889 }
e673ee24
DO
890}
891
892function l2addressFromDatabase ($string)
893{
894 switch (strlen ($string))
895 {
896 case 12: // Ethernet
d516d719 897 case 16: // FireWire/Fibre Channel
e673ee24
DO
898 $ret = implode (':', str_split ($string, 2));
899 break;
900 default:
901 $ret = $string;
902 break;
903 }
904 return $ret;
905}
906
907// The following 2 functions return previous and next rack IDs for
908// a given rack ID. The order of racks is the same as in renderRackspace()
909// or renderRow().
04d16213 910function getPrevIDforRack ($row_id, $rack_id)
e673ee24 911{
a8efc03e 912 $rackList = listCells ('rack', $row_id);
e673ee24
DO
913 doubleLink ($rackList);
914 if (isset ($rackList[$rack_id]['prev_key']))
915 return $rackList[$rack_id]['prev_key'];
916 return NULL;
917}
918
04d16213 919function getNextIDforRack ($row_id, $rack_id)
e673ee24 920{
a8efc03e 921 $rackList = listCells ('rack', $row_id);
e673ee24
DO
922 doubleLink ($rackList);
923 if (isset ($rackList[$rack_id]['next_key']))
924 return $rackList[$rack_id]['next_key'];
925 return NULL;
926}
927
928// This function finds previous and next array keys for each array key and
929// modifies its argument accordingly.
930function doubleLink (&$array)
931{
932 $prev_key = NULL;
933 foreach (array_keys ($array) as $key)
934 {
935 if ($prev_key)
936 {
937 $array[$key]['prev_key'] = $prev_key;
938 $array[$prev_key]['next_key'] = $key;
939 }
940 $prev_key = $key;
941 }
942}
943
e673ee24
DO
944function sortTokenize ($a, $b)
945{
946 $aold='';
947 while ($a != $aold)
948 {
949 $aold=$a;
84986395
DO
950 $a = preg_replace('/[^a-zA-Z0-9]/',' ',$a);
951 $a = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$a);
952 $a = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$a);
e673ee24
DO
953 }
954
955 $bold='';
956 while ($b != $bold)
957 {
958 $bold=$b;
84986395
DO
959 $b = preg_replace('/[^a-zA-Z0-9]/',' ',$b);
960 $b = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$b);
961 $b = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$b);
e673ee24
DO
962 }
963
964
965
966 $ar = explode(' ', $a);
967 $br = explode(' ', $b);
968 for ($i=0; $i<count($ar) && $i<count($br); $i++)
969 {
970 $ret = 0;
971 if (is_numeric($ar[$i]) and is_numeric($br[$i]))
972 $ret = ($ar[$i]==$br[$i])?0:($ar[$i]<$br[$i]?-1:1);
973 else
974 $ret = strcasecmp($ar[$i], $br[$i]);
975 if ($ret != 0)
976 return $ret;
977 }
978 if ($i<count($ar))
979 return 1;
980 if ($i<count($br))
981 return -1;
982 return 0;
983}
984
c31cd72c 985// This function returns an array of single element of object's FQDN attribute,
f321b50a
DO
986// if FQDN is set. The next choice is object's common name, if it looks like a
987// hostname. Otherwise an array of all 'regular' IP addresses of the
c31cd72c 988// object is returned (which may appear 0 and more elements long).
f321b50a 989function findAllEndpoints ($object_id, $fallback = '')
c31cd72c 990{
7fa7047a
DO
991 foreach (getAttrValues ($object_id) as $record)
992 if ($record['id'] == 3 && strlen ($record['value'])) // FQDN
c31cd72c 993 return array ($record['value']);
c31cd72c 994 $regular = array();
4318ced5 995 foreach (getObjectIPv4AllocationList ($object_id) as $ip_bin => $alloc)
85970da2 996 if ($alloc['type'] == 'regular')
4318ced5
AA
997 $regular[] = ip4_format ($ip_bin);
998 // FIXME: add IPv6 allocations to this list
59a83bd8 999 if (!count ($regular) && strlen ($fallback))
f321b50a 1000 return array ($fallback);
c31cd72c
DO
1001 return $regular;
1002}
1003
83ba6670
DO
1004// Some records in the dictionary may be written as plain text or as Wiki
1005// link in the following syntax:
1006// 1. word
1007// 2. [[word URL]] // FIXME: this isn't working
1008// 3. [[word word word | URL]]
8ad59489
AA
1009// This function parses the line in $record['value'] and modifies $record:
1010// $record['o_value'] is set to be the first part of link (word word word)
1011// $record['a_value'] is the same, but with %GPASS and %GSKIP macros applied
1012// $record['href'] is set to URL if it is specified in the input value
1013function parseWikiLink (&$record)
1014{
1015 if (! preg_match ('/^\[\[(.+)\]\]$/', $record['value'], $matches))
1016 $record['o_value'] = $record['value'];
1017 else
24cbe8af 1018 {
8ad59489
AA
1019 $s = explode ('|', $matches[1]);
1020 if (isset ($s[1]))
1021 $record['href'] = trim ($s[1]);
1022 $record['o_value'] = trim ($s[0]);
24cbe8af 1023 }
8ad59489 1024 $record['a_value'] = execGMarker ($record['o_value']);
83ba6670
DO
1025}
1026
c9edf725
DO
1027// FIXME: should this be saved as "P-data"?
1028function execGMarker ($line)
1029{
84986395 1030 return preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $line));
c9edf725
DO
1031}
1032
177b1e9b
DO
1033// rackspace usage for a single rack
1034// (T + W + U) / (height * 3 - A)
04d16213 1035function getRSUforRack ($data)
177b1e9b 1036{
6ffba290 1037 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
9e60f7df
DO
1038 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1039 for ($locidx = 0; $locidx < 3; $locidx++)
1040 $counter[$data[$unit_no][$locidx]['state']]++;
dfa3c075 1041 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
177b1e9b
DO
1042}
1043
11df133a 1044// Same for row.
9b8174d7 1045function getRSUforRow ($rowData)
11df133a 1046{
bb26a59e
DO
1047 if (!count ($rowData))
1048 return 0;
11df133a 1049 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
f81a2012 1050 $total_height = 0;
dfa3c075
DO
1051 foreach (array_keys ($rowData) as $rack_id)
1052 {
61a1d996
DO
1053 $data = spotEntity ('rack', $rack_id);
1054 amplifyCell ($data);
dfa3c075 1055 $total_height += $data['height'];
11df133a
DO
1056 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1057 for ($locidx = 0; $locidx < 3; $locidx++)
1058 $counter[$data[$unit_no][$locidx]['state']]++;
dfa3c075
DO
1059 }
1060 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
11df133a
DO
1061}
1062
4d7c0c70
AD
1063// Find any URL in a string and replace it with a clickable link
1064// Adopted from UrlLinker: https://bitbucket.org/kwi/urllinker/src
e6e7d8b3
DO
1065function string_insert_hrefs ($s)
1066{
1067 if (getConfigVar ('DETECT_URLS') != 'yes')
1068 return $s;
e6e7d8b3 1069
4d7c0c70
AD
1070 $rexProtocol = '(https?://)?';
1071 $rexDomain = '(?:[-a-zA-Z0-9]{1,63}\.)+[a-zA-Z][-a-zA-Z0-9]{1,62}';
1072 $rexIp = '(?:[1-9][0-9]{0,2}\.|0\.){3}(?:[1-9][0-9]{0,2}|0)'; // doesn't support IPv6 addresses
1073 $rexPort = '(:[0-9]{1,5})?';
1074 $rexPath = '(/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?';
1075 $rexQuery = '(\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';
1076 $rexFragment = '(#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';
1077 $rexUsername = '[^]\\\\\x00-\x20\"(),:-<>[\x7f-\xff]{1,64}';
1078 $rexPassword = $rexUsername; // allow the same characters as in the username
1079 $rexUrl = "$rexProtocol(?:($rexUsername)(:$rexPassword)?@)?($rexDomain|$rexIp)($rexPort$rexPath$rexQuery$rexFragment)";
1080 $rexUrlLinker = "{\\b$rexUrl(?=[?.!,;:\"]?(\s|$))}";
1081
1082 $html = '';
1083 $position = 0;
1084 while (preg_match($rexUrlLinker, $s, $match, PREG_OFFSET_CAPTURE, $position))
1085 {
1086 list($url, $urlPosition) = $match[0];
1087
1088 // Add the text leading up to the URL.
1089 $html .= substr($s, $position, $urlPosition - $position);
1090
1091 $protocol = $match[1][0];
1092 $username = $match[2][0];
1093 $password = $match[3][0];
1094 $domain = $match[4][0];
1095 $afterDomain = $match[5][0]; // everything following the domain
1096 $port = $match[6][0];
1097 $path = $match[7][0];
1098
1099 // Do not permit implicit protocol if a password is specified, as
1100 // this causes too many errors (e.g. "my email:foo@example.org").
1101 if (!$protocol && $password)
1102 {
1103 $html .= htmlspecialchars($username);
1104
1105 // Continue text parsing at the ':' following the "username".
1106 $position = $urlPosition + strlen($username);
1107 continue;
1108 }
1109
1110 if (!$protocol && $username && !$password && !$afterDomain)
1111 {
1112 // Looks like an email address.
1113 $emailUrl = TRUE;
1114 $completeUrl = "mailto:$url";
1115 $linkText = $url;
1116 }
1117 else
1118 {
1119 // Prepend http:// if no protocol specified
1120 $completeUrl = $protocol ? $url : "http://$url";
1121 $linkText = "$protocol$domain$port$path";
1122 }
1123
1124 $linkHtml = '<a href="' . htmlspecialchars($completeUrl) . '">'
1125 . htmlspecialchars($linkText)
1126 . '</a>';
1127
1128 // It's not an e-mail address, provide an additional link with a new window/tab as the target
1129 if (!isset($emailUrl))
1130 $linkHtml .= ' [<a href="' . htmlspecialchars($completeUrl) . '" target="_blank">^</a>]';
1131 unset($emailUrl);
1132
1133 // Add the hyperlink.
1134 $html .= $linkHtml;
1135
1136 // Continue text parsing from after the URL.
1137 $position = $urlPosition + strlen($url);
1138 }
1139
1140 // Add the remainder of the text.
1141 $html .= substr($s, $position);
1142 return $html;
e6e7d8b3
DO
1143}
1144
118e4c38
DO
1145// Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
1146// for the requested object_type_id.
1147function getAutoPorts ($type_id)
1148{
1149 $ret = array();
1150 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
1151 foreach ($typemap as $equation)
1152 {
1153 $tmp = explode ('=', $equation);
1154 if (count ($tmp) != 2)
1155 continue;
1156 $objtype_id = $tmp[0];
1157 if ($objtype_id != $type_id)
1158 continue;
1159 $portlist = $tmp[1];
1160 foreach (explode ('+', $portlist) as $product)
1161 {
1162 $tmp = explode ('*', $product);
e24e4549 1163 if (count ($tmp) > 4 || count ($tmp) < 3)
118e4c38 1164 continue;
e24e4549 1165 # format: <number of ports>*<port_type_id>[*<sprintf_name>*<startnumber>]
118e4c38
DO
1166 $nports = $tmp[0];
1167 $port_type = $tmp[1];
1168 $format = $tmp[2];
e24e4549 1169 $startnum = isset ($tmp[3]) ? $tmp[3] : 0;
118e4c38 1170 for ($i = 0; $i < $nports; $i++)
e24e4549 1171 $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i + $startnum));
118e4c38
DO
1172 }
1173 }
1174 return $ret;
1175}
1176
86256b96
DO
1177// Use pre-served trace to traverse the tree, then place given node where it belongs.
1178function pokeNode (&$tree, $trace, $key, $value, $threshold = 0)
1179{
1180 // This function needs the trace to be followed FIFO-way. The fastest
1181 // way to do so is to use array_push() for putting values into the
1182 // list and array_shift() for getting them out. This exposed up to 11%
1183 // performance gain compared to other patterns of array_push/array_unshift/
1184 // array_reverse/array_pop/array_shift conjunction.
1185 $myid = array_shift ($trace);
1186 if (!count ($trace)) // reached the target
9dfd4cc9 1187 {
86256b96
DO
1188 if (!$threshold or ($threshold and $tree[$myid]['kidc'] + 1 < $threshold))
1189 $tree[$myid]['kids'][$key] = $value;
1190 // Reset accumulated records once, when the limit is reached, not each time
1191 // after that.
1192 if (++$tree[$myid]['kidc'] == $threshold)
1193 $tree[$myid]['kids'] = array();
1194 }
1195 else // not yet
1196 {
1197 $self = __FUNCTION__;
1198 $self ($tree[$myid]['kids'], $trace, $key, $value, $threshold);
9dfd4cc9 1199 }
9dfd4cc9
DO
1200}
1201
0ba76ca2
DO
1202// Likewise traverse the tree with the trace and return the final node.
1203function peekNode ($tree, $trace, $target_id)
1204{
1205 $self = __FUNCTION__;
1206 if (NULL === ($next = array_shift ($trace))) // warm
1207 {
1208 foreach ($tree as $node)
f1e27fe5 1209 if (array_key_exists ('id', $node) and $node['id'] == $target_id) // hot
0ba76ca2
DO
1210 return $node;
1211 }
1212 else // cold
1213 {
1214 foreach ($tree as $node)
3e576410 1215 if (array_key_exists ('id', $node) and $node['id'] == $next) // warmer
0ba76ca2
DO
1216 return $self ($node['kids'], $trace, $target_id);
1217 }
99e09736 1218 throw new RackTablesError ('inconsistent tree data', RackTablesError::INTERNAL);
0ba76ca2
DO
1219}
1220
2056822d
AA
1221function treeItemCmp ($a, $b)
1222{
1223 return $a['__tree_index'] - $b['__tree_index'];
1224}
1225
d65353ad
DO
1226// Build a tree from the item list and return it. Input and output data is
1227// indexed by item id (nested items in output are recursively stored in 'kids'
51b6651a
DO
1228// key, which is in turn indexed by id. Functions, which are ready to handle
1229// tree collapsion/expansion themselves, may request non-zero threshold value
1230// for smaller resulting tree.
3fb336f6 1231function treeFromList (&$orig_nodelist, $threshold = 0, $return_main_payload = TRUE)
d65353ad 1232{
86256b96 1233 $tree = array();
3fb336f6 1234 $nodelist = $orig_nodelist;
2056822d
AA
1235
1236 // index the tree items by their order in $orig_nodelist
1237 $ti = 0;
1238 foreach ($nodelist as &$node)
1239 $node['__tree_index'] = $ti++;
1240
86256b96
DO
1241 // Array equivalent of traceEntity() function.
1242 $trace = array();
1243 // set kidc and kids only once
1244 foreach (array_keys ($nodelist) as $nodeid)
1245 {
1246 $nodelist[$nodeid]['kidc'] = 0;
1247 $nodelist[$nodeid]['kids'] = array();
1248 }
1249 do
9dfd4cc9 1250 {
86256b96
DO
1251 $nextpass = FALSE;
1252 foreach (array_keys ($nodelist) as $nodeid)
9dfd4cc9 1253 {
86256b96
DO
1254 // When adding a node to the working tree, book another
1255 // iteration, because the new item could make a way for
1256 // others onto the tree. Also remove any item added from
1257 // the input list, so iteration base shrinks.
1258 // First check if we can assign directly.
1259 if ($nodelist[$nodeid]['parent_id'] == NULL)
9dfd4cc9 1260 {
86256b96
DO
1261 $tree[$nodeid] = $nodelist[$nodeid];
1262 $trace[$nodeid] = array(); // Trace to root node is empty
1263 unset ($nodelist[$nodeid]);
1264 $nextpass = TRUE;
9dfd4cc9 1265 }
86256b96
DO
1266 // Now look if it fits somewhere on already built tree.
1267 elseif (isset ($trace[$nodelist[$nodeid]['parent_id']]))
9dfd4cc9 1268 {
86256b96
DO
1269 // Trace to a node is a trace to its parent plus parent id.
1270 $trace[$nodeid] = $trace[$nodelist[$nodeid]['parent_id']];
1271 $trace[$nodeid][] = $nodelist[$nodeid]['parent_id'];
1272 pokeNode ($tree, $trace[$nodeid], $nodeid, $nodelist[$nodeid], $threshold);
1273 // path to any other node is made of all parent nodes plus the added node itself
1274 unset ($nodelist[$nodeid]);
1275 $nextpass = TRUE;
9dfd4cc9
DO
1276 }
1277 }
9dfd4cc9 1278 }
86256b96 1279 while ($nextpass);
3fb336f6 1280 if (!$return_main_payload)
86256b96 1281 return $nodelist;
3fb336f6
DO
1282 // update each input node with its backtrace route
1283 foreach ($trace as $nodeid => $route)
1284 $orig_nodelist[$nodeid]['trace'] = $route;
2056822d 1285 sortTree ($tree, 'treeItemCmp'); // sort the resulting tree by the order in original list
3fb336f6 1286 return $tree;
9dfd4cc9
DO
1287}
1288
49fb1027 1289// Build a tree from the tag list and return everything _except_ the tree.
573214e0
DO
1290// IOW, return taginfo items, which have parent_id set and pointing outside
1291// of the "normal" tree, which originates from the root.
49fb1027
DO
1292function getOrphanedTags ()
1293{
1294 global $taglist;
86256b96 1295 return treeFromList ($taglist, 0, FALSE);
49fb1027
DO
1296}
1297
ba93bd98
DO
1298// Return the list of missing implicit tags.
1299function getImplicitTags ($oldtags)
1300{
3fb336f6
DO
1301 global $taglist;
1302 $tmp = array();
1303 foreach ($oldtags as $taginfo)
1304 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']);
1305 // don't call array_unique here, it is in the function we will call now
1306 return buildTagChainFromIds ($tmp);
ba93bd98
DO
1307}
1308
6e49bd1f 1309// Minimize the chain: exclude all implicit tags and return the result.
5fd2a004
DO
1310// This function makes use of an external cache with a miss/hit ratio
1311// about 3/7 (ticket:255).
3fb336f6 1312function getExplicitTagsOnly ($chain)
ab379543 1313{
5fd2a004 1314 global $taglist, $tagRelCache;
ab379543 1315 $ret = array();
5fd2a004
DO
1316 foreach (array_keys ($chain) as $keyA) // check each A
1317 {
1318 $tagidA = $chain[$keyA]['id'];
1319 // do not include A in result, if A is seen on the trace of any B!=A
1320 foreach (array_keys ($chain) as $keyB)
1321 {
1322 $tagidB = $chain[$keyB]['id'];
1323 if ($tagidA == $tagidB)
1324 continue;
1325 if (!isset ($tagRelCache[$tagidA][$tagidB]))
0745db4d 1326 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']);
5fd2a004
DO
1327 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B
1328 continue 2; // skip this A
1329 }
1330 $ret[] = $chain[$keyA];
1331 }
74ccacff
DO
1332 return $ret;
1333}
1334
abef7149
DO
1335// Check, if the given tag is present on the chain (will only work
1336// for regular tags with tag ID set.
1337function tagOnChain ($taginfo, $tagchain)
1338{
1339 if (!isset ($taginfo['id']))
1340 return FALSE;
1341 foreach ($tagchain as $test)
1342 if ($test['id'] == $taginfo['id'])
1343 return TRUE;
1344 return FALSE;
1345}
1346
f8821b96
DO
1347function tagNameOnChain ($tagname, $tagchain)
1348{
1349 foreach ($tagchain as $test)
1350 if ($test['tag'] == $tagname)
1351 return TRUE;
1352 return FALSE;
1353}
1354
abef7149
DO
1355// Return TRUE, if two tags chains differ (order of tags doesn't matter).
1356// Assume, that neither of the lists contains duplicates.
1357// FIXME: a faster, than O(x^2) method is possible for this calculation.
1358function tagChainCmp ($chain1, $chain2)
1359{
1360 if (count ($chain1) != count ($chain2))
1361 return TRUE;
1362 foreach ($chain1 as $taginfo1)
1363 if (!tagOnChain ($taginfo1, $chain2))
1364 return TRUE;
1365 return FALSE;
1366}
1367
0df8c52b 1368function redirectIfNecessary ()
da958e52 1369{
750d26d2
DO
1370 global
1371 $trigger,
1372 $pageno,
1373 $tabno;
a2b16202 1374 @session_start();
fa5b2764
AA
1375 if
1376 (
1377 ! isset ($_REQUEST['tab']) and
1378 isset ($_SESSION['RTLT'][$pageno]) and
1379 getConfigVar ('SHOW_LAST_TAB') == 'yes' and
1380 permitted ($pageno, $_SESSION['RTLT'][$pageno]['tabname']) and
1381 time() - $_SESSION['RTLT'][$pageno]['time'] <= TAB_REMEMBER_TIMEOUT
1382 )
a2b16202 1383 redirectUser (buildRedirectURL ($pageno, $_SESSION['RTLT'][$pageno]['tabname']));
fa5b2764 1384
750d26d2 1385 // check if we accidentaly got on a dynamic tab that shouldn't be shown for this object
3a7bdcc6
DO
1386 if
1387 (
1388 isset ($trigger[$pageno][$tabno]) and
1389 !strlen (call_user_func ($trigger[$pageno][$tabno]))
1390 )
fa5b2764
AA
1391 {
1392 $_SESSION['RTLT'][$pageno]['dont_remember'] = 1;
a2b16202 1393 redirectUser (buildRedirectURL ($pageno, 'default'));
fa5b2764 1394 }
bfb77a1f 1395 if (is_array (@$_SESSION['RTLT'][$pageno]) && isset ($_SESSION['RTLT'][$pageno]['dont_remember']))
a2b16202
AA
1396 unset ($_SESSION['RTLT'][$pageno]['dont_remember']);
1397 // store the last visited tab name
1398 if (isset ($_REQUEST['tab']))
1399 $_SESSION['RTLT'][$pageno] = array ('tabname' => $tabno, 'time' => time());
1400 session_commit(); // if we are continuing to run, unlock session data
0df8c52b
DO
1401}
1402
0c714908
DO
1403function prepareNavigation()
1404{
329ec966
DY
1405 global
1406 $pageno,
1407 $tabno;
329ec966
DY
1408 $pageno = (isset ($_REQUEST['page'])) ? $_REQUEST['page'] : 'index';
1409
0c714908 1410 if (isset ($_REQUEST['tab']))
329ec966 1411 $tabno = $_REQUEST['tab'];
0c714908 1412 else
329ec966 1413 $tabno = 'default';
329ec966
DY
1414}
1415
0df8c52b
DO
1416function fixContext ($target = NULL)
1417{
1418 global
1419 $pageno,
1420 $auto_tags,
1421 $expl_tags,
1422 $impl_tags,
1423 $target_given_tags,
1424 $user_given_tags,
1425 $etype_by_pageno,
1426 $page;
53bae67b 1427
0df8c52b
DO
1428 if ($target !== NULL)
1429 {
1430 $target_given_tags = $target['etags'];
1431 // Don't reset autochain, because auth procedures could push stuff there in.
1432 // Another important point is to ignore 'user' realm, so we don't infuse effective
1433 // context with autotags of the displayed account.
1434 if ($target['realm'] != 'user')
1435 $auto_tags = array_merge ($auto_tags, $target['atags']);
1436 }
1437 elseif (array_key_exists ($pageno, $etype_by_pageno))
53da7dca
DO
1438 {
1439 // Each page listed in the map above requires one uint argument.
0a50efb4 1440 $target_realm = $etype_by_pageno[$pageno];
0cc24e9a 1441 assertUIntArg ($page[$pageno]['bypass']);
0a50efb4 1442 $target_id = $_REQUEST[$page[$pageno]['bypass']];
b135a49d 1443 $target = spotEntity ($target_realm, $target_id);
53da7dca 1444 $target_given_tags = $target['etags'];
0df8c52b 1445 if ($target['realm'] != 'user')
53da7dca
DO
1446 $auto_tags = array_merge ($auto_tags, $target['atags']);
1447 }
4c9b513a
DO
1448 // Explicit and implicit chains should be normally empty at this point, so
1449 // overwrite the contents anyway.
1450 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1451 $impl_tags = getImplicitTags ($expl_tags);
da958e52
DO
1452}
1453
75a01117
DO
1454# Merge e/i/a-tags of the given cell structures into current context, when
1455# these aren't there yet.
1456function spreadContext ($extracell)
1457{
1458 global
1459 $auto_tags,
1460 $expl_tags,
1461 $impl_tags,
1462 $target_given_tags,
1463 $user_given_tags;
1464 foreach ($extracell['atags'] as $taginfo)
1465 if (! tagNameOnChain ($taginfo['tag'], $auto_tags))
1466 $auto_tags[] = $taginfo;
1467 $target_given_tags = mergeTagChains ($target_given_tags, $extracell['etags']);
1468 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1469 $impl_tags = getImplicitTags ($expl_tags);
1470}
1471
1472# return a structure suitable for feeding into restoreContext()
1473function getContext()
1474{
1475 global
1476 $auto_tags,
1477 $expl_tags,
1478 $impl_tags,
1479 $target_given_tags;
1480 return array
1481 (
1482 'auto_tags' => $auto_tags,
1483 'expl_tags' => $expl_tags,
1484 'impl_tags' => $impl_tags,
1485 'target_given_tags' => $target_given_tags,
1486 );
1487}
1488
1489function restoreContext ($ctx)
1490{
1491 global
1492 $auto_tags,
1493 $expl_tags,
1494 $impl_tags,
1495 $target_given_tags;
1496 $auto_tags = $ctx['auto_tags'];
1497 $expl_tags = $ctx['expl_tags'];
1498 $impl_tags = $ctx['impl_tags'];
1499 $target_given_tags = $ctx['target_given_tags'];
1500}
1501
abef7149
DO
1502// Take a list of user-supplied tag IDs to build a list of valid taginfo
1503// records indexed by tag IDs (tag chain).
6e49bd1f 1504function buildTagChainFromIds ($tagidlist)
ab379543 1505{
20c901a7 1506 global $taglist;
ab379543 1507 $ret = array();
abef7149 1508 foreach (array_unique ($tagidlist) as $tag_id)
ab379543
DO
1509 if (isset ($taglist[$tag_id]))
1510 $ret[] = $taglist[$tag_id];
1511 return $ret;
1512}
1513
e3b4b089
AA
1514function buildTagIdsFromChain ($tagchain)
1515{
1516 $ret = array();
1517 foreach ($tagchain as $taginfo)
1518 $ret[] = $taginfo['id'];
1519 return array_unique ($ret);
1520}
1521
7cc02fc1
DO
1522// Process a given tag tree and return only meaningful branches. The resulting
1523// (sub)tree will have refcnt leaves on every last branch.
a26a6ccc 1524function getObjectiveTagTree ($tree, $realm, $preselect)
7cc02fc1 1525{
51b6651a 1526 $self = __FUNCTION__;
7cc02fc1
DO
1527 $ret = array();
1528 foreach ($tree as $taginfo)
1529 {
a26a6ccc
DO
1530 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1531 // If the current node addresses something, add it to the result
1532 // regardless of how many sub-nodes it features.
1533 if
7cc02fc1 1534 (
a26a6ccc
DO
1535 isset ($taginfo['refcnt'][$realm]) or
1536 count ($subsearch) > 1 or
3fb336f6 1537 in_array ($taginfo['id'], $preselect)
a26a6ccc
DO
1538 )
1539 $ret[] = array
1540 (
1541 'id' => $taginfo['id'],
1542 'tag' => $taginfo['tag'],
1543 'parent_id' => $taginfo['parent_id'],
1544 'refcnt' => $taginfo['refcnt'],
1545 'kids' => $subsearch
1546 );
1547 else
1548 $ret = array_merge ($ret, $subsearch);
7cc02fc1
DO
1549 }
1550 return $ret;
1551}
1552
95857b5c
DO
1553// Preprocess tag tree to get only tags which can effectively reduce given filter result,
1554// than passes shrinked tag tree to getObjectiveTagTree and return its result.
21ee3351 1555// This makes sense only if andor mode is 'and', otherwise function does not modify tree.
95857b5c
DO
1556// 'Given filter' is a pair of $entity_list(filter result) and $preselect(filter data).
1557// 'Effectively' means reduce to non-empty result.
1558function getShrinkedTagTree($entity_list, $realm, $preselect) {
1559 global $tagtree;
3dcf526a 1560 if ($preselect['andor'] != 'and' || empty($entity_list) && $preselect['is_empty'])
95857b5c
DO
1561 return getObjectiveTagTree($tagtree, $realm, $preselect['tagidlist']);
1562
1563 $used_tags = array(); //associative, keys - tag ids, values - taginfos
1564 foreach ($entity_list as $entity)
1565 {
1566 foreach ($entity['etags'] as $etag)
1567 if (! array_key_exists($etag['id'], $used_tags))
1568 $used_tags[$etag['id']] = 1;
1569 else
1570 $used_tags[$etag['id']]++;
1571
1572 foreach ($entity['itags'] as $itag)
1573 if (! array_key_exists($itag['id'], $used_tags))
1574 $used_tags[$itag['id']] = 0;
1575 }
1576
1577 $shrinked_tree = shrinkSubtree($tagtree, $used_tags, $preselect, $realm);
1578 return getObjectiveTagTree($shrinked_tree, $realm, $preselect['tagidlist']);
1579}
1580
1581// deletes item from tag subtree unless it exists in $used_tags and not preselected
1582function shrinkSubtree($tree, $used_tags, $preselect, $realm) {
1583 $self = __FUNCTION__;
1584
1585 foreach($tree as $i => &$item) {
1586 $item['kids'] = $self($item['kids'], $used_tags, $preselect, $realm);
1587 $item['kidc'] = count($item['kids']);
1588 if
1589 (
1590 ! array_key_exists($item['id'], $used_tags) &&
1591 ! in_array($item['id'], $preselect['tagidlist']) &&
1592 ! $item['kidc']
1593 )
1594 unset($tree[$i]);
1595 else {
444953cc
AA
1596 if (isset ($used_tags[$item['id']]) && $used_tags[$item['id']])
1597 $item['refcnt'][$realm] = $used_tags[$item['id']];
1598 else
95857b5c
DO
1599 unset($item['refcnt'][$realm]);
1600 }
1601 }
1602 return $tree;
1603}
1604
7ddb2c05
DO
1605// Get taginfo record by tag name, return NULL, if record doesn't exist.
1606function getTagByName ($target_name)
1607{
1608 global $taglist;
1609 foreach ($taglist as $taginfo)
1610 if ($taginfo['tag'] == $target_name)
1611 return $taginfo;
1612 return NULL;
1613}
1614
fc73c734
DO
1615// Merge two chains, filtering dupes out. Return the resulting superset.
1616function mergeTagChains ($chainA, $chainB)
1617{
1618 // $ret = $chainA;
1619 // Reindex by tag id in any case.
1620 $ret = array();
1621 foreach ($chainA as $tag)
1622 $ret[$tag['id']] = $tag;
1623 foreach ($chainB as $tag)
1624 if (!isset ($ret[$tag['id']]))
1625 $ret[$tag['id']] = $tag;
1626 return $ret;
1627}
1628
201204fb
DO
1629# Return a list consisting of tag ID of the given tree node and IDs of all
1630# nodes it contains.
1631function getTagIDListForNode ($treenode)
1632{
1633 $self = __FUNCTION__;
1634 $ret = array ($treenode['id']);
1635 foreach ($treenode['kids'] as $item)
1636 $ret = array_merge ($ret, $self ($item));
1637 return $ret;
1638}
1639
31c941ec
DO
1640function getCellFilter ()
1641{
7da7450c 1642 global $sic;
eca0114c 1643 global $pageno;
3dcf526a 1644 $andor_used = FALSE;
a2b16202 1645 @session_start();
444953cc 1646 // 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.
4318ced5 1647 if (isset($_REQUEST['andor']))
3dcf526a 1648 $andor_used = TRUE;
4318ced5 1649 if ($andor_used || array_key_exists ('clear-cf', $_REQUEST))
444953cc 1650 unset($_SESSION[$pageno]); // delete saved filter
4318ced5 1651
444953cc
AA
1652 // otherwise inject saved filter to the $_REQUEST and $sic vars
1653 elseif (isset ($_SESSION[$pageno]['filter']) and is_array ($_SESSION[$pageno]['filter']) and getConfigVar ('STATIC_FILTER') == 'yes')
1654 foreach (array('andor', 'cfe', 'cft[]', 'cfp[]', 'nft[]', 'nfp[]') as $param)
1655 {
1656 $param = str_replace ('[]', '', $param, $is_array);
1657 if (! isset ($_REQUEST[$param]) and isset ($_SESSION[$pageno]['filter'][$param]) and (!$is_array or is_array ($_SESSION[$pageno]['filter'][$param])))
1658 {
1659 $_REQUEST[$param] = $_SESSION[$pageno]['filter'][$param];
1660 if (! $is_array)
1661 $sic[$param] = $_REQUEST[$param];
1662 }
1663 }
eca0114c 1664
3d670bba
DO
1665 $ret = array
1666 (
23cdc7e9
DO
1667 'tagidlist' => array(),
1668 'tnamelist' => array(),
1669 'pnamelist' => array(),
444953cc 1670 'negatedlist' => array(),
23cdc7e9 1671 'andor' => '',
3d670bba 1672 'text' => '',
23cdc7e9
DO
1673 'extratext' => '',
1674 'expression' => array(),
a8efc03e 1675 'urlextra' => '', // Just put text here and let makeHref call urlencode().
3dcf526a 1676 'is_empty' => TRUE,
3d670bba 1677 );
23cdc7e9 1678 switch (TRUE)
31c941ec 1679 {
23cdc7e9 1680 case (!isset ($_REQUEST['andor'])):
444953cc 1681 $andor = getConfigVar ('FILTER_DEFAULT_ANDOR');
3d670bba 1682 break;
23cdc7e9
DO
1683 case ($_REQUEST['andor'] == 'and'):
1684 case ($_REQUEST['andor'] == 'or'):
444953cc
AA
1685 $_SESSION[$pageno]['filter']['andor'] = $_REQUEST['andor'];
1686 $ret['andor'] = $andor = $_REQUEST['andor'];
3d670bba
DO
1687 break;
1688 default:
08408472 1689 showWarning ('Invalid and/or switch value in submitted form');
3d670bba 1690 return NULL;
31c941ec 1691 }
23cdc7e9
DO
1692 // Both tags and predicates, which don't exist, should be
1693 // handled somehow. Discard them silently for now.
444953cc
AA
1694 global $taglist, $pTable;
1695 foreach (array ('cft', 'cfp', 'nft', 'nfp') as $param)
1696 if (isset ($_REQUEST[$param]) and is_array ($_REQUEST[$param]))
1697 {
1698 $_SESSION[$pageno]['filter'][$param] = $_REQUEST[$param];
1699 foreach ($_REQUEST[$param] as $req_key)
23cdc7e9 1700 {
444953cc
AA
1701 if (strpos ($param, 'ft') !== FALSE)
1702 {
1703 // param is a taglist
1704 if (! isset ($taglist[$req_key]))
1705 continue;
1706 $ret['tagidlist'][] = $req_key;
1707 $ret['tnamelist'][] = $taglist[$req_key]['tag'];
1708 $text = '{' . $taglist[$req_key]['tag'] . '}';
1709 }
1710 else
1711 {
1712 // param is a predicate list
1713 if (! isset ($pTable[$req_key]))
1714 continue;
1715 $ret['pnamelist'][] = $req_key;
1716 $text = '[' . $req_key . ']';
1717 }
1718 if (strpos ($param, 'nf') === 0)
1719 {
1720 $text = "not $text";
1721 $ret['negatedlist'][] = $req_key;
1722 }
1723 if (! empty ($ret['text']))
1724 {
1725 $andor_used = TRUE;
1726 $ret['text'] .= " $andor ";
1727 }
1728 $ret['text'] .= $text;
1729 $ret['urlextra'] .= '&' . $param . '[]=' . $req_key;
23cdc7e9 1730 }
444953cc 1731 }
7da7450c
DO
1732 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1733 if (isset ($sic['cfe']))
a8efc03e 1734 {
444953cc 1735 $_SESSION[$pageno]['filter']['cfe'] = $sic['cfe'];
7da7450c
DO
1736 // Only consider extra text, when it is a correct RackCode expression.
1737 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1738 if ($parse['result'] == 'ACK')
1739 {
1740 $ret['extratext'] = trim ($sic['cfe']);
1741 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1742 }
a8efc03e 1743 }
23cdc7e9
DO
1744 $finaltext = array();
1745 if (strlen ($ret['text']))
1746 $finaltext[] = '(' . $ret['text'] . ')';
1747 if (strlen ($ret['extratext']))
1748 $finaltext[] = '(' . $ret['extratext'] . ')';
3dcf526a 1749 $andor_used = $andor_used || (count($finaltext) > 1);
444953cc 1750 $finaltext = implode (' ' . $andor . ' ', $finaltext);
23cdc7e9
DO
1751 if (strlen ($finaltext))
1752 {
3dcf526a 1753 $ret['is_empty'] = FALSE;
23cdc7e9
DO
1754 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1755 $ret['expression'] = $parse['result'] == 'ACK' ? $parse['load'] : NULL;
8c699525
DO
1756 // It's not quite fair enough to put the blame of the whole text onto
1757 // non-empty "extra" portion of it, but it's the only user-generated portion
1758 // of it, thus the most probable cause of parse error.
1759 if (strlen ($ret['extratext']))
1760 $ret['extraclass'] = $parse['result'] == 'ACK' ? 'validation-success' : 'validation-error';
31c941ec 1761 }
3dcf526a
DO
1762 if (! $andor_used)
1763 $ret['andor'] = getConfigVar ('FILTER_DEFAULT_ANDOR');
1764 else
1765 $ret['urlextra'] .= '&andor=' . $ret['andor'];
3d670bba 1766 return $ret;
31c941ec
DO
1767}
1768
a2b16202 1769function buildRedirectURL ($nextpage = NULL, $nexttab = NULL, $moreArgs = array())
53bae67b 1770{
a2b16202
AA
1771 global $page, $pageno, $tabno;
1772 if ($nextpage === NULL)
1773 $nextpage = $pageno;
1774 if ($nexttab === NULL)
1775 $nexttab = $tabno;
1776 $url = "index.php?page=${nextpage}&tab=${nexttab}";
1777 if (isset ($page[$nextpage]['bypass']))
1778 $url .= '&' . $page[$nextpage]['bypass'] . '=' . $_REQUEST[$page[$nextpage]['bypass']];
1779 if (isset ($page[$nextpage]['bypass_tabs']))
1780 foreach ($page[$nextpage]['bypass_tabs'] as $param_name)
fa5b2764 1781 if (isset ($_REQUEST[$param_name]))
a2b16202
AA
1782 $url .= '&' . urlencode ($param_name) . '=' . urlencode ($_REQUEST[$param_name]);
1783
1784 if (count ($moreArgs) > 0)
1785 foreach ($moreArgs as $arg => $value)
1786 if (is_array ($value))
1787 foreach ($value as $v)
1788 $url .= '&' . urlencode ($arg . '[]') . '=' . urlencode ($v);
1789 elseif ($arg != 'module')
1790 $url .= '&' . urlencode ($arg) . '=' . urlencode ($value);
1791 return $url;
1792}
1793
3efb4742
AA
1794// store the accumulated message list into he $SESSION array to display them later
1795function backupLogMessages()
a2b16202
AA
1796{
1797 global $log_messages;
1798 if (! empty ($log_messages))
1799 {
1800 @session_start();
1801 $_SESSION['log'] = $log_messages;
1802 }
3efb4742
AA
1803}
1804
1805function redirectUser ($url)
1806{
1807 backupLogMessages();
a2b16202 1808 header ("Location: " . $url);
53bae67b
DO
1809 die;
1810}
1811
9f3e5caa
DO
1812function getRackCodeStats ()
1813{
1814 global $rackCode;
914b439b 1815 $defc = $grantc = $modc = 0;
9f3e5caa
DO
1816 foreach ($rackCode as $s)
1817 switch ($s['type'])
1818 {
1819 case 'SYNT_DEFINITION':
1820 $defc++;
1821 break;
1822 case 'SYNT_GRANT':
1823 $grantc++;
1824 break;
914b439b
DO
1825 case 'SYNT_CTXMOD':
1826 $modc++;
1827 break;
9f3e5caa
DO
1828 default:
1829 break;
1830 }
914b439b
DO
1831 $ret = array
1832 (
1833 'Definition sentences' => $defc,
1834 'Grant sentences' => $grantc,
1835 'Context mod sentences' => $modc
1836 );
9f3e5caa
DO
1837 return $ret;
1838}
1839
d5157018
DO
1840function getRackImageWidth ()
1841{
529eac25
DO
1842 global $rtwidth;
1843 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
d5157018
DO
1844}
1845
1846function getRackImageHeight ($units)
1847{
1848 return 3 + 3 + $units * 2;
1849}
1850
2d318652 1851// Indicate occupation state of each IP address: none, ordinary or problematic.
21ee3351 1852function markupIPAddrList (&$addrlist)
2d318652
DO
1853{
1854 foreach (array_keys ($addrlist) as $ip_bin)
1855 {
d983f70a
DO
1856 $refc = array
1857 (
00c5d8cb
DO
1858 'shared' => 0, // virtual
1859 'virtual' => 0, // loopback
1860 'regular' => 0, // connected host
1861 'router' => 0 // connected gateway
d983f70a
DO
1862 );
1863 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1864 $refc[$a['type']]++;
79ddc250 1865 $nvirtloopback = ($refc['shared'] + $refc['virtual'] + $refc['router'] > 0) ? 1 : 0; // modulus of virtual + shared + router
d983f70a 1866 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
79ddc250
AD
1867 $nrealms = $nreserved + $nvirtloopback + $refc['regular']; // last is connected allocation
1868
2d318652
DO
1869 if ($nrealms == 1)
1870 $addrlist[$ip_bin]['class'] = 'trbusy';
1871 elseif ($nrealms > 1)
1872 $addrlist[$ip_bin]['class'] = 'trerror';
c8020acc 1873 elseif (! empty ($addrlist[$ip_bin]['vslist']) or ! empty ($addrlist[$ip_bin]['rsplist']))
212b565a 1874 $addrlist[$ip_bin]['class'] = 'trbusy';
2d318652
DO
1875 else
1876 $addrlist[$ip_bin]['class'] = '';
1877 }
1878}
1879
21ee3351 1880// Scan the given address list (returned by scanIPv4Space/scanIPv6Space) and return a list of all routers found.
04d619d0
DO
1881function findRouters ($addrlist)
1882{
1883 $ret = array();
1884 foreach ($addrlist as $addr)
1885 foreach ($addr['allocs'] as $alloc)
1886 if ($alloc['type'] == 'router')
1887 $ret[] = array
1888 (
1889 'id' => $alloc['object_id'],
1890 'iface' => $alloc['name'],
1891 'dname' => $alloc['object_name'],
4318ced5 1892 'ip_bin' => $addr['ip_bin'],
04d619d0
DO
1893 );
1894 return $ret;
1895}
1896
1327d9dd
DO
1897// Compare networks. When sorting a tree, the records on the list will have
1898// distinct base IP addresses.
1752c495
AA
1899// valid return values are: 1, 0, -1, -2
1900// -2 has special meaning: $netA includes $netB
3444ecf2
DO
1901// "The comparison function must return an integer less than, equal to, or greater
1902// than zero if the first argument is considered to be respectively less than,
1903// equal to, or greater than the second." (c) PHP manual
4318ced5
AA
1904function IPNetworkCmp ($netA, $netB)
1905{
c09cdace
AA
1906 if (strlen ($netA['ip_bin']) !== strlen ($netB['ip_bin']))
1907 return strlen ($netA['ip_bin']) < strlen ($netB['ip_bin']) ? -1 : 1;
4318ced5 1908 $ret = strcmp ($netA['ip_bin'], $netB['ip_bin']);
1752c495 1909 $ret = ($ret > 0 ? 1 : ($ret < 0 ? -1 : 0));
4318ced5
AA
1910 if ($ret == 0)
1911 $ret = $netA['mask'] < $netB['mask'] ? -1 : ($netA['mask'] > $netB['mask'] ? 1 : 0);
1912 if ($ret == -1 and $netA['ip_bin'] === ($netB['ip_bin'] & $netA['mask_bin']))
1913 $ret = -2;
1914 return $ret;
1327d9dd
DO
1915}
1916
4318ced5 1917function IPNetContainsOrEqual ($netA, $netB)
21ee3351 1918{
4318ced5
AA
1919 $res = IPNetworkCmp ($netA, $netB);
1920 return ($res == -2 || $res == 0);
1921}
1922
1923function IPNetContains ($netA, $netB)
1924{
1925 return (-2 == IPNetworkCmp ($netA, $netB));
21ee3351
AA
1926}
1927
71066ef1
AA
1928function ip_in_range ($ip_bin, $range)
1929{
1930 return ($ip_bin & $range['mask_bin']) === $range['ip_bin'];
1931}
1932
fb7a4967 1933// Modify the given tag tree so, that each level's items are sorted alphabetically.
1327d9dd 1934function sortTree (&$tree, $sortfunc = '')
fb7a4967 1935{
59a83bd8 1936 if (!strlen ($sortfunc))
1327d9dd 1937 return;
51b6651a 1938 $self = __FUNCTION__;
1327d9dd 1939 usort ($tree, $sortfunc);
fb7a4967
DO
1940 // Don't make a mistake of directly iterating over the items of current level, because this way
1941 // the sorting will be performed on a _copy_ if each item, not the item itself.
1942 foreach (array_keys ($tree) as $tagid)
51b6651a 1943 $self ($tree[$tagid]['kids'], $sortfunc);
fb7a4967
DO
1944}
1945
0137d53c
DO
1946function iptree_fill (&$netdata)
1947{
59a83bd8 1948 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
0137d53c 1949 return;
a987ff52 1950 // If we really have nested prefixes, they must fit into the tree.
4318ced5 1951 $worktree = $netdata;
0137d53c
DO
1952 foreach ($netdata['kids'] as $pfx)
1953 iptree_embed ($worktree, $pfx);
1954 $netdata['kids'] = iptree_construct ($worktree);
1955 $netdata['kidc'] = count ($netdata['kids']);
1956}
1957
1958function iptree_construct ($node)
1959{
1960 $self = __FUNCTION__;
1961
1962 if (!isset ($node['right']))
1963 {
4318ced5 1964 if (!isset ($node['kids']))
0137d53c 1965 {
0137d53c 1966 $node['kids'] = array();
fec0c8da 1967 $node['kidc'] = 0;
0137d53c
DO
1968 $node['name'] = '';
1969 }
1970 return array ($node);
1971 }
1972 else
1973 return array_merge ($self ($node['left']), $self ($node['right']));
1974}
1975
4318ced5 1976function ip_format ($ip_bin)
21ee3351 1977{
4318ced5
AA
1978 switch (strlen ($ip_bin))
1979 {
1980 case 4: return ip4_format ($ip_bin);
1981 case 16: return ip6_format ($ip_bin);
1982 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
1983 }
1984}
21ee3351 1985
4318ced5
AA
1986function ip4_format ($ip_bin)
1987{
1988 if (4 != ($len = strlen ($ip_bin)))
1989 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
1990 return implode ('.', unpack ('C*', $ip_bin));
1991}
1992
1993function ip6_format ($ip_bin)
1994{
1995 // maybe this is IPv6-to-IPv4 address?
1996 if (substr ($ip_bin, 0, 12) == "\0\0\0\0\0\0\0\0\0\0\xff\xff")
1997 return '::ffff:' . implode ('.', unpack ('C*', substr ($ip_bin, 12, 4)));
1998
1999 $result = array();
2000 $hole_index = NULL;
2001 $max_hole_index = NULL;
2002 $hole_length = 0;
2003 $max_hole_length = 0;
2004
2005 for ($i = 0; $i < 8; $i++)
21ee3351 2006 {
4318ced5
AA
2007 $unpacked = unpack ('n', substr ($ip_bin, $i * 2, 2));
2008 $value = array_shift ($unpacked);
2009 $result[] = dechex ($value & 0xffff);
2010 if ($value != 0)
21ee3351 2011 {
4318ced5
AA
2012 unset ($hole_index);
2013 $hole_length = 0;
21ee3351 2014 }
4318ced5
AA
2015 else
2016 {
2017 if (! isset ($hole_index))
2018 $hole_index = $i;
2019 if (++$hole_length >= $max_hole_length)
2020 {
2021 $max_hole_index = $hole_index;
2022 $max_hole_length = $hole_length;
2023 }
2024 }
2025 }
2026 if (isset ($max_hole_index))
2027 {
2028 array_splice ($result, $max_hole_index, $max_hole_length, array (''));
2029 if ($max_hole_index == 0 && $max_hole_length == 8)
2030 return '::';
2031 elseif ($max_hole_index == 0)
2032 return ':' . implode (':', $result);
2033 elseif ($max_hole_index + $max_hole_length == 8)
2034 return implode (':', $result) . ':';
21ee3351 2035 }
4318ced5
AA
2036 return implode (':', $result);
2037}
2038
2039function ip_parse ($ip)
2040{
2041 if (FALSE !== strpos ($ip, ':'))
2042 return ip6_parse ($ip);
21ee3351 2043 else
4318ced5 2044 return ip4_parse ($ip);
21ee3351
AA
2045}
2046
4318ced5 2047function ip4_parse ($ip)
0137d53c 2048{
4318ced5
AA
2049 if (FALSE === ($int = ip2long ($ip)))
2050 throw new InvalidArgException ('ip', $ip, "Invalid IPv4 address");
2051 else
2052 return pack ('N', $int);
2053}
0137d53c 2054
4318ced5
AA
2055// returns 16-byte string ip_bin
2056// throws exception if unable to parse
2057function ip6_parse ($ip)
2058{
2059 do {
2060 if (empty ($ip))
2061 break;
2062
2063 $result = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // 16 bytes
2064 // remove one of double beginning/tailing colons
2065 if (substr ($ip, 0, 2) == '::')
2066 $ip = substr ($ip, 1);
2067 elseif (substr ($ip, -2, 2) == '::')
2068 $ip = substr ($ip, 0, strlen ($ip) - 1);
2069
2070 $tokens = explode (':', $ip);
2071 $last_token = $tokens[count ($tokens) - 1];
2072 $split = explode ('.', $last_token);
2073 if (count ($split) == 4)
2074 {
2075 $hex_tokens = array();
2076 $hex_tokens[] = dechex ($split[0] * 256 + $split[1]);
2077 $hex_tokens[] = dechex ($split[2] * 256 + $split[3]);
2078 array_splice ($tokens, -1, 1, $hex_tokens);
2079 }
2080 if (count ($tokens) > 8)
2081 break;
2082 for ($i = 0; $i < count ($tokens); $i++)
2083 {
2084 if ($tokens[$i] != '')
2085 {
2086 if (! set_word_value ($result, $i, $tokens[$i]))
2087 break;
2088 }
2089 else
2090 {
2091 $k = 8; //index in result string (last word)
2092 for ($j = count ($tokens) - 1; $j > $i; $j--) // $j is an index in $tokens for reverse walk
2093 if ($tokens[$j] == '')
2094 break;
2095 elseif (! set_word_value ($result, --$k, $tokens[$j]))
2096 break;
2097 if ($i != $j)
2098 break; //error, more than 1 '::' range
2099 break;
2100 }
2101 }
2102 if (! isset ($k) && count ($tokens) != 8)
2103 break;
2104 return $result;
2105 } while (FALSE);
2106
2107 throw new InvalidArgException ('ip', $ip, "Invalid IPv6 address");
2108}
2109
26ead7e2
AA
2110function ip_get_arpa ($ip_bin)
2111{
2112 switch (strlen ($ip_bin))
2113 {
2114 case 4: return ip4_get_arpa ($ip_bin);
2115 case 16: return ip6_get_arpa ($ip_bin);
2116 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2117 }
2118}
2119
2120function ip4_get_arpa ($ip_bin)
2121{
2122 $ret = '';
2123 for ($i = 3; $i >= 0; $i--)
2124 $ret .= ord($ip_bin[$i]) . '.';
2125 return $ret . 'in-addr.arpa';
2126}
2127
2128function ip6_get_arpa ($ip_bin)
2129{
2130 $ret = '';
2131 $hex = implode ('', unpack('H32', $ip_bin));
2132 for ($i = 31; $i >= 0; $i--)
2133 $ret .= $hex[$i] . '.';
2134 return $ret . 'ip6.arpa';
2135}
2136
4318ced5
AA
2137function set_word_value (&$haystack, $nword, $hexvalue)
2138{
2139 // check that $hexvalue is like /^[0-9a-fA-F]*$/
2140 for ($i = 0; $i < strlen ($hexvalue); $i++)
0137d53c 2141 {
4318ced5
AA
2142 $char = ord ($hexvalue[$i]);
2143 if (! ($char >= 0x30 && $char <= 0x39 || $char >= 0x41 && $char <= 0x46 || $char >=0x61 && $char <= 0x66))
2144 return FALSE;
0137d53c 2145 }
4318ced5
AA
2146 $haystack = substr_replace ($haystack, pack ('n', hexdec ($hexvalue)), $nword * 2, 2);
2147 return TRUE;
2148}
0137d53c 2149
4318ced5
AA
2150// returns binary IP or FALSE
2151function ip_checkparse ($ip)
2152{
2153 try
2154 {
2155 return ip_parse ($ip);
2156 }
2157 catch (InvalidArgException $e)
0137d53c 2158 {
4318ced5
AA
2159 return FALSE;
2160 }
2161}
a987ff52 2162
4318ced5
AA
2163// returns binary IP or FALSE
2164function ip4_checkparse ($ip)
2165{
2166 try
2167 {
2168 return ip4_parse ($ip);
2169 }
2170 catch (InvalidArgException $e)
2171 {
2172 return FALSE;
0137d53c 2173 }
4318ced5 2174}
0137d53c 2175
4318ced5
AA
2176// returns binary IP or FALSE
2177function ip6_checkparse ($ip)
2178{
2179 try
2180 {
2181 return ip6_parse ($ip);
2182 }
2183 catch (InvalidArgException $e)
2184 {
2185 return FALSE;
2186 }
2187}
2188
2189function ip4_int2bin ($ip_int)
2190{
5a8bd9a3 2191 return pack ('N', $ip_int + 0);
4318ced5
AA
2192}
2193
2194function ip4_bin2int ($ip_bin)
2195{
2196 if (4 != strlen ($ip_bin))
2197 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2198 $list = unpack ('N', $ip_bin);
2199 return array_shift ($list);
2200}
2201
2202// Use this function only when you need to export binary ip out of PHP running context (e.g., DB)
2203// !DO NOT perform arithmetic and bitwise operations with the result of this function!
2204function ip4_bin2db ($ip_bin)
2205{
2206 $ip_int = ip4_bin2int ($ip_bin);
2207 if ($ip_int < 0)
2208 return sprintf ('%u', 0x00000000 + $ip_int);
0137d53c 2209 else
4318ced5
AA
2210 return $ip_int;
2211}
2212
2213function ip_last ($net)
2214{
2215 return $net['ip_bin'] | ~$net['mask_bin'];
2216}
2217
2218function ip_next ($ip_bin)
2219{
2220 $ret = $ip_bin;
2221 $p = 1;
2222 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2223 {
2224 $oct = $p + ord ($ret[$i]);
2225 $ret[$i] = chr ($oct & 0xff);
2226 if ($oct <= 255 and $oct >= 0)
2227 break;
2228 }
2229 return $ret;
2230}
2231
2232function ip_prev ($ip_bin)
2233{
2234 $ret = $ip_bin;
2235 $p = -1;
2236 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2237 {
2238 $oct = $p + ord ($ret[$i]);
2239 $ret[$i] = chr ($oct & 0xff);
2240 if ($oct <= 255 and $oct >= 0)
2241 break;
2242 }
2243 return $ret;
2244}
2245
2246function ip4_range_size ($range)
2247{
2248 return ip4_mask_size ($range['mask']);
2249}
2250
2251function ip4_mask_size ($mask)
2252{
2253 return (0xffffffff >> $mask) + 1;
2254}
2255
2256// returns array with keys 'ip', 'ip_bin', 'mask', 'mask_bin'
2257function constructIPRange ($ip_bin, $mask)
2258{
2259 $node = array();
2260 switch (strlen ($ip_bin))
2261 {
2262 case 4: // IPv4
2263 if ($mask < 0 || $mask > 32)
2264 throw new InvalidArgException ('mask', $mask, "Invalid v4 prefix length");
2265 $node['mask_bin'] = ip4_mask ($mask);
2266 $node['mask'] = $mask;
2267 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2268 $node['ip'] = ip4_format ($node['ip_bin']);
2269 break;
2270 case 16: // IPv6
2271 if ($mask < 0 || $mask > 128)
2272 throw new InvalidArgException ('mask', $mask, "Invalid v6 prefix length");
2273 $node['mask_bin'] = ip6_mask ($mask);
2274 $node['mask'] = $mask;
2275 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2276 $node['ip'] = ip6_format ($node['ip_bin']);
2277 break;
2278 default:
2279 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2280 }
2281 return $node;
2282}
2283
2284// Return minimal IP address structure
2285function constructIPAddress ($ip_bin)
2286{
2287 // common v4/v6 part
2288 $ret = array
2289 (
2290 'ip' => ip_format ($ip_bin),
2291 'ip_bin' => $ip_bin,
2292 'name' => '',
0cd7c0f6 2293 'comment' => '',
4318ced5
AA
2294 'reserved' => 'no',
2295 'allocs' => array(),
71066ef1
AA
2296 'vslist' => array(),
2297 'rsplist' => array(),
4318ced5
AA
2298 );
2299
2300 // specific v4 part
2301 if (strlen ($ip_bin) == 4)
2302 $ret = array_merge
2303 (
2304 $ret,
2305 array
2306 (
2307 'outpf' => array(),
2308 'inpf' => array(),
4318ced5
AA
2309 )
2310 );
2311 return $ret;
0137d53c
DO
2312}
2313
4318ced5 2314function iptree_embed (&$node, $pfx)
21ee3351
AA
2315{
2316 $self = __FUNCTION__;
2317
2318 // hit?
4318ced5 2319 if (0 == IPNetworkCmp ($node, $pfx))
21ee3351
AA
2320 {
2321 $node = $pfx;
2322 return;
2323 }
2324 if ($node['mask'] == $pfx['mask'])
2325 throw new RackTablesError ('the recurring loop lost control', RackTablesError::INTERNAL);
2326
2327 // split?
2328 if (!isset ($node['right']))
2329 {
4318ced5
AA
2330 $node['left'] = constructIPRange ($node['ip_bin'], $node['mask'] + 1);
2331 $node['right'] = constructIPRange (ip_last ($node), $node['mask'] + 1);
21ee3351
AA
2332 }
2333
4318ced5 2334 if (IPNetContainsOrEqual ($node['left'], $pfx))
21ee3351 2335 $self ($node['left'], $pfx);
4318ced5 2336 elseif (IPNetContainsOrEqual ($node['right'], $pfx))
21ee3351
AA
2337 $self ($node['right'], $pfx);
2338 else
2339 throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
2340}
2341
3b81cb98 2342function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
0137d53c 2343{
59a83bd8 2344 if (!strlen ($func))
0137d53c
DO
2345 return;
2346 $self = __FUNCTION__;
2347 foreach (array_keys ($tree) as $key)
2348 {
2349 $func ($tree[$key]);
59a83bd8 2350 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
c3315231 2351 continue;
0137d53c
DO
2352 $self ($tree[$key]['kids'], $func);
2353 }
2354}
2355
4318ced5 2356function nodeIsCollapsed ($node)
a987ff52 2357{
4318ced5 2358 return $node['symbol'] == 'node-collapsed';
a987ff52 2359}
b18d26dc 2360
f59e175c
AA
2361// sets 'addrlist', 'own_addrlist', 'addrc', 'own_addrc' keys of $node
2362// 'addrc' and 'own_addrc' are sizes of 'addrlist' and 'own_addrlist', respectively
2363// 'own_addrlist' is empty if the node network has no children
4318ced5 2364function loadIPAddrList (&$node)
b18d26dc 2365{
4318ced5
AA
2366 $node['addrlist'] = scanIPSpace (array (array ('first' => $node['ip_bin'], 'last' => ip_last ($node))));
2367
2368 if (! isset ($node['id']))
2369 $node['own_addrlist'] = $node['addrlist'];
b18d26dc 2370 else
4318ced5 2371 {
f59e175c
AA
2372 if ($node['kidc'] == 0)
2373 $node['own_addrlist'] = $node['addrlist'];
2374 //$node['own_addrlist'] = array();
2375 else
2376 {
2377 $node['own_addrlist'] = array();
4318ced5
AA
2378 // node has childs
2379 foreach ($node['spare_ranges'] as $mask => $spare_list)
2380 foreach ($spare_list as $spare_ip)
2381 {
2382 $spare_range = constructIPRange ($spare_ip, $mask);
2383 foreach ($node['addrlist'] as $bin_ip => $addr)
2384 if (($bin_ip & $spare_range['mask_bin']) == $spare_range['ip_bin'])
2385 $node['own_addrlist'][$bin_ip] = $addr;
2386 }
f59e175c 2387 }
4318ced5
AA
2388 }
2389 $node['addrc'] = count ($node['addrlist']);
2390 $node['own_addrc'] = count ($node['own_addrlist']);
b6b87070
DO
2391}
2392
4318ced5 2393function getIPv4OwnRangeSize ($range)
c3315231 2394{
f59e175c 2395 if (empty ($range['spare_ranges']))
4318ced5 2396 return ip4_range_size ($range);
f59e175c
AA
2397 else
2398 {
2399 $ret = 0;
4318ced5
AA
2400 foreach ($range['spare_ranges'] as $mask => $spare_list)
2401 $ret += count ($spare_list) * ip4_mask_size ($mask);
f59e175c
AA
2402 return $ret;
2403 }
c3315231
DO
2404}
2405
4318ced5
AA
2406// returns the array of structure described by constructIPAddress
2407function getIPAddress ($ip_bin)
b6b87070 2408{
4318ced5
AA
2409 $scanres = scanIPSpace (array (array ('first' => $ip_bin, 'last' => $ip_bin)));
2410 if (empty ($scanres))
2411 return constructIPAddress ($ip_bin);
2412 markupIPAddrList ($scanres);
2413 return $scanres[$ip_bin];
b18d26dc
DO
2414}
2415
4318ced5 2416function getIPv4Address ($ip_bin)
21ee3351 2417{
4318ced5
AA
2418 if (strlen ($ip_bin) != 4)
2419 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2420 return getIPAddress ($ip_bin);
21ee3351
AA
2421}
2422
4318ced5 2423function getIPv6Address ($ip_bin)
21ee3351 2424{
4318ced5
AA
2425 if (strlen ($ip_bin) != 16)
2426 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2427 return getIPAddress ($ip_bin);
21ee3351
AA
2428}
2429
4318ced5 2430function makeIPTree ($netlist)
fec0c8da 2431{
573214e0 2432 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
4318ced5
AA
2433 // so perform necessary pre-processing to calculate parent_id of each item of $netlist.
2434 $stack = array();
2435 foreach ($netlist as $net_id => &$net)
2436 {
2437 while (! empty ($stack))
2438 {
2439 $top_id = $stack[count ($stack) - 1];
2440 if (! IPNetContains ($netlist[$top_id], $net)) // unless $net is a child of stack top
2441 array_pop ($stack);
2442 else
2443 {
2444 $net['parent_id'] = $top_id;
2445 break;
2446 }
2447 }
2448 if (empty ($stack))
2449 $net['parent_id'] = NULL;
2450 array_push ($stack, $net_id);
2451 }
2452 unset ($stack);
2453
fec0c8da 2454 $tree = treeFromList ($netlist); // medium call
fec0c8da
DO
2455 return $tree;
2456}
2457
4318ced5 2458function prepareIPTree ($netlist, $expanded_id = 0)
21ee3351 2459{
4318ced5 2460 $tree = makeIPTree ($netlist);
21ee3351 2461 // complement the tree before markup to make the spare networks have "symbol" set
4318ced5 2462 treeApplyFunc ($tree, 'iptree_fill');
21ee3351
AA
2463 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
2464 return $tree;
2465}
2466
a280c8b4
DO
2467# Traverse IPv4/IPv6 tree and return a list of all networks, which
2468# exist in DB and don't have any sub-networks.
2469function getTerminalNetworks ($tree)
2470{
2471 $self = __FUNCTION__;
2472 $ret = array();
2473 foreach ($tree as $node)
2474 if ($node['kidc'] == 0 and isset ($node['realm']))
2475 $ret[] = $node;
2476 else
2477 $ret = array_merge ($ret, $self ($node['kids']));
2478 return $ret;
2479}
2480
fec0c8da
DO
2481// Check all items of the tree recursively, until the requested target id is
2482// found. Mark all items leading to this item as "expanded", collapsing all
2483// the rest, which exceed the given threshold (if the threshold is given).
2484function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
2485{
2486 $self = __FUNCTION__;
2487 $ret = FALSE;
2488 foreach (array_keys ($tree) as $key)
2489 {
5388794d 2490 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
fec0c8da 2491 $below = $self ($tree[$key]['kids'], $threshold, $target);
4318ced5 2492 $expand_enabled = ($target !== 'NONE');
fec0c8da
DO
2493 if (!$tree[$key]['kidc']) // terminal node
2494 $tree[$key]['symbol'] = 'spacer';
4318ced5 2495 elseif ($expand_enabled and $tree[$key]['kidc'] < $threshold)
fec0c8da 2496 $tree[$key]['symbol'] = 'node-expanded-static';
4318ced5 2497 elseif ($expand_enabled and ($here or $below))
fec0c8da
DO
2498 $tree[$key]['symbol'] = 'node-expanded';
2499 else
2500 $tree[$key]['symbol'] = 'node-collapsed';
2501 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
2502 }
2503 return $ret;
2504}
2505
e1ae3fb4
AD
2506// Convert entity name to human-readable value
2507function formatEntityName ($name) {
2508 switch ($name)
2509 {
2510 case 'ipv4net':
2511 return 'IPv4 Network';
7fa4b0e4
AA
2512 case 'ipv6net':
2513 return 'IPv6 Network';
e1ae3fb4
AD
2514 case 'ipv4rspool':
2515 return 'IPv4 RS Pool';
2516 case 'ipv4vs':
2517 return 'IPv4 Virtual Service';
2518 case 'object':
2519 return 'Object';
2520 case 'rack':
2521 return 'Rack';
e1add254
AD
2522 case 'location':
2523 return 'Location';
e1ae3fb4
AD
2524 case 'user':
2525 return 'User';
2526 }
2527 return 'invalid';
2528}
2529
8bc5d1e4
DO
2530// Display hrefs for all of a file's parents. If scissors are requested,
2531// prepend cutting button to each of them.
2532function serializeFileLinks ($links, $scissors = FALSE)
e1ae3fb4 2533{
e1ae3fb4
AD
2534 $comma = '';
2535 $ret = '';
2536 foreach ($links as $link_id => $li)
2537 {
2538 switch ($li['entity_type'])
2539 {
2540 case 'ipv4net':
2541 $params = "page=ipv4net&id=";
2542 break;
7fa4b0e4
AA
2543 case 'ipv6net':
2544 $params = "page=ipv6net&id=";
2545 break;
e1ae3fb4
AD
2546 case 'ipv4rspool':
2547 $params = "page=ipv4rspool&pool_id=";
2548 break;
2549 case 'ipv4vs':
2550 $params = "page=ipv4vs&vs_id=";
2551 break;
2552 case 'object':
2553 $params = "page=object&object_id=";
2554 break;
2555 case 'rack':
2556 $params = "page=rack&rack_id=";
2557 break;
e1add254
AD
2558 case 'location':
2559 $params = "page=location&location_id=";
2560 break;
e1ae3fb4
AD
2561 case 'user':
2562 $params = "page=user&user_id=";
2563 break;
2564 }
8bc5d1e4
DO
2565 $ret .= $comma;
2566 if ($scissors)
2567 {
2568 $ret .= "<a href='" . makeHrefProcess(array('op'=>'unlinkFile', 'link_id'=>$link_id)) . "'";
2569 $ret .= getImageHREF ('cut') . '</a> ';
2570 }
790a60e8 2571 $ret .= sprintf("<a href='index.php?%s%s'>%s</a>", $params, $li['entity_id'], $li['name']);
8bc5d1e4 2572 $comma = '<br>';
e1ae3fb4 2573 }
e1ae3fb4
AD
2574 return $ret;
2575}
2576
2577// Convert filesize to appropriate unit and make it human-readable
2578function formatFileSize ($bytes) {
2579 // bytes
2580 if($bytes < 1024) // bytes
2581 return "${bytes} bytes";
2582
2583 // kilobytes
2584 if ($bytes < 1024000)
2585 return sprintf ("%.1fk", round (($bytes / 1024), 1));
2586
2587 // megabytes
2588 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
2589}
2590
2591// Reverse of formatFileSize, it converts human-readable value to bytes
2592function convertToBytes ($value) {
2593 $value = trim($value);
2594 $last = strtolower($value[strlen($value)-1]);
2595 switch ($last)
2596 {
2597 case 'g':
2598 $value *= 1024;
2599 case 'm':
2600 $value *= 1024;
2601 case 'k':
2602 $value *= 1024;
2603 }
2604
2605 return $value;
2606}
2607
36ef72d9 2608// make "A" HTML element
3b3c5e21
DO
2609function mkA ($text, $nextpage, $bypass = NULL, $nexttab = NULL)
2610{
2611 global $page, $tab;
ae61a274 2612 if ($text == '')
3b3c5e21 2613 throw new InvalidArgException ('text', $text);
ae61a274
DO
2614 if (! array_key_exists ($nextpage, $page))
2615 throw new InvalidArgException ('nextpage', $nextpage, 'not found');
3b3c5e21
DO
2616 $args = array ('page' => $nextpage);
2617 if ($nexttab !== NULL)
2618 {
ae61a274
DO
2619 if (! array_key_exists ($nexttab, $tab[$nextpage]))
2620 throw new InvalidArgException ('nexttab', $nexttab, 'not found');
3b3c5e21
DO
2621 $args['tab'] = $nexttab;
2622 }
2623 if (array_key_exists ('bypass', $page[$nextpage]))
2624 {
2625 if ($bypass === NULL)
ae61a274 2626 throw new InvalidArgException ('bypass', '(NULL)');
3b3c5e21
DO
2627 $args[$page[$nextpage]['bypass']] = $bypass;
2628 }
2629 return '<a href="' . makeHref ($args) . '">' . $text . '</a>';
2630}
2631
36ef72d9 2632// make "HREF" HTML attribute
ae61a274 2633function makeHref ($params = array())
4fbb5a00 2634{
ae61a274
DO
2635 $tmp = array();
2636 foreach ($params as $key => $value)
3bbf8c03
AA
2637 {
2638 if (is_array ($value))
2639 $key .= "[]";
2640 else
2641 $value = array ($value);
73348a60
AA
2642 if (!count ($value))
2643 $tmp[] = urlencode ($key) . '=';
2644 else
2645 foreach ($value as $sub_value)
2646 $tmp[] = urlencode ($key) . '=' . urlencode ($sub_value);
3bbf8c03 2647 }
ae61a274 2648 return 'index.php?' . implode ('&', $tmp);
4fbb5a00
DY
2649}
2650
ae61a274 2651function makeHrefProcess ($params = array())
4fbb5a00 2652{
aa017c43 2653 global $pageno, $tabno, $page;
ae61a274
DO
2654 $tmp = array();
2655 if (! array_key_exists ('page', $params))
9f14a7ef 2656 $params['page'] = $pageno;
ae61a274 2657 if (! array_key_exists ('tab', $params))
9f14a7ef 2658 $params['tab'] = $tabno;
aa017c43
AA
2659 if (isset ($page[$pageno]['bypass']))
2660 {
2661 $bypass = $page[$pageno]['bypass'];
2662 if (! array_key_exists ($bypass, $params) and array_key_exists ($bypass, $_REQUEST))
2663 $params[$bypass] = $_REQUEST[$bypass];
2664 }
ae61a274
DO
2665 foreach ($params as $key => $value)
2666 $tmp[] = urlencode ($key) . '=' . urlencode ($value);
2667 return '?module=redirect&' . implode ('&', $tmp);
4fbb5a00
DY
2668}
2669
39106006 2670function makeHrefForHelper ($helper_name, $params = array())
4fbb5a00 2671{
e0ce8064 2672 $ret = '?module=popup&helper=' . $helper_name;
4fbb5a00 2673 foreach($params as $key=>$value)
39106006 2674 $ret .= '&'.urlencode($key).'='.urlencode($value);
4fbb5a00
DY
2675 return $ret;
2676}
2677
f3d274bf
DO
2678// Process the given list of records to build data suitable for printNiftySelect()
2679// (like it was formerly executed by printSelect()). Screen out vendors according
2680// to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
2681// selected OPTION is protected from being screened.
2682function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
2683{
2684 $ret = array();
2685 // Always keep "other" OPTGROUP at the SELECT bottom.
2686 $therest = array();
2687 foreach ($recordList as $dict_key => $dict_value)
2688 if (strpos ($dict_value, '%GSKIP%') !== FALSE)
2689 {
2690 $tmp = explode ('%GSKIP%', $dict_value, 2);
2691 $ret[$tmp[0]][$dict_key] = $tmp[1];
2692 }
2693 elseif (strpos ($dict_value, '%GPASS%') !== FALSE)
2694 {
2695 $tmp = explode ('%GPASS%', $dict_value, 2);
2696 $ret[$tmp[0]][$dict_key] = $tmp[1];
2697 }
2698 else
2699 $therest[$dict_key] = $dict_value;
2700 if ($object_type_id != 0)
2701 {
2702 $screenlist = array();
2703 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
eea3ca5e 2704 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
f3d274bf 2705 $screenlist[] = $regs[1];
eea3ca5e 2706 }
f3d274bf
DO
2707 foreach (array_keys ($ret) as $vendor)
2708 if (in_array ($vendor, $screenlist))
2709 {
2710 $ok_to_screen = TRUE;
2711 if ($existing_value)
2712 foreach (array_keys ($ret[$vendor]) as $recordkey)
2713 if ($recordkey == $existing_value)
2714 {
2715 $ok_to_screen = FALSE;
2716 break;
2717 }
2718 if ($ok_to_screen)
2719 unset ($ret[$vendor]);
2720 }
2721 }
2722 $ret['other'] = $therest;
2723 return $ret;
2724}
2725
9dd73255
DO
2726function unix2dos ($text)
2727{
2728 return str_replace ("\n", "\r\n", $text);
2729}
2730
a0527aef 2731function buildPredicateTable ($parsetree)
2c21a10c 2732{
a0527aef
DO
2733 $ret = array();
2734 foreach ($parsetree as $sentence)
72e8baf6 2735 if ($sentence['type'] == 'SYNT_DEFINITION')
a0527aef 2736 $ret[$sentence['term']] = $sentence['definition'];
72e8baf6
DO
2737 // Now we have predicate table filled in with the latest definitions of each
2738 // particular predicate met. This isn't as chik, as on-the-fly predicate
2739 // overloading during allow/deny scan, but quite sufficient for this task.
a0527aef
DO
2740 return $ret;
2741}
2742
2743// Take a list of records and filter against given RackCode expression. Return
2744// the original list intact, if there was no filter requested, but return an
2745// empty list, if there was an error.
573214e0 2746function filterCellList ($list_in, $expression = array())
d08d766d
DO
2747{
2748 if ($expression === NULL)
2749 return array();
2750 if (!count ($expression))
2751 return $list_in;
d08d766d
DO
2752 $list_out = array();
2753 foreach ($list_in as $item_key => $item_value)
573214e0 2754 if (TRUE === judgeCell ($item_value, $expression))
d08d766d
DO
2755 $list_out[$item_key] = $item_value;
2756 return $list_out;
2757}
2758
ef0503fc
DO
2759function eval_expression ($expr, $tagchain, $ptable, $silent = FALSE)
2760{
2761 $self = __FUNCTION__;
2762 switch ($expr['type'])
2763 {
2764 // Return true, if given tag is present on the tag chain.
2765 case 'LEX_TAG':
2766 case 'LEX_AUTOTAG':
2767 foreach ($tagchain as $tagInfo)
2768 if ($expr['load'] == $tagInfo['tag'])
2769 return TRUE;
2770 return FALSE;
2771 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it.
2772 $pname = $expr['load'];
2773 if (!isset ($ptable[$pname]))
2774 {
2775 if (!$silent)
08408472 2776 showWarning ("Predicate '${pname}' is referenced before declaration");
ef0503fc
DO
2777 return NULL;
2778 }
2779 return $self ($ptable[$pname], $tagchain, $ptable);
2780 case 'LEX_TRUE':
2781 return TRUE;
2782 case 'LEX_FALSE':
2783 return FALSE;
2784 case 'SYNT_NOT_EXPR':
2785 $tmp = $self ($expr['load'], $tagchain, $ptable);
2786 if ($tmp === TRUE)
2787 return FALSE;
2788 elseif ($tmp === FALSE)
2789 return TRUE;
2790 else
2791 return $tmp;
2792 case 'SYNT_AND_EXPR': // binary AND
2793 if (FALSE == $self ($expr['left'], $tagchain, $ptable))
2794 return FALSE; // early failure
2795 return $self ($expr['right'], $tagchain, $ptable);
2796 case 'SYNT_EXPR': // binary OR
2797 if (TRUE == $self ($expr['left'], $tagchain, $ptable))
2798 return TRUE; // early success
2799 return $self ($expr['right'], $tagchain, $ptable);
2800 default:
2801 if (!$silent)
08408472 2802 showWarning ("Evaluation error, cannot process expression type '${expr['type']}'");
ef0503fc
DO
2803 return NULL;
2804 break;
2805 }
2806}
2807
212c9d8a 2808// Tell, if the given expression is true for the given entity. Take complete record on input.
573214e0 2809function judgeCell ($cell, $expression)
d08d766d
DO
2810{
2811 global $pTable;
2812 return eval_expression
2813 (
2814 $expression,
2815 array_merge
2816 (
573214e0
DO
2817 $cell['etags'],
2818 $cell['itags'],
2819 $cell['atags']
d08d766d
DO
2820 ),
2821 $pTable,
2822 TRUE
2823 );
2824}
2825
9d7e7b8b
AA
2826function judgeContext ($expression)
2827{
2828 global $pTable, $expl_tags, $impl_tags, $auto_tags;
2829 return eval_expression
2830 (
2831 $expression,
2832 array_merge
2833 (
2834 $expl_tags,
2835 $impl_tags,
2836 $auto_tags
2837 ),
2838 $pTable,
2839 TRUE
2840 );
2841}
2842
7ddbcf59 2843// Tell, if a constraint from config option permits given record.
99446891 2844// An undefined $cell means current context.
212c9d8a 2845function considerConfiguredConstraint ($cell, $varname)
c6bc0ac5 2846{
7ddbcf59 2847 if (!strlen (getConfigVar ($varname)))
c6bc0ac5 2848 return TRUE; // no restriction
7ddbcf59
DO
2849 global $parseCache;
2850 if (!isset ($parseCache[$varname]))
2851 // getConfigVar() doesn't re-read the value from DB because of its
2852 // own cache, so there is no race condition here between two calls.
2853 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2854 if ($parseCache[$varname]['result'] != 'ACK')
2855 return FALSE; // constraint set, but cannot be used due to compilation error
99446891
AA
2856 if (isset ($cell))
2857 return judgeCell ($cell, $parseCache[$varname]['load']);
2858 else
2859 return judgeContext ($parseCache[$varname]['load']);
c6bc0ac5
DO
2860}
2861
682e77d1
DO
2862// Tell, if the given arbitrary RackCode text addresses the given record
2863// (an empty text matches any record).
68292868 2864// An undefined $cell means current context.
682e77d1
DO
2865function considerGivenConstraint ($cell, $filtertext)
2866{
2867 if ($filtertext == '')
2868 return TRUE;
2869 $parse = spotPayload ($filtertext, 'SYNT_EXPR');
2870 if ($parse['result'] != 'ACK')
2871 throw new InvalidRequestArgException ('filtertext', $filtertext, 'RackCode parsing error');
68292868
AA
2872 if (isset ($cell))
2873 return judgeCell ($cell, $parse['load']);
2874 else
2875 return judgeContext ($parse['load']);
682e77d1
DO
2876}
2877
17112e81
DO
2878// Return list of records in the given realm, which conform to
2879// the given RackCode expression. If the realm is unknown or text
2880// doesn't validate as a RackCode expression, return NULL.
2881// Otherwise (successful scan) return a list of all matched
2882// records, even if the list is empty (array() !== NULL). If the
2883// text is an empty string, return all found records in the given
2884// realm.
2885function scanRealmByText ($realm = NULL, $ftext = '')
2886{
2887 switch ($realm)
2888 {
2889 case 'object':
95e4b0f5 2890 case 'rack':
17112e81
DO
2891 case 'user':
2892 case 'ipv4net':
7fa4b0e4 2893 case 'ipv6net':
17112e81 2894 case 'file':
39776127 2895 case 'ipv4vs':
41780975 2896 case 'ipv4rspool':
883bd06c 2897 case 'vst':
17112e81
DO
2898 if (!strlen ($ftext = trim ($ftext)))
2899 $fexpr = array();
2900 else
2901 {
2902 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2903 if ($fparse['result'] != 'ACK')
2904 return NULL;
2905 $fexpr = $fparse['load'];
2906 }
2907 return filterCellList (listCells ($realm), $fexpr);
2908 default:
c5f84f48 2909 throw new InvalidArgException ('$realm', $realm);
17112e81
DO
2910 }
2911
2912}
39776127 2913
cc3d6915
DO
2914function getVSTOptions()
2915{
2916 $ret = array();
2917 foreach (listCells ('vst') as $vst)
2918 $ret[$vst['id']] = niftyString ($vst['description'], 30, FALSE);
2919 return $ret;
2920}
2921
c7e370ed
DO
2922# Return a 2-dimensional array in the format understood by getNiftySelect(),
2923# which would contain all VLANs of all VLAN domains except domains, which IDs
2924# are present in $except_domains argument.
2925function getAllVLANOptions ($except_domains = array())
52e38709 2926{
c7e370ed
DO
2927 $ret = array();
2928 foreach (getVLANDomainStats() as $domain)
2929 if (! in_array ($domain['id'], $except_domains))
2930 foreach (getDomainVLANs ($domain['id']) as $vlan)
2931 $ret[$domain['description']]["${domain['id']}-${vlan['vlan_id']}"] =
2932 "${vlan['vlan_id']} (${vlan['netc']}) ${vlan['vlan_descr']}";
52e38709
DO
2933 return $ret;
2934}
2935
56a797ef
DO
2936// Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2937function dump ($var)
2938{
2939 echo '<div align=left><pre>';
2940 print_r ($var);
2941 echo '</pre></div>';
2942}
2943
9e51318b
DO
2944function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2945{
2946 global $taglist;
2947 // first build top-N chart...
2948 $toplist = array();
2949 foreach ($taglist as $taginfo)
2950 if (isset ($taginfo['refcnt'][$realm]))
2951 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2952 arsort ($toplist, SORT_NUMERIC);
2953 $ret = array();
2954 $done = 0;
2955 foreach (array_keys ($toplist) as $tag_id)
2956 {
2957 $ret[$tag_id] = $taglist[$tag_id];
2958 if (++$done == $limit)
2959 break;
2960 }
2961 // ...then make sure, that every item of the special list is shown
2962 // (using the same sort order)
2963 $extra = array();
2964 foreach ($special_tags as $taginfo)
2965 if (!array_key_exists ($taginfo['id'], $ret))
2966 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2967 arsort ($extra, SORT_NUMERIC);
2968 foreach (array_keys ($extra) as $tag_id)
2969 $ret[] = $taglist[$tag_id];
2970 return $ret;
2971}
2972
7fa7047a
DO
2973function decodeObjectType ($objtype_id, $style = 'r')
2974{
2975 static $types = array();
2976 if (!count ($types))
2977 $types = array
2978 (
2979 'r' => readChapter (CHAP_OBJTYPE),
2980 'a' => readChapter (CHAP_OBJTYPE, 'a'),
2981 'o' => readChapter (CHAP_OBJTYPE, 'o')
2982 );
2983 return $types[$style][$objtype_id];
2984}
2985
0df8c52b
DO
2986function isolatedPermission ($p, $t, $cell)
2987{
2988 // This function is called from both "file" page and a number of other pages,
2989 // which have already fixed security context and authorized the user for it.
2990 // OTOH, it is necessary here to authorize against the current file, which
2991 // means saving the current context and building a new one.
2992 global
2993 $expl_tags,
2994 $impl_tags,
2995 $target_given_tags,
2996 $auto_tags;
2997 // push current context
2998 $orig_expl_tags = $expl_tags;
2999 $orig_impl_tags = $impl_tags;
3000 $orig_target_given_tags = $target_given_tags;
3001 $orig_auto_tags = $auto_tags;
3002 // retarget
3003 fixContext ($cell);
3004 // remember decision
3005 $ret = permitted ($p, $t);
3006 // pop context
3007 $expl_tags = $orig_expl_tags;
3008 $impl_tags = $orig_impl_tags;
3009 $target_given_tags = $orig_target_given_tags;
3010 $auto_tags = $orig_auto_tags;
3011 return $ret;
3012}
3013
3153a326
DO
3014function getPortListPrefs()
3015{
3016 $ret = array();
3017 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
3018 $ret['iif_pick'] = 1;
3019 $ret['oif_picks'] = array();
3020 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
3021 {
3022 $tmp = explode ('=', trim ($tmp));
3023 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
3024 $ret['oif_picks'][$tmp[0]] = $tmp[1];
3025 }
3026 // enforce default value
3027 if (!array_key_exists (1, $ret['oif_picks']))
3028 $ret['oif_picks'][1] = 24;
3029 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
3030 return $ret;
3031}
3032
2dfa1b73
DO
3033// Return data for printNiftySelect() with port type options. All OIF options
3034// for the default IIF will be shown, but only the default OIFs will be present
3035// for each other IIFs. IIFs, for which there is no default OIF, will not
3036// be listed.
3037// This SELECT will be used for the "add new port" form.
3038function getNewPortTypeOptions()
3039{
3040 $ret = array();
3041 $prefs = getPortListPrefs();
3042 foreach (getPortInterfaceCompat() as $row)
3043 {
3044 if ($row['iif_id'] == $prefs['iif_pick'])
3045 $optgroup = $row['iif_name'];
3046 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
3047 $optgroup = 'other';
3048 else
3049 continue;
3050 if (!array_key_exists ($optgroup, $ret))
3051 $ret[$optgroup] = array();
3052 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
3053 }
3054 return $ret;
3055}
3056
8198f2c6
DO
3057// Return a serialized version of VLAN configuration for a port.
3058// If a native VLAN is defined, print it first. All other VLANs
d0dadd80
DO
3059// are tagged and are listed after a plus sign. When no configuration
3060// is set for a port, return "default" string.
de1e0987 3061function serializeVLANPack ($vlanport)
8198f2c6 3062{
4f860864
DO
3063 if (!array_key_exists ('mode', $vlanport))
3064 return 'error';
3065 switch ($vlanport['mode'])
3066 {
3067 case 'none':
3068 return 'none';
3069 case 'access':
3070 $ret = 'A';
3071 break;
3072 case 'trunk':
3073 $ret = 'T';
3074 break;
36a70b71
DO
3075 case 'uplink':
3076 $ret = 'U';
3077 break;
3078 case 'downlink':
3079 $ret = 'D';
3080 break;
4f860864
DO
3081 default:
3082 return 'error';
3083 }
0ab9ec25
AA
3084 if ($vlanport['native'])
3085 $ret .= $vlanport['native'];
e55917ed
AA
3086 $tagged_bits = groupIntsToRanges ($vlanport['allowed'], $vlanport['native']);
3087 if (count ($tagged_bits))
de1e0987 3088 $ret .= '+' . implode (', ', $tagged_bits);
e55917ed
AA
3089 return strlen ($ret) ? $ret : 'default';
3090}
3091
3092function groupIntsToRanges ($list, $exclude_value = NULL)
3093{
3094 $result = array();
3095 sort ($list);
e9be55de 3096 $id_from = $id_to = 0;
e55917ed
AA
3097 $list[] = -1;
3098 foreach ($list as $next_id)
3099 if (!isset ($exclude_value) or $next_id != $exclude_value)
3100 if ($id_to && $next_id == $id_to + 1)
3101 $id_to = $next_id; // merge
3102 else
e9be55de 3103 {
e55917ed
AA
3104 if ($id_to)
3105 $result[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}"; // flush
3106 $id_from = $id_to = $next_id; // start next pair
e9be55de 3107 }
e55917ed 3108 return $result;
8198f2c6
DO
3109}
3110
8846b060
DO
3111// Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
3112// return the numbers as an array of two.
3113function decodeVLANCK ($string)
3114{
3115 $matches = array();
3116 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
3117 throw new InvalidArgException ('VLAN compound key', $string);
3118 return array ($matches[1], $matches[2]);
3119}
3120
ce85f5c8
DO
3121// Return VLAN name formatted for HTML output (note, that input
3122// argument comes from database unescaped).
a72aa89f
DO
3123function formatVLANName ($vlaninfo, $context = 'markup long')
3124{
3125 switch ($context)
3126 {
3127 case 'option':
3128 $ret = $vlaninfo['vlan_id'];
3129 if ($vlaninfo['vlan_descr'] != '')
3130 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
3131 return $ret;
3132 case 'label':
3133 $ret = $vlaninfo['vlan_id'];
3134 if ($vlaninfo['vlan_descr'] != '')
3135 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
3136 return $ret;
0812b506
DO
3137 case 'plain long':
3138 $ret = 'VLAN' . $vlaninfo['vlan_id'];
3139 if ($vlaninfo['vlan_descr'] != '')
1f54e1ba
DO
3140 $ret .= ' (' . niftyString ($vlaninfo['vlan_descr'], 20, FALSE) . ')';
3141 return $ret;
3142 case 'hyperlink':
3143 $ret = '<a href="';
3144 $ret .= makeHref (array ('page' => 'vlan', 'vlan_ck' => $vlaninfo['domain_id'] . '-' . $vlaninfo['vlan_id']));
3145 $ret .= '">' . formatVLANName ($vlaninfo, 'markup long') . '</a>';
0812b506 3146 return $ret;
a72aa89f
DO
3147 case 'markup long':
3148 default:
3149 $ret = 'VLAN' . $vlaninfo['vlan_id'];
3150 $ret .= ' @' . niftyString ($vlaninfo['domain_descr']);
3151 if ($vlaninfo['vlan_descr'] != '')
3152 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
3153 return $ret;
3154 }
ce85f5c8
DO
3155}
3156
e9d357e1
DO
3157// map interface name
3158function ios12ShortenIfName ($ifname)
3159{
680c6167
AA
3160 if (preg_match ('@^eth-trunk(\d+)$@i', $ifname, $m))
3161 return "Eth-Trunk${m[1]}";
20cd20ad 3162 $ifname = preg_replace ('@^(?:[Ee]thernet|Eth)(.+)$@', 'e\\1', $ifname);
e9d357e1 3163 $ifname = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $ifname);
65e557dd 3164 $ifname = preg_replace ('@^(?:GigabitEthernet|GE)(.+)$@', 'gi\\1', $ifname);
e9d357e1 3165 $ifname = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $ifname);
d5f6bc83 3166 $ifname = preg_replace ('@^port-channel(.+)$@i', 'po\\1', $ifname);
65e557dd 3167 $ifname = preg_replace ('@^(?:XGigabitEthernet|XGE)(.+)$@', 'xg\\1', $ifname);
eb8637f0 3168 $ifname = preg_replace ('@^LongReachEthernet(.+)$@', 'lo\\1', $ifname);
d5f6bc83 3169 $ifname = preg_replace ('@^Management(.+)$@', 'ma\\1', $ifname);
e40e90d0 3170 $ifname = preg_replace ('@^Et(\d.*)$@', 'e\\1', $ifname);
1ebbf889 3171 $ifname = strtolower ($ifname);
e9d357e1
DO
3172 return $ifname;
3173}
3174
f10dd5cc
DO
3175function iosParseVLANString ($string)
3176{
3177 $ret = array();
3178 foreach (explode (',', $string) as $item)
3179 {
3180 $matches = array();
735e323f 3181 $item = trim ($item, ' ');
f10dd5cc
DO
3182 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches))
3183 $ret[] = $matches[1];
3184 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches))
3185 $ret = array_merge ($ret, range ($matches[1], $matches[2]));
3186 }
3187 return $ret;
3188}
3189
bb35bb93
DO
3190// Scan given array and return the key, which addresses the first item
3191// with requested column set to given value (or NULL if there is none such).
19350222
DO
3192// Note that 0 and NULL mean completely different things and thus
3193// require strict checking (=== and !===).
dbc00990
DO
3194function scanArrayForItem ($table, $scan_column, $scan_value)
3195{
3196 foreach ($table as $key => $row)
3197 if ($row[$scan_column] == $scan_value)
3198 return $key;
3199 return NULL;
3200}
3201
66658512
DO
3202// Return TRUE, if every value of A1 is present in A2 and vice versa,
3203// regardless of each array's sort order and indexing.
3204function array_values_same ($a1, $a2)
3205{
3206 return !count (array_diff ($a1, $a2)) and !count (array_diff ($a2, $a1));
3207}
3208
56561657
DO
3209# Reindex provided array of arrays by a column value, which is present in
3210# each sub-array and is assumed to be unique. Most often, make "id" column in
3211# a list of cells into the key space.
3212function reindexById ($input, $column_name = 'id')
3213{
6c0bf066 3214 $ret = array();
56561657
DO
3215 foreach ($input as $item)
3216 {
3217 if (! array_key_exists ($column_name, $item))
3218 throw new InvalidArgException ('input', '(array)', 'ID column missing');
3219 if (array_key_exists ($item[$column_name], $ret))
3220 throw new InvalidArgException ('column_name', $column_name, 'duplicate ID value ' . $item[$column_name]);
3221 $ret[$item[$column_name]] = $item;
3222 }
3223 return $ret;
3224}
3225
1ce258f7
DO
3226// Use the VLAN switch template to set VST role for each port of
3227// the provided list. Return resulting list.
bc254f49 3228function apply8021QOrder ($vst_id, $portlist)
25930440 3229{
cc3d6915
DO
3230 $vst = spotEntity ('vst', $vst_id);
3231 amplifyCell ($vst);
1ce258f7
DO
3232 foreach (array_keys ($portlist) as $port_name)
3233 {
3234 foreach ($vst['rules'] as $rule)
3235 if (preg_match ($rule['port_pcre'], $port_name))
25930440
DO
3236 {
3237 $portlist[$port_name]['vst_role'] = $rule['port_role'];
091768aa 3238 $portlist[$port_name]['wrt_vlans'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']);
1ce258f7 3239 continue 2;
25930440 3240 }
1ce258f7
DO
3241 $portlist[$port_name]['vst_role'] = 'none';
3242 }
25930440
DO
3243 return $portlist;
3244}
3245
091768aa
DO
3246// return a sequence of ranges for given string form and port role
3247function buildVLANFilter ($role, $string)
be28b696 3248{
091768aa 3249 // set base
1ce258f7 3250 switch ($role)
be28b696 3251 {
1ce258f7
DO
3252 case 'access': // 1-4094
3253 $min = VLAN_MIN_ID;
3254 $max = VLAN_MAX_ID;
3255 break;
3256 case 'trunk': // 2-4094
3257 case 'uplink':
65da0c15 3258 case 'downlink':
ec523868 3259 case 'anymode':
1ce258f7
DO
3260 $min = VLAN_MIN_ID + 1;
3261 $max = VLAN_MAX_ID;
3262 break;
65da0c15 3263 default: // none
1ce258f7 3264 return array();
be28b696 3265 }
091768aa
DO
3266 if ($string == '') // fast track
3267 return array (array ('from' => $min, 'to' => $max));
3268 // transform
3269 $vlanidlist = array();
1ce258f7
DO
3270 foreach (iosParseVLANString ($string) as $vlan_id)
3271 if ($min <= $vlan_id and $vlan_id <= $max)
091768aa 3272 $vlanidlist[] = $vlan_id;
cc6a6c4e
DO
3273 return listToRanges ($vlanidlist);
3274}
3275
3276// pack set of integers into list of integer ranges
3277// e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11))
ec523868
DO
3278// The second argument, when it is different from 0, limits amount of
3279// items in each generated range.
3280function listToRanges ($vlanidlist, $limit = 0)
cc6a6c4e 3281{
091768aa
DO
3282 sort ($vlanidlist);
3283 $ret = array();
3284 $from = $to = NULL;
3285 foreach ($vlanidlist as $vlan_id)
3286 if ($from == NULL)
ec523868
DO
3287 {
3288 if ($limit == 1)
3289 $ret[] = array ('from' => $vlan_id, 'to' => $vlan_id);
3290 else
3291 $from = $to = $vlan_id;
3292 }
091768aa 3293 elseif ($to + 1 == $vlan_id)
ec523868 3294 {
091768aa 3295 $to = $vlan_id;
ec523868
DO
3296 if ($to - $from + 1 == $limit)
3297 {
3298 // cut accumulated range and start over
3299 $ret[] = array ('from' => $from, 'to' => $to);
3300 $from = $to = NULL;
3301 }
3302 }
091768aa
DO
3303 else
3304 {
3305 $ret[] = array ('from' => $from, 'to' => $to);
3306 $from = $to = $vlan_id;
3307 }
3308 if ($from != NULL)
3309 $ret[] = array ('from' => $from, 'to' => $to);
be28b696
DO
3310 return $ret;
3311}
3312
091768aa
DO
3313// return TRUE, if given VLAN ID belongs to one of filter's ranges
3314function matchVLANFilter ($vlan_id, $vfilter)
3315{
3316 foreach ($vfilter as $range)
3317 if ($range['from'] <= $vlan_id and $vlan_id <= $range['to'])
3318 return TRUE;
3319 return FALSE;
3320}
3321
651c0804 3322function generate8021QDeployOps ($vswitch, $device_vlanlist, $before, $changes)
bcd14540 3323{
651c0804
AA
3324 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3325 $employed_vlans = getEmployedVlans ($vswitch['object_id'], $domain_vlanlist);
3326
bcd14540
DO
3327 // only ignore VLANs, which exist and are explicitly shown as "alien"
3328 $old_managed_vlans = array();
bcd14540
DO
3329 foreach ($device_vlanlist as $vlan_id)
3330 if
3331 (
3332 !array_key_exists ($vlan_id, $domain_vlanlist) or
3333 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien'
3334 )
3335 $old_managed_vlans[] = $vlan_id;
3336 $ports_to_do = array();
651c0804
AA
3337 $ports_to_do_queue1 = array();
3338 $ports_to_do_queue2 = array();
bcd14540
DO
3339 $after = $before;
3340 foreach ($changes as $port_name => $port)
3341 {
651c0804 3342 $changeset = array
bcd14540
DO
3343 (
3344 'old_mode' => $before[$port_name]['mode'],
3345 'old_allowed' => $before[$port_name]['allowed'],
3346 'old_native' => $before[$port_name]['native'],
3347 'new_mode' => $port['mode'],
3348 'new_allowed' => $port['allowed'],
3349 'new_native' => $port['native'],
3350 );
651c0804
AA
3351 // put the ports with employed vlans first, the others - below them
3352 if (! count (array_intersect ($changeset['old_allowed'], $employed_vlans)))
3353 $ports_to_do_queue2[$port_name] = $changeset;
3354 else
3355 $ports_to_do_queue1[$port_name] = $changeset;
bcd14540
DO
3356 $after[$port_name] = $port;
3357 }
f95bc64f
DO
3358 # Two arrays without common keys get merged with "+" operator just fine,
3359 # with an important difference from array_merge() in that the latter
3360 # renumbers numeric keys, and "+" does not. This matters, when port name
3361 # is a number (like in XOS12 system).
3362 $ports_to_do = $ports_to_do_queue1 + $ports_to_do_queue2;
bcd14540
DO
3363 // New VLAN table is a union of:
3364 // 1. all compulsory VLANs
3365 // 2. all "current" non-alien allowed VLANs of those ports, which are left
3366 // intact (regardless if a VLAN exists in VLAN domain, but looking,
3367 // if it is present in device's own VLAN table)
3368 // 3. all "new" allowed VLANs of those ports, which we do "push" now
3369 // Like for old_managed_vlans, a VLANs is never listed, only if it
3370 // exists and belongs to "alien" type.
3371 $new_managed_vlans = array();
108a028f
AA
3372 // We need to count down the number of ports still using specific vlan
3373 // in order to delete it from device as soon as vlan will be removed from the last port
3374 // This array tracks port count:
3375 // * keys are vlan_id's;
3376 // * values are the number of changed ports which were using this vlan in old configuration
3377 $used_vlans = array();
bcd14540
DO
3378 // 1
3379 foreach ($domain_vlanlist as $vlan_id => $vlan)
3380 if ($vlan['vlan_type'] == 'compulsory')
3381 $new_managed_vlans[] = $vlan_id;
3382 // 2
3383 foreach ($before as $port_name => $port)
3384 if (!array_key_exists ($port_name, $changes))
3385 foreach ($port['allowed'] as $vlan_id)
3386 {
3387 if (in_array ($vlan_id, $new_managed_vlans))
3388 continue;
3389 if
3390 (
3391 array_key_exists ($vlan_id, $domain_vlanlist) and
3392 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien'
3393 )
3394 continue;
3395 if (in_array ($vlan_id, $device_vlanlist))
3396 $new_managed_vlans[] = $vlan_id;
3397 }
108a028f
AA
3398 else
3399 foreach ($port['allowed'] as $vlan_id)
3400 {
3401 if (!array_key_exists ($vlan_id, $used_vlans))
3402 $used_vlans[$vlan_id] = 0;
3403 $used_vlans[$vlan_id]++;
3404 }
bcd14540
DO
3405 // 3
3406 foreach ($changes as $port)
10b6b476 3407 foreach ($port['allowed'] as $vlan_id)
bcd14540
DO
3408 if
3409 (
e405f256 3410 isset ($domain_vlanlist[$vlan_id]) and
bcd14540
DO
3411 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' and
3412 !in_array ($vlan_id, $new_managed_vlans)
3413 )
3414 $new_managed_vlans[] = $vlan_id;
108a028f
AA
3415
3416 $vlans_to_add = array_diff ($new_managed_vlans, $old_managed_vlans);
3417 $vlans_to_del = array_diff ($old_managed_vlans, $new_managed_vlans);
bcd14540 3418 $crq = array();
cb021344
AA
3419
3420 // destroy unused VLANs on device
3421 $deleted_vlans = array_diff ($vlans_to_del, array_keys ($used_vlans));
3422 foreach ($deleted_vlans as $vlan_id)
3423 $crq[] = array
3424 (
3425 'opcode' => 'destroy VLAN',
3426 'arg1' => $vlan_id,
3427 );
3428 $vlans_to_del = array_diff ($vlans_to_del, $deleted_vlans);
108a028f
AA
3429
3430 foreach (sortPortList ($ports_to_do) as $port_name => $port)
3431 {
3432 // Before removing each old VLAN as such it is necessary to unassign
3433 // ports from it (to remove VLAN from each ports' list of "allowed"
3434 // VLANs). This change in turn requires, that a port's "native"
3435 // VLAN isn't set to the one being removed from its "allowed" list.
bcd14540
DO
3436 switch ($port['old_mode'] . '->' . $port['new_mode'])
3437 {
3438 case 'trunk->trunk':
3439 // "old" native is set and differs from the "new" native
3440 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3441 $crq[] = array
3442 (
3443 'opcode' => 'unset native',
3444 'arg1' => $port_name,
3445 'arg2' => $port['old_native'],
3446 );
651c0804
AA
3447 $vlans_to_remove = array_diff ($port['old_allowed'], $port['new_allowed']);
3448 $queues = array();
3449 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first
3450 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards
3451 foreach ($queues as $queue)
3452 if (! empty ($queue))
108a028f 3453 {
651c0804
AA
3454 $crq[] = array
3455 (
3456 'opcode' => 'rem allowed',
3457 'port' => $port_name,
3458 'vlans' => $queue,
3459 );
108a028f
AA
3460 foreach ($queue as $vlan_id)
3461 $used_vlans[$vlan_id]--;
3462 }
bcd14540
DO
3463 break;
3464 case 'access->access':
3465 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
108a028f 3466 {
bcd14540
DO
3467 $crq[] = array
3468 (
3469 'opcode' => 'unset access',
3470 'arg1' => $port_name,
3471 'arg2' => $port['old_native'],
3472 );
108a028f
AA
3473 $used_vlans[$port['old_native']]--;
3474 }
bcd14540
DO
3475 break;
3476 case 'access->trunk':
3477 $crq[] = array
3478 (
3479 'opcode' => 'unset access',
3480 'arg1' => $port_name,
3481 'arg2' => $port['old_native'],
3482 );
108a028f 3483 $used_vlans[$port['old_native']]--;
bcd14540
DO
3484 break;
3485 case 'trunk->access':
453c8fb8
DO
3486 if ($port['old_native'])
3487 $crq[] = array
3488 (
3489 'opcode' => 'unset native',
3490 'arg1' => $port_name,
3491 'arg2' => $port['old_native'],
3492 );
651c0804
AA
3493 $vlans_to_remove = $port['old_allowed'];
3494 $queues = array();
3495 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first
3496 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards
3497 foreach ($queues as $queue)
3498 if (! empty ($queue))
108a028f 3499 {
651c0804
AA
3500 $crq[] = array
3501 (
3502 'opcode' => 'rem allowed',
3503 'port' => $port_name,
3504 'vlans' => $queue,
3505 );
108a028f
AA
3506 foreach ($queue as $vlan_id)
3507 $used_vlans[$vlan_id]--;
3508 }
bcd14540
DO
3509 break;
3510 default:
164ba494 3511 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
bcd14540 3512 }
108a028f
AA
3513
3514 // destroy unneeded VLANs on device
3515 $deleted_vlans = array();
3516 foreach ($vlans_to_del as $vlan_id)
3517 if ($used_vlans[$vlan_id] == 0)
3518 {
3519 $crq[] = array
3520 (
3521 'opcode' => 'destroy VLAN',
3522 'arg1' => $vlan_id,
3523 );
108a028f
AA
3524 $deleted_vlans[] = $vlan_id;
3525 }
3526 $vlans_to_del = array_diff ($vlans_to_del, $deleted_vlans);
3527
3528 // create new VLANs on device
3529 $added_vlans = array_intersect ($vlans_to_add, $port['new_allowed']);
3530 foreach ($added_vlans as $vlan_id)
3531 $crq[] = array
3532 (
3533 'opcode' => 'create VLAN',
3534 'arg1' => $vlan_id,
3535 );
3536 $vlans_to_add = array_diff ($vlans_to_add, $added_vlans);
3537
3538 // change port mode if needed
3539 if ($port['old_mode'] != $port['new_mode'])
3540 $crq[] = array
3541 (
3542 'opcode' => 'set mode',
3543 'arg1' => $port_name,
3544 'arg2' => $port['new_mode'],
3545 );
3546
3547 // Now, when all new VLANs are created (queued), it is safe to assign (queue)
3548 // ports to the new VLANs.
bcd14540
DO
3549 switch ($port['old_mode'] . '->' . $port['new_mode'])
3550 {
3551 case 'trunk->trunk':
3552 // For each allowed VLAN, which is present on the "new" list and missing from
3553 // the "old" one, queue a command to assign current port to that VLAN.
cc6a6c4e 3554 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed'])))
bcd14540
DO
3555 $crq[] = array
3556 (
3557 'opcode' => 'add allowed',
cc6a6c4e
DO
3558 'port' => $port_name,
3559 'vlans' => $tmp,
bcd14540
DO
3560 );
3561 // One of the "allowed" VLANs for this port may probably be "native".
3562 // "new native" is set and differs from "old native"
3563 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3564 $crq[] = array
3565 (
3566 'opcode' => 'set native',
3567 'arg1' => $port_name,
3568 'arg2' => $port['new_native'],
3569 );
3570 break;
3571 case 'access->access':
3572 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3573 $crq[] = array
3574 (
3575 'opcode' => 'set access',
3576 'arg1' => $port_name,
3577 'arg2' => $port['new_native'],
3578 );
3579 break;
3580 case 'access->trunk':
cc6a6c4e 3581 if (count ($port['new_allowed']))
bcd14540
DO
3582 $crq[] = array
3583 (
3584 'opcode' => 'add allowed',
cc6a6c4e
DO
3585 'port' => $port_name,
3586 'vlans' => $port['new_allowed'],
bcd14540 3587 );
453c8fb8
DO
3588 if ($port['new_native'])
3589 $crq[] = array
3590 (
3591 'opcode' => 'set native',
3592 'arg1' => $port_name,
3593 'arg2' => $port['new_native'],
3594 );
bcd14540
DO
3595 break;
3596 case 'trunk->access':
bcd14540
DO
3597 $crq[] = array
3598 (
3599 'opcode' => 'set access',
3600 'arg1' => $port_name,
3601 'arg2' => $port['new_native'],
3602 );
3603 break;
3604 default:
164ba494 3605 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
bcd14540 3606 }
108a028f 3607 }
cb021344
AA
3608
3609 // add the rest of VLANs to device (compulsory VLANs)
108a028f
AA
3610 foreach ($vlans_to_add as $vlan_id)
3611 $crq[] = array
3612 (
3613 'opcode' => 'create VLAN',
3614 'arg1' => $vlan_id,
3615 );
3616
0b24ab60
AA
3617 return $crq;
3618}
3619
3620function exportSwitch8021QConfig
3621(
3622 $vswitch,
3623 $device_vlanlist,
3624 $before,
8d6c3668
AA
3625 $changes,
3626 $vlan_names
0b24ab60
AA
3627)
3628{
651c0804 3629 $crq = generate8021QDeployOps ($vswitch, $device_vlanlist, $before, $changes);
5017142e
DO
3630 if (count ($crq))
3631 {
3632 array_unshift ($crq, array ('opcode' => 'begin configuration'));
3633 $crq[] = array ('opcode' => 'end configuration');
3634 if (considerConfiguredConstraint (spotEntity ('object', $vswitch['object_id']), '8021Q_WRI_AFTER_CONFT_LISTSRC'))
3635 $crq[] = array ('opcode' => 'save configuration');
8d6c3668 3636 setDevice8021QConfig ($vswitch['object_id'], $crq, $vlan_names);
5017142e 3637 }
bcd14540
DO
3638 return count ($crq);
3639}
3640
af204724 3641// filter list of changed ports to cancel changes forbidden by VST and domain
af204724
DO
3642function filter8021QChangeRequests
3643(
3644 $domain_vlanlist,
3645 $before, // current saved configuration of all ports
3646 $changes // changed ports with VST markup
3647)
3648{
3649 $domain_immune_vlans = array();
3650 foreach ($domain_vlanlist as $vlan_id => $vlan)
3651 if ($vlan['vlan_type'] == 'alien')
3652 $domain_immune_vlans[] = $vlan_id;
3653 $ret = array();
3654 foreach ($changes as $port_name => $port)
3655 {
ec523868
DO
3656 // VST violation ?
3657 if (!goodModeForVSTRole ($port['mode'], $port['vst_role']))
3658 continue; // ignore change request
af204724 3659 // find and cancel any changes regarding immune VLANs
ec523868 3660 switch ($port['mode'])
034a61c9
DO
3661 {
3662 case 'access':
034a61c9 3663 foreach ($domain_immune_vlans as $immune)
af204724
DO
3664 // Reverting an attempt to set an access port from
3665 // "normal" VLAN to immune one (or vice versa) requires
3666 // special handling, becase the calling function has
3667 // discarded the old contents of 'allowed' for current port.
af204724
DO
3668 if
3669 (
3670 $before[$port_name]['native'] == $immune or
3671 $port['native'] == $immune
3672 )
3673 {
3674 $port['native'] = $before[$port_name]['native'];
3675 $port['allowed'] = array ($port['native']);
034a61c9
DO
3676 // Such reversal happens either once or never for an
3677 // access port.
3678 break;
af204724 3679 }
034a61c9
DO
3680 break;
3681 case 'trunk':
034a61c9