use getBypassValue() in trigger functions
[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
66b9b2a6
AA
1298// removes implicit tags from ['etags'] array and fills ['itags'] array
1299// Replaces call sequence "getExplicitTagsOnly, getImplicitTags"
1300function sortEntityTags (&$cell)
1301{
1302 global $taglist;
1303 if (! is_array ($cell['etags']))
1304 throw new InvalidArgException ('$cell[etags]', $cell['etags']);
1305 $cell['itags'] = array();
1306 foreach ($cell['etags'] as $tag_id => $taginfo)
1307 foreach ($taglist[$tag_id]['trace'] as $parent_id)
1308 {
1309 $cell['itags'][$parent_id] = $taglist[$parent_id];
1310 unset ($cell['etags'][$parent_id]);
1311 }
1312}
1313
ba93bd98
DO
1314// Return the list of missing implicit tags.
1315function getImplicitTags ($oldtags)
1316{
3fb336f6
DO
1317 global $taglist;
1318 $tmp = array();
1319 foreach ($oldtags as $taginfo)
1320 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']);
1321 // don't call array_unique here, it is in the function we will call now
1322 return buildTagChainFromIds ($tmp);
ba93bd98
DO
1323}
1324
6e49bd1f 1325// Minimize the chain: exclude all implicit tags and return the result.
5fd2a004
DO
1326// This function makes use of an external cache with a miss/hit ratio
1327// about 3/7 (ticket:255).
3fb336f6 1328function getExplicitTagsOnly ($chain)
ab379543 1329{
5fd2a004 1330 global $taglist, $tagRelCache;
ab379543 1331 $ret = array();
5fd2a004
DO
1332 foreach (array_keys ($chain) as $keyA) // check each A
1333 {
1334 $tagidA = $chain[$keyA]['id'];
1335 // do not include A in result, if A is seen on the trace of any B!=A
1336 foreach (array_keys ($chain) as $keyB)
1337 {
1338 $tagidB = $chain[$keyB]['id'];
1339 if ($tagidA == $tagidB)
1340 continue;
1341 if (!isset ($tagRelCache[$tagidA][$tagidB]))
0745db4d 1342 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']);
5fd2a004
DO
1343 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B
1344 continue 2; // skip this A
1345 }
1346 $ret[] = $chain[$keyA];
1347 }
74ccacff
DO
1348 return $ret;
1349}
1350
abef7149
DO
1351// Check, if the given tag is present on the chain (will only work
1352// for regular tags with tag ID set.
1353function tagOnChain ($taginfo, $tagchain)
1354{
1355 if (!isset ($taginfo['id']))
1356 return FALSE;
1357 foreach ($tagchain as $test)
1358 if ($test['id'] == $taginfo['id'])
1359 return TRUE;
1360 return FALSE;
1361}
1362
f8821b96
DO
1363function tagNameOnChain ($tagname, $tagchain)
1364{
1365 foreach ($tagchain as $test)
1366 if ($test['tag'] == $tagname)
1367 return TRUE;
1368 return FALSE;
1369}
1370
abef7149
DO
1371// Return TRUE, if two tags chains differ (order of tags doesn't matter).
1372// Assume, that neither of the lists contains duplicates.
1373// FIXME: a faster, than O(x^2) method is possible for this calculation.
1374function tagChainCmp ($chain1, $chain2)
1375{
1376 if (count ($chain1) != count ($chain2))
1377 return TRUE;
1378 foreach ($chain1 as $taginfo1)
1379 if (!tagOnChain ($taginfo1, $chain2))
1380 return TRUE;
1381 return FALSE;
1382}
1383
0df8c52b 1384function redirectIfNecessary ()
da958e52 1385{
750d26d2
DO
1386 global
1387 $trigger,
1388 $pageno,
1389 $tabno;
a2b16202 1390 @session_start();
fa5b2764
AA
1391 if
1392 (
1393 ! isset ($_REQUEST['tab']) and
1394 isset ($_SESSION['RTLT'][$pageno]) and
1395 getConfigVar ('SHOW_LAST_TAB') == 'yes' and
1396 permitted ($pageno, $_SESSION['RTLT'][$pageno]['tabname']) and
1397 time() - $_SESSION['RTLT'][$pageno]['time'] <= TAB_REMEMBER_TIMEOUT
1398 )
a2b16202 1399 redirectUser (buildRedirectURL ($pageno, $_SESSION['RTLT'][$pageno]['tabname']));
fa5b2764 1400
750d26d2 1401 // check if we accidentaly got on a dynamic tab that shouldn't be shown for this object
3a7bdcc6
DO
1402 if
1403 (
1404 isset ($trigger[$pageno][$tabno]) and
1405 !strlen (call_user_func ($trigger[$pageno][$tabno]))
1406 )
fa5b2764
AA
1407 {
1408 $_SESSION['RTLT'][$pageno]['dont_remember'] = 1;
a2b16202 1409 redirectUser (buildRedirectURL ($pageno, 'default'));
fa5b2764 1410 }
bfb77a1f 1411 if (is_array (@$_SESSION['RTLT'][$pageno]) && isset ($_SESSION['RTLT'][$pageno]['dont_remember']))
a2b16202
AA
1412 unset ($_SESSION['RTLT'][$pageno]['dont_remember']);
1413 // store the last visited tab name
1414 if (isset ($_REQUEST['tab']))
1415 $_SESSION['RTLT'][$pageno] = array ('tabname' => $tabno, 'time' => time());
1416 session_commit(); // if we are continuing to run, unlock session data
0df8c52b
DO
1417}
1418
0c714908
DO
1419function prepareNavigation()
1420{
329ec966
DY
1421 global
1422 $pageno,
1423 $tabno;
329ec966
DY
1424 $pageno = (isset ($_REQUEST['page'])) ? $_REQUEST['page'] : 'index';
1425
0c714908 1426 if (isset ($_REQUEST['tab']))
329ec966 1427 $tabno = $_REQUEST['tab'];
0c714908 1428 else
329ec966 1429 $tabno = 'default';
329ec966
DY
1430}
1431
0df8c52b
DO
1432function fixContext ($target = NULL)
1433{
1434 global
1435 $pageno,
1436 $auto_tags,
1437 $expl_tags,
1438 $impl_tags,
1439 $target_given_tags,
1440 $user_given_tags,
1441 $etype_by_pageno,
1442 $page;
53bae67b 1443
0df8c52b
DO
1444 if ($target !== NULL)
1445 {
1446 $target_given_tags = $target['etags'];
1447 // Don't reset autochain, because auth procedures could push stuff there in.
1448 // Another important point is to ignore 'user' realm, so we don't infuse effective
1449 // context with autotags of the displayed account.
1450 if ($target['realm'] != 'user')
1451 $auto_tags = array_merge ($auto_tags, $target['atags']);
1452 }
1453 elseif (array_key_exists ($pageno, $etype_by_pageno))
53da7dca
DO
1454 {
1455 // Each page listed in the map above requires one uint argument.
0a50efb4 1456 $target_realm = $etype_by_pageno[$pageno];
0cc24e9a 1457 assertUIntArg ($page[$pageno]['bypass']);
0a50efb4 1458 $target_id = $_REQUEST[$page[$pageno]['bypass']];
b135a49d 1459 $target = spotEntity ($target_realm, $target_id);
53da7dca 1460 $target_given_tags = $target['etags'];
0df8c52b 1461 if ($target['realm'] != 'user')
53da7dca
DO
1462 $auto_tags = array_merge ($auto_tags, $target['atags']);
1463 }
4c9b513a
DO
1464 // Explicit and implicit chains should be normally empty at this point, so
1465 // overwrite the contents anyway.
1466 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1467 $impl_tags = getImplicitTags ($expl_tags);
da958e52
DO
1468}
1469
75a01117
DO
1470# Merge e/i/a-tags of the given cell structures into current context, when
1471# these aren't there yet.
1472function spreadContext ($extracell)
1473{
1474 global
1475 $auto_tags,
1476 $expl_tags,
1477 $impl_tags,
1478 $target_given_tags,
1479 $user_given_tags;
1480 foreach ($extracell['atags'] as $taginfo)
1481 if (! tagNameOnChain ($taginfo['tag'], $auto_tags))
1482 $auto_tags[] = $taginfo;
1483 $target_given_tags = mergeTagChains ($target_given_tags, $extracell['etags']);
1484 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1485 $impl_tags = getImplicitTags ($expl_tags);
1486}
1487
1488# return a structure suitable for feeding into restoreContext()
1489function getContext()
1490{
1491 global
1492 $auto_tags,
1493 $expl_tags,
1494 $impl_tags,
1495 $target_given_tags;
1496 return array
1497 (
1498 'auto_tags' => $auto_tags,
1499 'expl_tags' => $expl_tags,
1500 'impl_tags' => $impl_tags,
1501 'target_given_tags' => $target_given_tags,
1502 );
1503}
1504
1505function restoreContext ($ctx)
1506{
1507 global
1508 $auto_tags,
1509 $expl_tags,
1510 $impl_tags,
1511 $target_given_tags;
1512 $auto_tags = $ctx['auto_tags'];
1513 $expl_tags = $ctx['expl_tags'];
1514 $impl_tags = $ctx['impl_tags'];
1515 $target_given_tags = $ctx['target_given_tags'];
1516}
1517
abef7149
DO
1518// Take a list of user-supplied tag IDs to build a list of valid taginfo
1519// records indexed by tag IDs (tag chain).
6e49bd1f 1520function buildTagChainFromIds ($tagidlist)
ab379543 1521{
20c901a7 1522 global $taglist;
ab379543 1523 $ret = array();
abef7149 1524 foreach (array_unique ($tagidlist) as $tag_id)
ab379543
DO
1525 if (isset ($taglist[$tag_id]))
1526 $ret[] = $taglist[$tag_id];
1527 return $ret;
1528}
1529
e3b4b089
AA
1530function buildTagIdsFromChain ($tagchain)
1531{
1532 $ret = array();
1533 foreach ($tagchain as $taginfo)
1534 $ret[] = $taginfo['id'];
1535 return array_unique ($ret);
1536}
1537
7cc02fc1
DO
1538// Process a given tag tree and return only meaningful branches. The resulting
1539// (sub)tree will have refcnt leaves on every last branch.
a26a6ccc 1540function getObjectiveTagTree ($tree, $realm, $preselect)
7cc02fc1 1541{
51b6651a 1542 $self = __FUNCTION__;
7cc02fc1
DO
1543 $ret = array();
1544 foreach ($tree as $taginfo)
1545 {
a26a6ccc
DO
1546 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1547 // If the current node addresses something, add it to the result
1548 // regardless of how many sub-nodes it features.
1549 if
7cc02fc1 1550 (
a26a6ccc
DO
1551 isset ($taginfo['refcnt'][$realm]) or
1552 count ($subsearch) > 1 or
3fb336f6 1553 in_array ($taginfo['id'], $preselect)
a26a6ccc
DO
1554 )
1555 $ret[] = array
1556 (
1557 'id' => $taginfo['id'],
1558 'tag' => $taginfo['tag'],
1559 'parent_id' => $taginfo['parent_id'],
1560 'refcnt' => $taginfo['refcnt'],
1561 'kids' => $subsearch
1562 );
1563 else
1564 $ret = array_merge ($ret, $subsearch);
7cc02fc1
DO
1565 }
1566 return $ret;
1567}
1568
95857b5c
DO
1569// Preprocess tag tree to get only tags which can effectively reduce given filter result,
1570// than passes shrinked tag tree to getObjectiveTagTree and return its result.
21ee3351 1571// This makes sense only if andor mode is 'and', otherwise function does not modify tree.
95857b5c
DO
1572// 'Given filter' is a pair of $entity_list(filter result) and $preselect(filter data).
1573// 'Effectively' means reduce to non-empty result.
1574function getShrinkedTagTree($entity_list, $realm, $preselect) {
1575 global $tagtree;
3dcf526a 1576 if ($preselect['andor'] != 'and' || empty($entity_list) && $preselect['is_empty'])
95857b5c
DO
1577 return getObjectiveTagTree($tagtree, $realm, $preselect['tagidlist']);
1578
1579 $used_tags = array(); //associative, keys - tag ids, values - taginfos
1580 foreach ($entity_list as $entity)
1581 {
1582 foreach ($entity['etags'] as $etag)
1583 if (! array_key_exists($etag['id'], $used_tags))
1584 $used_tags[$etag['id']] = 1;
1585 else
1586 $used_tags[$etag['id']]++;
1587
1588 foreach ($entity['itags'] as $itag)
1589 if (! array_key_exists($itag['id'], $used_tags))
1590 $used_tags[$itag['id']] = 0;
1591 }
1592
1593 $shrinked_tree = shrinkSubtree($tagtree, $used_tags, $preselect, $realm);
1594 return getObjectiveTagTree($shrinked_tree, $realm, $preselect['tagidlist']);
1595}
1596
1597// deletes item from tag subtree unless it exists in $used_tags and not preselected
1598function shrinkSubtree($tree, $used_tags, $preselect, $realm) {
1599 $self = __FUNCTION__;
1600
1601 foreach($tree as $i => &$item) {
1602 $item['kids'] = $self($item['kids'], $used_tags, $preselect, $realm);
1603 $item['kidc'] = count($item['kids']);
1604 if
1605 (
1606 ! array_key_exists($item['id'], $used_tags) &&
1607 ! in_array($item['id'], $preselect['tagidlist']) &&
1608 ! $item['kidc']
1609 )
1610 unset($tree[$i]);
1611 else {
444953cc
AA
1612 if (isset ($used_tags[$item['id']]) && $used_tags[$item['id']])
1613 $item['refcnt'][$realm] = $used_tags[$item['id']];
1614 else
95857b5c
DO
1615 unset($item['refcnt'][$realm]);
1616 }
1617 }
1618 return $tree;
1619}
1620
7ddb2c05
DO
1621// Get taginfo record by tag name, return NULL, if record doesn't exist.
1622function getTagByName ($target_name)
1623{
1624 global $taglist;
1625 foreach ($taglist as $taginfo)
1626 if ($taginfo['tag'] == $target_name)
1627 return $taginfo;
1628 return NULL;
1629}
1630
fc73c734
DO
1631// Merge two chains, filtering dupes out. Return the resulting superset.
1632function mergeTagChains ($chainA, $chainB)
1633{
1634 // $ret = $chainA;
1635 // Reindex by tag id in any case.
1636 $ret = array();
1637 foreach ($chainA as $tag)
1638 $ret[$tag['id']] = $tag;
1639 foreach ($chainB as $tag)
1640 if (!isset ($ret[$tag['id']]))
1641 $ret[$tag['id']] = $tag;
1642 return $ret;
1643}
1644
201204fb
DO
1645# Return a list consisting of tag ID of the given tree node and IDs of all
1646# nodes it contains.
1647function getTagIDListForNode ($treenode)
1648{
1649 $self = __FUNCTION__;
1650 $ret = array ($treenode['id']);
1651 foreach ($treenode['kids'] as $item)
1652 $ret = array_merge ($ret, $self ($item));
1653 return $ret;
1654}
1655
31c941ec
DO
1656function getCellFilter ()
1657{
7da7450c 1658 global $sic;
eca0114c 1659 global $pageno;
3dcf526a 1660 $andor_used = FALSE;
a2b16202 1661 @session_start();
444953cc 1662 // 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 1663 if (isset($_REQUEST['andor']))
3dcf526a 1664 $andor_used = TRUE;
4318ced5 1665 if ($andor_used || array_key_exists ('clear-cf', $_REQUEST))
444953cc 1666 unset($_SESSION[$pageno]); // delete saved filter
4318ced5 1667
444953cc
AA
1668 // otherwise inject saved filter to the $_REQUEST and $sic vars
1669 elseif (isset ($_SESSION[$pageno]['filter']) and is_array ($_SESSION[$pageno]['filter']) and getConfigVar ('STATIC_FILTER') == 'yes')
1670 foreach (array('andor', 'cfe', 'cft[]', 'cfp[]', 'nft[]', 'nfp[]') as $param)
1671 {
1672 $param = str_replace ('[]', '', $param, $is_array);
1673 if (! isset ($_REQUEST[$param]) and isset ($_SESSION[$pageno]['filter'][$param]) and (!$is_array or is_array ($_SESSION[$pageno]['filter'][$param])))
1674 {
1675 $_REQUEST[$param] = $_SESSION[$pageno]['filter'][$param];
1676 if (! $is_array)
1677 $sic[$param] = $_REQUEST[$param];
1678 }
1679 }
eca0114c 1680
3d670bba
DO
1681 $ret = array
1682 (
23cdc7e9
DO
1683 'tagidlist' => array(),
1684 'tnamelist' => array(),
1685 'pnamelist' => array(),
444953cc 1686 'negatedlist' => array(),
23cdc7e9 1687 'andor' => '',
3d670bba 1688 'text' => '',
23cdc7e9
DO
1689 'extratext' => '',
1690 'expression' => array(),
a8efc03e 1691 'urlextra' => '', // Just put text here and let makeHref call urlencode().
3dcf526a 1692 'is_empty' => TRUE,
3d670bba 1693 );
23cdc7e9 1694 switch (TRUE)
31c941ec 1695 {
23cdc7e9 1696 case (!isset ($_REQUEST['andor'])):
444953cc 1697 $andor = getConfigVar ('FILTER_DEFAULT_ANDOR');
3d670bba 1698 break;
23cdc7e9
DO
1699 case ($_REQUEST['andor'] == 'and'):
1700 case ($_REQUEST['andor'] == 'or'):
444953cc
AA
1701 $_SESSION[$pageno]['filter']['andor'] = $_REQUEST['andor'];
1702 $ret['andor'] = $andor = $_REQUEST['andor'];
3d670bba
DO
1703 break;
1704 default:
08408472 1705 showWarning ('Invalid and/or switch value in submitted form');
3d670bba 1706 return NULL;
31c941ec 1707 }
23cdc7e9
DO
1708 // Both tags and predicates, which don't exist, should be
1709 // handled somehow. Discard them silently for now.
444953cc
AA
1710 global $taglist, $pTable;
1711 foreach (array ('cft', 'cfp', 'nft', 'nfp') as $param)
1712 if (isset ($_REQUEST[$param]) and is_array ($_REQUEST[$param]))
1713 {
1714 $_SESSION[$pageno]['filter'][$param] = $_REQUEST[$param];
1715 foreach ($_REQUEST[$param] as $req_key)
23cdc7e9 1716 {
444953cc
AA
1717 if (strpos ($param, 'ft') !== FALSE)
1718 {
1719 // param is a taglist
1720 if (! isset ($taglist[$req_key]))
1721 continue;
1722 $ret['tagidlist'][] = $req_key;
1723 $ret['tnamelist'][] = $taglist[$req_key]['tag'];
1724 $text = '{' . $taglist[$req_key]['tag'] . '}';
1725 }
1726 else
1727 {
1728 // param is a predicate list
1729 if (! isset ($pTable[$req_key]))
1730 continue;
1731 $ret['pnamelist'][] = $req_key;
1732 $text = '[' . $req_key . ']';
1733 }
1734 if (strpos ($param, 'nf') === 0)
1735 {
1736 $text = "not $text";
1737 $ret['negatedlist'][] = $req_key;
1738 }
1739 if (! empty ($ret['text']))
1740 {
1741 $andor_used = TRUE;
1742 $ret['text'] .= " $andor ";
1743 }
1744 $ret['text'] .= $text;
1745 $ret['urlextra'] .= '&' . $param . '[]=' . $req_key;
23cdc7e9 1746 }
444953cc 1747 }
7da7450c
DO
1748 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1749 if (isset ($sic['cfe']))
a8efc03e 1750 {
444953cc 1751 $_SESSION[$pageno]['filter']['cfe'] = $sic['cfe'];
7da7450c
DO
1752 // Only consider extra text, when it is a correct RackCode expression.
1753 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1754 if ($parse['result'] == 'ACK')
1755 {
1756 $ret['extratext'] = trim ($sic['cfe']);
1757 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1758 }
a8efc03e 1759 }
23cdc7e9
DO
1760 $finaltext = array();
1761 if (strlen ($ret['text']))
1762 $finaltext[] = '(' . $ret['text'] . ')';
1763 if (strlen ($ret['extratext']))
1764 $finaltext[] = '(' . $ret['extratext'] . ')';
3dcf526a 1765 $andor_used = $andor_used || (count($finaltext) > 1);
444953cc 1766 $finaltext = implode (' ' . $andor . ' ', $finaltext);
23cdc7e9
DO
1767 if (strlen ($finaltext))
1768 {
3dcf526a 1769 $ret['is_empty'] = FALSE;
23cdc7e9
DO
1770 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1771 $ret['expression'] = $parse['result'] == 'ACK' ? $parse['load'] : NULL;
8c699525
DO
1772 // It's not quite fair enough to put the blame of the whole text onto
1773 // non-empty "extra" portion of it, but it's the only user-generated portion
1774 // of it, thus the most probable cause of parse error.
1775 if (strlen ($ret['extratext']))
1776 $ret['extraclass'] = $parse['result'] == 'ACK' ? 'validation-success' : 'validation-error';
31c941ec 1777 }
3dcf526a
DO
1778 if (! $andor_used)
1779 $ret['andor'] = getConfigVar ('FILTER_DEFAULT_ANDOR');
1780 else
1781 $ret['urlextra'] .= '&andor=' . $ret['andor'];
3d670bba 1782 return $ret;
31c941ec
DO
1783}
1784
a2b16202 1785function buildRedirectURL ($nextpage = NULL, $nexttab = NULL, $moreArgs = array())
53bae67b 1786{
a2b16202
AA
1787 global $page, $pageno, $tabno;
1788 if ($nextpage === NULL)
1789 $nextpage = $pageno;
1790 if ($nexttab === NULL)
1791 $nexttab = $tabno;
1792 $url = "index.php?page=${nextpage}&tab=${nexttab}";
1793 if (isset ($page[$nextpage]['bypass']))
1794 $url .= '&' . $page[$nextpage]['bypass'] . '=' . $_REQUEST[$page[$nextpage]['bypass']];
1795 if (isset ($page[$nextpage]['bypass_tabs']))
1796 foreach ($page[$nextpage]['bypass_tabs'] as $param_name)
fa5b2764 1797 if (isset ($_REQUEST[$param_name]))
a2b16202
AA
1798 $url .= '&' . urlencode ($param_name) . '=' . urlencode ($_REQUEST[$param_name]);
1799
1800 if (count ($moreArgs) > 0)
1801 foreach ($moreArgs as $arg => $value)
1802 if (is_array ($value))
1803 foreach ($value as $v)
1804 $url .= '&' . urlencode ($arg . '[]') . '=' . urlencode ($v);
1805 elseif ($arg != 'module')
1806 $url .= '&' . urlencode ($arg) . '=' . urlencode ($value);
1807 return $url;
1808}
1809
3efb4742
AA
1810// store the accumulated message list into he $SESSION array to display them later
1811function backupLogMessages()
a2b16202
AA
1812{
1813 global $log_messages;
1814 if (! empty ($log_messages))
1815 {
1816 @session_start();
1817 $_SESSION['log'] = $log_messages;
1818 }
3efb4742
AA
1819}
1820
1821function redirectUser ($url)
1822{
1823 backupLogMessages();
a2b16202 1824 header ("Location: " . $url);
53bae67b
DO
1825 die;
1826}
1827
9f3e5caa
DO
1828function getRackCodeStats ()
1829{
1830 global $rackCode;
914b439b 1831 $defc = $grantc = $modc = 0;
9f3e5caa
DO
1832 foreach ($rackCode as $s)
1833 switch ($s['type'])
1834 {
1835 case 'SYNT_DEFINITION':
1836 $defc++;
1837 break;
1838 case 'SYNT_GRANT':
1839 $grantc++;
1840 break;
914b439b
DO
1841 case 'SYNT_CTXMOD':
1842 $modc++;
1843 break;
9f3e5caa
DO
1844 default:
1845 break;
1846 }
914b439b
DO
1847 $ret = array
1848 (
1849 'Definition sentences' => $defc,
1850 'Grant sentences' => $grantc,
1851 'Context mod sentences' => $modc
1852 );
9f3e5caa
DO
1853 return $ret;
1854}
1855
d5157018
DO
1856function getRackImageWidth ()
1857{
529eac25
DO
1858 global $rtwidth;
1859 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
d5157018
DO
1860}
1861
1862function getRackImageHeight ($units)
1863{
1864 return 3 + 3 + $units * 2;
1865}
1866
2d318652 1867// Indicate occupation state of each IP address: none, ordinary or problematic.
21ee3351 1868function markupIPAddrList (&$addrlist)
2d318652
DO
1869{
1870 foreach (array_keys ($addrlist) as $ip_bin)
1871 {
d983f70a
DO
1872 $refc = array
1873 (
00c5d8cb
DO
1874 'shared' => 0, // virtual
1875 'virtual' => 0, // loopback
1876 'regular' => 0, // connected host
1877 'router' => 0 // connected gateway
d983f70a
DO
1878 );
1879 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1880 $refc[$a['type']]++;
79ddc250 1881 $nvirtloopback = ($refc['shared'] + $refc['virtual'] + $refc['router'] > 0) ? 1 : 0; // modulus of virtual + shared + router
d983f70a 1882 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
79ddc250
AD
1883 $nrealms = $nreserved + $nvirtloopback + $refc['regular']; // last is connected allocation
1884
2d318652
DO
1885 if ($nrealms == 1)
1886 $addrlist[$ip_bin]['class'] = 'trbusy';
1887 elseif ($nrealms > 1)
1888 $addrlist[$ip_bin]['class'] = 'trerror';
c8020acc 1889 elseif (! empty ($addrlist[$ip_bin]['vslist']) or ! empty ($addrlist[$ip_bin]['rsplist']))
212b565a 1890 $addrlist[$ip_bin]['class'] = 'trbusy';
2d318652
DO
1891 else
1892 $addrlist[$ip_bin]['class'] = '';
1893 }
1894}
1895
21ee3351 1896// Scan the given address list (returned by scanIPv4Space/scanIPv6Space) and return a list of all routers found.
04d619d0
DO
1897function findRouters ($addrlist)
1898{
1899 $ret = array();
1900 foreach ($addrlist as $addr)
1901 foreach ($addr['allocs'] as $alloc)
1902 if ($alloc['type'] == 'router')
1903 $ret[] = array
1904 (
1905 'id' => $alloc['object_id'],
1906 'iface' => $alloc['name'],
1907 'dname' => $alloc['object_name'],
4318ced5 1908 'ip_bin' => $addr['ip_bin'],
04d619d0
DO
1909 );
1910 return $ret;
1911}
1912
1327d9dd
DO
1913// Compare networks. When sorting a tree, the records on the list will have
1914// distinct base IP addresses.
1752c495
AA
1915// valid return values are: 1, 0, -1, -2
1916// -2 has special meaning: $netA includes $netB
3444ecf2
DO
1917// "The comparison function must return an integer less than, equal to, or greater
1918// than zero if the first argument is considered to be respectively less than,
1919// equal to, or greater than the second." (c) PHP manual
4318ced5
AA
1920function IPNetworkCmp ($netA, $netB)
1921{
c09cdace
AA
1922 if (strlen ($netA['ip_bin']) !== strlen ($netB['ip_bin']))
1923 return strlen ($netA['ip_bin']) < strlen ($netB['ip_bin']) ? -1 : 1;
4318ced5 1924 $ret = strcmp ($netA['ip_bin'], $netB['ip_bin']);
1752c495 1925 $ret = ($ret > 0 ? 1 : ($ret < 0 ? -1 : 0));
4318ced5
AA
1926 if ($ret == 0)
1927 $ret = $netA['mask'] < $netB['mask'] ? -1 : ($netA['mask'] > $netB['mask'] ? 1 : 0);
1928 if ($ret == -1 and $netA['ip_bin'] === ($netB['ip_bin'] & $netA['mask_bin']))
1929 $ret = -2;
1930 return $ret;
1327d9dd
DO
1931}
1932
4318ced5 1933function IPNetContainsOrEqual ($netA, $netB)
21ee3351 1934{
4318ced5
AA
1935 $res = IPNetworkCmp ($netA, $netB);
1936 return ($res == -2 || $res == 0);
1937}
1938
1939function IPNetContains ($netA, $netB)
1940{
1941 return (-2 == IPNetworkCmp ($netA, $netB));
21ee3351
AA
1942}
1943
71066ef1
AA
1944function ip_in_range ($ip_bin, $range)
1945{
1946 return ($ip_bin & $range['mask_bin']) === $range['ip_bin'];
1947}
1948
fb7a4967 1949// Modify the given tag tree so, that each level's items are sorted alphabetically.
1327d9dd 1950function sortTree (&$tree, $sortfunc = '')
fb7a4967 1951{
59a83bd8 1952 if (!strlen ($sortfunc))
1327d9dd 1953 return;
51b6651a 1954 $self = __FUNCTION__;
1327d9dd 1955 usort ($tree, $sortfunc);
fb7a4967
DO
1956 // Don't make a mistake of directly iterating over the items of current level, because this way
1957 // the sorting will be performed on a _copy_ if each item, not the item itself.
1958 foreach (array_keys ($tree) as $tagid)
51b6651a 1959 $self ($tree[$tagid]['kids'], $sortfunc);
fb7a4967
DO
1960}
1961
0137d53c
DO
1962function iptree_fill (&$netdata)
1963{
59a83bd8 1964 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
0137d53c 1965 return;
a987ff52 1966 // If we really have nested prefixes, they must fit into the tree.
4318ced5 1967 $worktree = $netdata;
0137d53c
DO
1968 foreach ($netdata['kids'] as $pfx)
1969 iptree_embed ($worktree, $pfx);
1970 $netdata['kids'] = iptree_construct ($worktree);
1971 $netdata['kidc'] = count ($netdata['kids']);
1972}
1973
1974function iptree_construct ($node)
1975{
1976 $self = __FUNCTION__;
1977
1978 if (!isset ($node['right']))
1979 {
4318ced5 1980 if (!isset ($node['kids']))
0137d53c 1981 {
0137d53c 1982 $node['kids'] = array();
fec0c8da 1983 $node['kidc'] = 0;
0137d53c
DO
1984 $node['name'] = '';
1985 }
1986 return array ($node);
1987 }
1988 else
1989 return array_merge ($self ($node['left']), $self ($node['right']));
1990}
1991
4318ced5 1992function ip_format ($ip_bin)
21ee3351 1993{
4318ced5
AA
1994 switch (strlen ($ip_bin))
1995 {
1996 case 4: return ip4_format ($ip_bin);
1997 case 16: return ip6_format ($ip_bin);
1998 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
1999 }
2000}
21ee3351 2001
4318ced5
AA
2002function ip4_format ($ip_bin)
2003{
2004 if (4 != ($len = strlen ($ip_bin)))
2005 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2006 return implode ('.', unpack ('C*', $ip_bin));
2007}
2008
2009function ip6_format ($ip_bin)
2010{
2011 // maybe this is IPv6-to-IPv4 address?
2012 if (substr ($ip_bin, 0, 12) == "\0\0\0\0\0\0\0\0\0\0\xff\xff")
2013 return '::ffff:' . implode ('.', unpack ('C*', substr ($ip_bin, 12, 4)));
2014
2015 $result = array();
2016 $hole_index = NULL;
2017 $max_hole_index = NULL;
2018 $hole_length = 0;
2019 $max_hole_length = 0;
2020
2021 for ($i = 0; $i < 8; $i++)
21ee3351 2022 {
4318ced5
AA
2023 $unpacked = unpack ('n', substr ($ip_bin, $i * 2, 2));
2024 $value = array_shift ($unpacked);
2025 $result[] = dechex ($value & 0xffff);
2026 if ($value != 0)
21ee3351 2027 {
4318ced5
AA
2028 unset ($hole_index);
2029 $hole_length = 0;
21ee3351 2030 }
4318ced5
AA
2031 else
2032 {
2033 if (! isset ($hole_index))
2034 $hole_index = $i;
2035 if (++$hole_length >= $max_hole_length)
2036 {
2037 $max_hole_index = $hole_index;
2038 $max_hole_length = $hole_length;
2039 }
2040 }
2041 }
2042 if (isset ($max_hole_index))
2043 {
2044 array_splice ($result, $max_hole_index, $max_hole_length, array (''));
2045 if ($max_hole_index == 0 && $max_hole_length == 8)
2046 return '::';
2047 elseif ($max_hole_index == 0)
2048 return ':' . implode (':', $result);
2049 elseif ($max_hole_index + $max_hole_length == 8)
2050 return implode (':', $result) . ':';
21ee3351 2051 }
4318ced5
AA
2052 return implode (':', $result);
2053}
2054
2055function ip_parse ($ip)
2056{
2057 if (FALSE !== strpos ($ip, ':'))
2058 return ip6_parse ($ip);
21ee3351 2059 else
4318ced5 2060 return ip4_parse ($ip);
21ee3351
AA
2061}
2062
4318ced5 2063function ip4_parse ($ip)
0137d53c 2064{
4318ced5
AA
2065 if (FALSE === ($int = ip2long ($ip)))
2066 throw new InvalidArgException ('ip', $ip, "Invalid IPv4 address");
2067 else
2068 return pack ('N', $int);
2069}
0137d53c 2070
4318ced5
AA
2071// returns 16-byte string ip_bin
2072// throws exception if unable to parse
2073function ip6_parse ($ip)
2074{
2075 do {
2076 if (empty ($ip))
2077 break;
2078
2079 $result = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // 16 bytes
2080 // remove one of double beginning/tailing colons
2081 if (substr ($ip, 0, 2) == '::')
2082 $ip = substr ($ip, 1);
2083 elseif (substr ($ip, -2, 2) == '::')
2084 $ip = substr ($ip, 0, strlen ($ip) - 1);
2085
2086 $tokens = explode (':', $ip);
2087 $last_token = $tokens[count ($tokens) - 1];
2088 $split = explode ('.', $last_token);
2089 if (count ($split) == 4)
2090 {
2091 $hex_tokens = array();
2092 $hex_tokens[] = dechex ($split[0] * 256 + $split[1]);
2093 $hex_tokens[] = dechex ($split[2] * 256 + $split[3]);
2094 array_splice ($tokens, -1, 1, $hex_tokens);
2095 }
2096 if (count ($tokens) > 8)
2097 break;
2098 for ($i = 0; $i < count ($tokens); $i++)
2099 {
2100 if ($tokens[$i] != '')
2101 {
2102 if (! set_word_value ($result, $i, $tokens[$i]))
2103 break;
2104 }
2105 else
2106 {
2107 $k = 8; //index in result string (last word)
2108 for ($j = count ($tokens) - 1; $j > $i; $j--) // $j is an index in $tokens for reverse walk
2109 if ($tokens[$j] == '')
2110 break;
2111 elseif (! set_word_value ($result, --$k, $tokens[$j]))
2112 break;
2113 if ($i != $j)
2114 break; //error, more than 1 '::' range
2115 break;
2116 }
2117 }
2118 if (! isset ($k) && count ($tokens) != 8)
2119 break;
2120 return $result;
2121 } while (FALSE);
2122
2123 throw new InvalidArgException ('ip', $ip, "Invalid IPv6 address");
2124}
2125
26ead7e2
AA
2126function ip_get_arpa ($ip_bin)
2127{
2128 switch (strlen ($ip_bin))
2129 {
2130 case 4: return ip4_get_arpa ($ip_bin);
2131 case 16: return ip6_get_arpa ($ip_bin);
2132 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2133 }
2134}
2135
2136function ip4_get_arpa ($ip_bin)
2137{
2138 $ret = '';
2139 for ($i = 3; $i >= 0; $i--)
2140 $ret .= ord($ip_bin[$i]) . '.';
2141 return $ret . 'in-addr.arpa';
2142}
2143
2144function ip6_get_arpa ($ip_bin)
2145{
2146 $ret = '';
2147 $hex = implode ('', unpack('H32', $ip_bin));
2148 for ($i = 31; $i >= 0; $i--)
2149 $ret .= $hex[$i] . '.';
2150 return $ret . 'ip6.arpa';
2151}
2152
4318ced5
AA
2153function set_word_value (&$haystack, $nword, $hexvalue)
2154{
2155 // check that $hexvalue is like /^[0-9a-fA-F]*$/
2156 for ($i = 0; $i < strlen ($hexvalue); $i++)
0137d53c 2157 {
4318ced5
AA
2158 $char = ord ($hexvalue[$i]);
2159 if (! ($char >= 0x30 && $char <= 0x39 || $char >= 0x41 && $char <= 0x46 || $char >=0x61 && $char <= 0x66))
2160 return FALSE;
0137d53c 2161 }
4318ced5
AA
2162 $haystack = substr_replace ($haystack, pack ('n', hexdec ($hexvalue)), $nword * 2, 2);
2163 return TRUE;
2164}
0137d53c 2165
4318ced5
AA
2166// returns binary IP or FALSE
2167function ip_checkparse ($ip)
2168{
2169 try
2170 {
2171 return ip_parse ($ip);
2172 }
2173 catch (InvalidArgException $e)
0137d53c 2174 {
4318ced5
AA
2175 return FALSE;
2176 }
2177}
a987ff52 2178
4318ced5
AA
2179// returns binary IP or FALSE
2180function ip4_checkparse ($ip)
2181{
2182 try
2183 {
2184 return ip4_parse ($ip);
2185 }
2186 catch (InvalidArgException $e)
2187 {
2188 return FALSE;
0137d53c 2189 }
4318ced5 2190}
0137d53c 2191
4318ced5
AA
2192// returns binary IP or FALSE
2193function ip6_checkparse ($ip)
2194{
2195 try
2196 {
2197 return ip6_parse ($ip);
2198 }
2199 catch (InvalidArgException $e)
2200 {
2201 return FALSE;
2202 }
2203}
2204
2205function ip4_int2bin ($ip_int)
2206{
5a8bd9a3 2207 return pack ('N', $ip_int + 0);
4318ced5
AA
2208}
2209
2210function ip4_bin2int ($ip_bin)
2211{
2212 if (4 != strlen ($ip_bin))
2213 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2214 $list = unpack ('N', $ip_bin);
2215 return array_shift ($list);
2216}
2217
2218// Use this function only when you need to export binary ip out of PHP running context (e.g., DB)
2219// !DO NOT perform arithmetic and bitwise operations with the result of this function!
2220function ip4_bin2db ($ip_bin)
2221{
2222 $ip_int = ip4_bin2int ($ip_bin);
2223 if ($ip_int < 0)
2224 return sprintf ('%u', 0x00000000 + $ip_int);
0137d53c 2225 else
4318ced5
AA
2226 return $ip_int;
2227}
2228
2229function ip_last ($net)
2230{
2231 return $net['ip_bin'] | ~$net['mask_bin'];
2232}
2233
2234function ip_next ($ip_bin)
2235{
2236 $ret = $ip_bin;
2237 $p = 1;
2238 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2239 {
2240 $oct = $p + ord ($ret[$i]);
2241 $ret[$i] = chr ($oct & 0xff);
2242 if ($oct <= 255 and $oct >= 0)
2243 break;
2244 }
2245 return $ret;
2246}
2247
2248function ip_prev ($ip_bin)
2249{
2250 $ret = $ip_bin;
2251 $p = -1;
2252 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2253 {
2254 $oct = $p + ord ($ret[$i]);
2255 $ret[$i] = chr ($oct & 0xff);
2256 if ($oct <= 255 and $oct >= 0)
2257 break;
2258 }
2259 return $ret;
2260}
2261
2262function ip4_range_size ($range)
2263{
2264 return ip4_mask_size ($range['mask']);
2265}
2266
2267function ip4_mask_size ($mask)
2268{
2269 return (0xffffffff >> $mask) + 1;
2270}
2271
2272// returns array with keys 'ip', 'ip_bin', 'mask', 'mask_bin'
2273function constructIPRange ($ip_bin, $mask)
2274{
2275 $node = array();
2276 switch (strlen ($ip_bin))
2277 {
2278 case 4: // IPv4
2279 if ($mask < 0 || $mask > 32)
2280 throw new InvalidArgException ('mask', $mask, "Invalid v4 prefix length");
2281 $node['mask_bin'] = ip4_mask ($mask);
2282 $node['mask'] = $mask;
2283 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2284 $node['ip'] = ip4_format ($node['ip_bin']);
2285 break;
2286 case 16: // IPv6
2287 if ($mask < 0 || $mask > 128)
2288 throw new InvalidArgException ('mask', $mask, "Invalid v6 prefix length");
2289 $node['mask_bin'] = ip6_mask ($mask);
2290 $node['mask'] = $mask;
2291 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2292 $node['ip'] = ip6_format ($node['ip_bin']);
2293 break;
2294 default:
2295 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2296 }
2297 return $node;
2298}
2299
2300// Return minimal IP address structure
2301function constructIPAddress ($ip_bin)
2302{
2303 // common v4/v6 part
2304 $ret = array
2305 (
2306 'ip' => ip_format ($ip_bin),
2307 'ip_bin' => $ip_bin,
2308 'name' => '',
0cd7c0f6 2309 'comment' => '',
4318ced5
AA
2310 'reserved' => 'no',
2311 'allocs' => array(),
71066ef1
AA
2312 'vslist' => array(),
2313 'rsplist' => array(),
4318ced5
AA
2314 );
2315
2316 // specific v4 part
2317 if (strlen ($ip_bin) == 4)
2318 $ret = array_merge
2319 (
2320 $ret,
2321 array
2322 (
2323 'outpf' => array(),
2324 'inpf' => array(),
4318ced5
AA
2325 )
2326 );
2327 return $ret;
0137d53c
DO
2328}
2329
4318ced5 2330function iptree_embed (&$node, $pfx)
21ee3351
AA
2331{
2332 $self = __FUNCTION__;
2333
2334 // hit?
4318ced5 2335 if (0 == IPNetworkCmp ($node, $pfx))
21ee3351
AA
2336 {
2337 $node = $pfx;
2338 return;
2339 }
2340 if ($node['mask'] == $pfx['mask'])
2341 throw new RackTablesError ('the recurring loop lost control', RackTablesError::INTERNAL);
2342
2343 // split?
2344 if (!isset ($node['right']))
2345 {
4318ced5
AA
2346 $node['left'] = constructIPRange ($node['ip_bin'], $node['mask'] + 1);
2347 $node['right'] = constructIPRange (ip_last ($node), $node['mask'] + 1);
21ee3351
AA
2348 }
2349
4318ced5 2350 if (IPNetContainsOrEqual ($node['left'], $pfx))
21ee3351 2351 $self ($node['left'], $pfx);
4318ced5 2352 elseif (IPNetContainsOrEqual ($node['right'], $pfx))
21ee3351
AA
2353 $self ($node['right'], $pfx);
2354 else
2355 throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
2356}
2357
3b81cb98 2358function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
0137d53c 2359{
59a83bd8 2360 if (!strlen ($func))
0137d53c
DO
2361 return;
2362 $self = __FUNCTION__;
2363 foreach (array_keys ($tree) as $key)
2364 {
2365 $func ($tree[$key]);
59a83bd8 2366 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
c3315231 2367 continue;
0137d53c
DO
2368 $self ($tree[$key]['kids'], $func);
2369 }
2370}
2371
4318ced5 2372function nodeIsCollapsed ($node)
a987ff52 2373{
4318ced5 2374 return $node['symbol'] == 'node-collapsed';
a987ff52 2375}
b18d26dc 2376
f59e175c
AA
2377// sets 'addrlist', 'own_addrlist', 'addrc', 'own_addrc' keys of $node
2378// 'addrc' and 'own_addrc' are sizes of 'addrlist' and 'own_addrlist', respectively
2379// 'own_addrlist' is empty if the node network has no children
4318ced5 2380function loadIPAddrList (&$node)
b18d26dc 2381{
4318ced5
AA
2382 $node['addrlist'] = scanIPSpace (array (array ('first' => $node['ip_bin'], 'last' => ip_last ($node))));
2383
2384 if (! isset ($node['id']))
2385 $node['own_addrlist'] = $node['addrlist'];
b18d26dc 2386 else
4318ced5 2387 {
f59e175c
AA
2388 if ($node['kidc'] == 0)
2389 $node['own_addrlist'] = $node['addrlist'];
2390 //$node['own_addrlist'] = array();
2391 else
2392 {
2393 $node['own_addrlist'] = array();
4318ced5
AA
2394 // node has childs
2395 foreach ($node['spare_ranges'] as $mask => $spare_list)
2396 foreach ($spare_list as $spare_ip)
2397 {
2398 $spare_range = constructIPRange ($spare_ip, $mask);
2399 foreach ($node['addrlist'] as $bin_ip => $addr)
2400 if (($bin_ip & $spare_range['mask_bin']) == $spare_range['ip_bin'])
2401 $node['own_addrlist'][$bin_ip] = $addr;
2402 }
f59e175c 2403 }
4318ced5
AA
2404 }
2405 $node['addrc'] = count ($node['addrlist']);
2406 $node['own_addrc'] = count ($node['own_addrlist']);
b6b87070
DO
2407}
2408
4318ced5 2409function getIPv4OwnRangeSize ($range)
c3315231 2410{
f59e175c 2411 if (empty ($range['spare_ranges']))
4318ced5 2412 return ip4_range_size ($range);
f59e175c
AA
2413 else
2414 {
2415 $ret = 0;
4318ced5
AA
2416 foreach ($range['spare_ranges'] as $mask => $spare_list)
2417 $ret += count ($spare_list) * ip4_mask_size ($mask);
f59e175c
AA
2418 return $ret;
2419 }
c3315231
DO
2420}
2421
4318ced5
AA
2422// returns the array of structure described by constructIPAddress
2423function getIPAddress ($ip_bin)
b6b87070 2424{
4318ced5
AA
2425 $scanres = scanIPSpace (array (array ('first' => $ip_bin, 'last' => $ip_bin)));
2426 if (empty ($scanres))
2427 return constructIPAddress ($ip_bin);
2428 markupIPAddrList ($scanres);
2429 return $scanres[$ip_bin];
b18d26dc
DO
2430}
2431
4318ced5 2432function getIPv4Address ($ip_bin)
21ee3351 2433{
4318ced5
AA
2434 if (strlen ($ip_bin) != 4)
2435 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2436 return getIPAddress ($ip_bin);
21ee3351
AA
2437}
2438
4318ced5 2439function getIPv6Address ($ip_bin)
21ee3351 2440{
4318ced5
AA
2441 if (strlen ($ip_bin) != 16)
2442 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2443 return getIPAddress ($ip_bin);
21ee3351
AA
2444}
2445
4318ced5 2446function makeIPTree ($netlist)
fec0c8da 2447{
573214e0 2448 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
4318ced5
AA
2449 // so perform necessary pre-processing to calculate parent_id of each item of $netlist.
2450 $stack = array();
2451 foreach ($netlist as $net_id => &$net)
2452 {
2453 while (! empty ($stack))
2454 {
2455 $top_id = $stack[count ($stack) - 1];
2456 if (! IPNetContains ($netlist[$top_id], $net)) // unless $net is a child of stack top
2457 array_pop ($stack);
2458 else
2459 {
2460 $net['parent_id'] = $top_id;
2461 break;
2462 }
2463 }
2464 if (empty ($stack))
2465 $net['parent_id'] = NULL;
2466 array_push ($stack, $net_id);
2467 }
2468 unset ($stack);
2469
fec0c8da 2470 $tree = treeFromList ($netlist); // medium call
fec0c8da
DO
2471 return $tree;
2472}
2473
4318ced5 2474function prepareIPTree ($netlist, $expanded_id = 0)
21ee3351 2475{
4318ced5 2476 $tree = makeIPTree ($netlist);
21ee3351 2477 // complement the tree before markup to make the spare networks have "symbol" set
4318ced5 2478 treeApplyFunc ($tree, 'iptree_fill');
21ee3351
AA
2479 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
2480 return $tree;
2481}
2482
a280c8b4
DO
2483# Traverse IPv4/IPv6 tree and return a list of all networks, which
2484# exist in DB and don't have any sub-networks.
2485function getTerminalNetworks ($tree)
2486{
2487 $self = __FUNCTION__;
2488 $ret = array();
2489 foreach ($tree as $node)
2490 if ($node['kidc'] == 0 and isset ($node['realm']))
2491 $ret[] = $node;
2492 else
2493 $ret = array_merge ($ret, $self ($node['kids']));
2494 return $ret;
2495}
2496
fec0c8da
DO
2497// Check all items of the tree recursively, until the requested target id is
2498// found. Mark all items leading to this item as "expanded", collapsing all
2499// the rest, which exceed the given threshold (if the threshold is given).
2500function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
2501{
2502 $self = __FUNCTION__;
2503 $ret = FALSE;
2504 foreach (array_keys ($tree) as $key)
2505 {
5388794d 2506 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
fec0c8da 2507 $below = $self ($tree[$key]['kids'], $threshold, $target);
4318ced5 2508 $expand_enabled = ($target !== 'NONE');
fec0c8da
DO
2509 if (!$tree[$key]['kidc']) // terminal node
2510 $tree[$key]['symbol'] = 'spacer';
4318ced5 2511 elseif ($expand_enabled and $tree[$key]['kidc'] < $threshold)
fec0c8da 2512 $tree[$key]['symbol'] = 'node-expanded-static';
4318ced5 2513 elseif ($expand_enabled and ($here or $below))
fec0c8da
DO
2514 $tree[$key]['symbol'] = 'node-expanded';
2515 else
2516 $tree[$key]['symbol'] = 'node-collapsed';
2517 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
2518 }
2519 return $ret;
2520}
2521
e1ae3fb4
AD
2522// Convert entity name to human-readable value
2523function formatEntityName ($name) {
2524 switch ($name)
2525 {
2526 case 'ipv4net':
2527 return 'IPv4 Network';
7fa4b0e4
AA
2528 case 'ipv6net':
2529 return 'IPv6 Network';
e1ae3fb4
AD
2530 case 'ipv4rspool':
2531 return 'IPv4 RS Pool';
2532 case 'ipv4vs':
2533 return 'IPv4 Virtual Service';
2534 case 'object':
2535 return 'Object';
2536 case 'rack':
2537 return 'Rack';
e1add254
AD
2538 case 'location':
2539 return 'Location';
e1ae3fb4
AD
2540 case 'user':
2541 return 'User';
2542 }
2543 return 'invalid';
2544}
2545
8bc5d1e4
DO
2546// Display hrefs for all of a file's parents. If scissors are requested,
2547// prepend cutting button to each of them.
2548function serializeFileLinks ($links, $scissors = FALSE)
e1ae3fb4 2549{
e1ae3fb4
AD
2550 $comma = '';
2551 $ret = '';
2552 foreach ($links as $link_id => $li)
2553 {
2554 switch ($li['entity_type'])
2555 {
2556 case 'ipv4net':
2557 $params = "page=ipv4net&id=";
2558 break;
7fa4b0e4
AA
2559 case 'ipv6net':
2560 $params = "page=ipv6net&id=";
2561 break;
e1ae3fb4
AD
2562 case 'ipv4rspool':
2563 $params = "page=ipv4rspool&pool_id=";
2564 break;
2565 case 'ipv4vs':
2566 $params = "page=ipv4vs&vs_id=";
2567 break;
2568 case 'object':
2569 $params = "page=object&object_id=";
2570 break;
2571 case 'rack':
2572 $params = "page=rack&rack_id=";
2573 break;
e1add254
AD
2574 case 'location':
2575 $params = "page=location&location_id=";
2576 break;
e1ae3fb4
AD
2577 case 'user':
2578 $params = "page=user&user_id=";
2579 break;
2580 }
8bc5d1e4
DO
2581 $ret .= $comma;
2582 if ($scissors)
2583 {
2584 $ret .= "<a href='" . makeHrefProcess(array('op'=>'unlinkFile', 'link_id'=>$link_id)) . "'";
2585 $ret .= getImageHREF ('cut') . '</a> ';
2586 }
790a60e8 2587 $ret .= sprintf("<a href='index.php?%s%s'>%s</a>", $params, $li['entity_id'], $li['name']);
8bc5d1e4 2588 $comma = '<br>';
e1ae3fb4 2589 }
e1ae3fb4
AD
2590 return $ret;
2591}
2592
2593// Convert filesize to appropriate unit and make it human-readable
2594function formatFileSize ($bytes) {
2595 // bytes
2596 if($bytes < 1024) // bytes
2597 return "${bytes} bytes";
2598
2599 // kilobytes
2600 if ($bytes < 1024000)
2601 return sprintf ("%.1fk", round (($bytes / 1024), 1));
2602
2603 // megabytes
2604 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
2605}
2606
2607// Reverse of formatFileSize, it converts human-readable value to bytes
2608function convertToBytes ($value) {
2609 $value = trim($value);
2610 $last = strtolower($value[strlen($value)-1]);
2611 switch ($last)
2612 {
2613 case 'g':
2614 $value *= 1024;
2615 case 'm':
2616 $value *= 1024;
2617 case 'k':
2618 $value *= 1024;
2619 }
2620
2621 return $value;
2622}
2623
36ef72d9 2624// make "A" HTML element
3b3c5e21
DO
2625function mkA ($text, $nextpage, $bypass = NULL, $nexttab = NULL)
2626{
2627 global $page, $tab;
ae61a274 2628 if ($text == '')
3b3c5e21 2629 throw new InvalidArgException ('text', $text);
ae61a274
DO
2630 if (! array_key_exists ($nextpage, $page))
2631 throw new InvalidArgException ('nextpage', $nextpage, 'not found');
3b3c5e21
DO
2632 $args = array ('page' => $nextpage);
2633 if ($nexttab !== NULL)
2634 {
ae61a274
DO
2635 if (! array_key_exists ($nexttab, $tab[$nextpage]))
2636 throw new InvalidArgException ('nexttab', $nexttab, 'not found');
3b3c5e21
DO
2637 $args['tab'] = $nexttab;
2638 }
2639 if (array_key_exists ('bypass', $page[$nextpage]))
2640 {
2641 if ($bypass === NULL)
ae61a274 2642 throw new InvalidArgException ('bypass', '(NULL)');
3b3c5e21
DO
2643 $args[$page[$nextpage]['bypass']] = $bypass;
2644 }
2645 return '<a href="' . makeHref ($args) . '">' . $text . '</a>';
2646}
2647
36ef72d9 2648// make "HREF" HTML attribute
ae61a274 2649function makeHref ($params = array())
4fbb5a00 2650{
ae61a274
DO
2651 $tmp = array();
2652 foreach ($params as $key => $value)
3bbf8c03
AA
2653 {
2654 if (is_array ($value))
2655 $key .= "[]";
2656 else
2657 $value = array ($value);
73348a60
AA
2658 if (!count ($value))
2659 $tmp[] = urlencode ($key) . '=';
2660 else
2661 foreach ($value as $sub_value)
2662 $tmp[] = urlencode ($key) . '=' . urlencode ($sub_value);
3bbf8c03 2663 }
ae61a274 2664 return 'index.php?' . implode ('&', $tmp);
4fbb5a00
DY
2665}
2666
ae61a274 2667function makeHrefProcess ($params = array())
4fbb5a00 2668{
aa017c43 2669 global $pageno, $tabno, $page;
ae61a274
DO
2670 $tmp = array();
2671 if (! array_key_exists ('page', $params))
9f14a7ef 2672 $params['page'] = $pageno;
ae61a274 2673 if (! array_key_exists ('tab', $params))
9f14a7ef 2674 $params['tab'] = $tabno;
aa017c43
AA
2675 if (isset ($page[$pageno]['bypass']))
2676 {
2677 $bypass = $page[$pageno]['bypass'];
2678 if (! array_key_exists ($bypass, $params) and array_key_exists ($bypass, $_REQUEST))
2679 $params[$bypass] = $_REQUEST[$bypass];
2680 }
ae61a274
DO
2681 foreach ($params as $key => $value)
2682 $tmp[] = urlencode ($key) . '=' . urlencode ($value);
2683 return '?module=redirect&' . implode ('&', $tmp);
4fbb5a00
DY
2684}
2685
39106006 2686function makeHrefForHelper ($helper_name, $params = array())
4fbb5a00 2687{
e0ce8064 2688 $ret = '?module=popup&helper=' . $helper_name;
4fbb5a00 2689 foreach($params as $key=>$value)
39106006 2690 $ret .= '&'.urlencode($key).'='.urlencode($value);
4fbb5a00
DY
2691 return $ret;
2692}
2693
f3d274bf
DO
2694// Process the given list of records to build data suitable for printNiftySelect()
2695// (like it was formerly executed by printSelect()). Screen out vendors according
2696// to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
2697// selected OPTION is protected from being screened.
2698function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
2699{
2700 $ret = array();
2701 // Always keep "other" OPTGROUP at the SELECT bottom.
2702 $therest = array();
2703 foreach ($recordList as $dict_key => $dict_value)
2704 if (strpos ($dict_value, '%GSKIP%') !== FALSE)
2705 {
2706 $tmp = explode ('%GSKIP%', $dict_value, 2);
2707 $ret[$tmp[0]][$dict_key] = $tmp[1];
2708 }
2709 elseif (strpos ($dict_value, '%GPASS%') !== FALSE)
2710 {
2711 $tmp = explode ('%GPASS%', $dict_value, 2);
2712 $ret[$tmp[0]][$dict_key] = $tmp[1];
2713 }
2714 else
2715 $therest[$dict_key] = $dict_value;
2716 if ($object_type_id != 0)
2717 {
2718 $screenlist = array();
2719 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
eea3ca5e 2720 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
f3d274bf 2721 $screenlist[] = $regs[1];
eea3ca5e 2722 }
f3d274bf
DO
2723 foreach (array_keys ($ret) as $vendor)
2724 if (in_array ($vendor, $screenlist))
2725 {
2726 $ok_to_screen = TRUE;
2727 if ($existing_value)
2728 foreach (array_keys ($ret[$vendor]) as $recordkey)
2729 if ($recordkey == $existing_value)
2730 {
2731 $ok_to_screen = FALSE;
2732 break;
2733 }
2734 if ($ok_to_screen)
2735 unset ($ret[$vendor]);
2736 }
2737 }
2738 $ret['other'] = $therest;
2739 return $ret;
2740}
2741
9dd73255
DO
2742function unix2dos ($text)
2743{
2744 return str_replace ("\n", "\r\n", $text);
2745}
2746
a0527aef 2747function buildPredicateTable ($parsetree)
2c21a10c 2748{
a0527aef
DO
2749 $ret = array();
2750 foreach ($parsetree as $sentence)
72e8baf6 2751 if ($sentence['type'] == 'SYNT_DEFINITION')
a0527aef 2752 $ret[$sentence['term']] = $sentence['definition'];
72e8baf6
DO
2753 // Now we have predicate table filled in with the latest definitions of each
2754 // particular predicate met. This isn't as chik, as on-the-fly predicate
2755 // overloading during allow/deny scan, but quite sufficient for this task.
a0527aef
DO
2756 return $ret;
2757}
2758
2759// Take a list of records and filter against given RackCode expression. Return
2760// the original list intact, if there was no filter requested, but return an
2761// empty list, if there was an error.
573214e0 2762function filterCellList ($list_in, $expression = array())
d08d766d
DO
2763{
2764 if ($expression === NULL)
2765 return array();
2766 if (!count ($expression))
2767 return $list_in;
d08d766d
DO
2768 $list_out = array();
2769 foreach ($list_in as $item_key => $item_value)
573214e0 2770 if (TRUE === judgeCell ($item_value, $expression))
d08d766d
DO
2771 $list_out[$item_key] = $item_value;
2772 return $list_out;
2773}
2774
ef0503fc
DO
2775function eval_expression ($expr, $tagchain, $ptable, $silent = FALSE)
2776{
2777 $self = __FUNCTION__;
2778 switch ($expr['type'])
2779 {
2780 // Return true, if given tag is present on the tag chain.
2781 case 'LEX_TAG':
2782 case 'LEX_AUTOTAG':
2783 foreach ($tagchain as $tagInfo)
2784 if ($expr['load'] == $tagInfo['tag'])
2785 return TRUE;
2786 return FALSE;
2787 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it.
2788 $pname = $expr['load'];
2789 if (!isset ($ptable[$pname]))
2790 {
2791 if (!$silent)
08408472 2792 showWarning ("Predicate '${pname}' is referenced before declaration");
ef0503fc
DO
2793 return NULL;
2794 }
2795 return $self ($ptable[$pname], $tagchain, $ptable);
2796 case 'LEX_TRUE':
2797 return TRUE;
2798 case 'LEX_FALSE':
2799 return FALSE;
2800 case 'SYNT_NOT_EXPR':
2801 $tmp = $self ($expr['load'], $tagchain, $ptable);
2802 if ($tmp === TRUE)
2803 return FALSE;
2804 elseif ($tmp === FALSE)
2805 return TRUE;
2806 else
2807 return $tmp;
2808 case 'SYNT_AND_EXPR': // binary AND
2809 if (FALSE == $self ($expr['left'], $tagchain, $ptable))
2810 return FALSE; // early failure
2811 return $self ($expr['right'], $tagchain, $ptable);
2812 case 'SYNT_EXPR': // binary OR
2813 if (TRUE == $self ($expr['left'], $tagchain, $ptable))
2814 return TRUE; // early success
2815 return $self ($expr['right'], $tagchain, $ptable);
2816 default:
2817 if (!$silent)
08408472 2818 showWarning ("Evaluation error, cannot process expression type '${expr['type']}'");
ef0503fc
DO
2819 return NULL;
2820 break;
2821 }
2822}
2823
212c9d8a 2824// Tell, if the given expression is true for the given entity. Take complete record on input.
573214e0 2825function judgeCell ($cell, $expression)
d08d766d
DO
2826{
2827 global $pTable;
2828 return eval_expression
2829 (
2830 $expression,
2831 array_merge
2832 (
573214e0
DO
2833 $cell['etags'],
2834 $cell['itags'],
2835 $cell['atags']
d08d766d
DO
2836 ),
2837 $pTable,
2838 TRUE
2839 );
2840}
2841
9d7e7b8b
AA
2842function judgeContext ($expression)
2843{
2844 global $pTable, $expl_tags, $impl_tags, $auto_tags;
2845 return eval_expression
2846 (
2847 $expression,
2848 array_merge
2849 (
2850 $expl_tags,
2851 $impl_tags,
2852 $auto_tags
2853 ),
2854 $pTable,
2855 TRUE
2856 );
2857}
2858
7ddbcf59 2859// Tell, if a constraint from config option permits given record.
99446891 2860// An undefined $cell means current context.
212c9d8a 2861function considerConfiguredConstraint ($cell, $varname)
c6bc0ac5 2862{
7ddbcf59 2863 if (!strlen (getConfigVar ($varname)))
c6bc0ac5 2864 return TRUE; // no restriction
7ddbcf59
DO
2865 global $parseCache;
2866 if (!isset ($parseCache[$varname]))
2867 // getConfigVar() doesn't re-read the value from DB because of its
2868 // own cache, so there is no race condition here between two calls.
2869 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2870 if ($parseCache[$varname]['result'] != 'ACK')
2871 return FALSE; // constraint set, but cannot be used due to compilation error
99446891
AA
2872 if (isset ($cell))
2873 return judgeCell ($cell, $parseCache[$varname]['load']);
2874 else
2875 return judgeContext ($parseCache[$varname]['load']);
c6bc0ac5
DO
2876}
2877
682e77d1
DO
2878// Tell, if the given arbitrary RackCode text addresses the given record
2879// (an empty text matches any record).
68292868 2880// An undefined $cell means current context.
682e77d1
DO
2881function considerGivenConstraint ($cell, $filtertext)
2882{
2883 if ($filtertext == '')
2884 return TRUE;
2885 $parse = spotPayload ($filtertext, 'SYNT_EXPR');
2886 if ($parse['result'] != 'ACK')
2887 throw new InvalidRequestArgException ('filtertext', $filtertext, 'RackCode parsing error');
68292868
AA
2888 if (isset ($cell))
2889 return judgeCell ($cell, $parse['load']);
2890 else
2891 return judgeContext ($parse['load']);
682e77d1
DO
2892}
2893
17112e81
DO
2894// Return list of records in the given realm, which conform to
2895// the given RackCode expression. If the realm is unknown or text
2896// doesn't validate as a RackCode expression, return NULL.
2897// Otherwise (successful scan) return a list of all matched
2898// records, even if the list is empty (array() !== NULL). If the
2899// text is an empty string, return all found records in the given
2900// realm.
2901function scanRealmByText ($realm = NULL, $ftext = '')
2902{
2903 switch ($realm)
2904 {
2905 case 'object':
95e4b0f5 2906 case 'rack':
17112e81
DO
2907 case 'user':
2908 case 'ipv4net':
7fa4b0e4 2909 case 'ipv6net':
17112e81 2910 case 'file':
39776127 2911 case 'ipv4vs':
41780975 2912 case 'ipv4rspool':
883bd06c 2913 case 'vst':
17112e81
DO
2914 if (!strlen ($ftext = trim ($ftext)))
2915 $fexpr = array();
2916 else
2917 {
2918 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2919 if ($fparse['result'] != 'ACK')
2920 return NULL;
2921 $fexpr = $fparse['load'];
2922 }
2923 return filterCellList (listCells ($realm), $fexpr);
2924 default:
c5f84f48 2925 throw new InvalidArgException ('$realm', $realm);
17112e81
DO
2926 }
2927
2928}
39776127 2929
cc3d6915
DO
2930function getVSTOptions()
2931{
2932 $ret = array();
2933 foreach (listCells ('vst') as $vst)
2934 $ret[$vst['id']] = niftyString ($vst['description'], 30, FALSE);
2935 return $ret;
2936}
2937
c7e370ed
DO
2938# Return a 2-dimensional array in the format understood by getNiftySelect(),
2939# which would contain all VLANs of all VLAN domains except domains, which IDs
2940# are present in $except_domains argument.
2941function getAllVLANOptions ($except_domains = array())
52e38709 2942{
c7e370ed
DO
2943 $ret = array();
2944 foreach (getVLANDomainStats() as $domain)
2945 if (! in_array ($domain['id'], $except_domains))
2946 foreach (getDomainVLANs ($domain['id']) as $vlan)
2947 $ret[$domain['description']]["${domain['id']}-${vlan['vlan_id']}"] =
2948 "${vlan['vlan_id']} (${vlan['netc']}) ${vlan['vlan_descr']}";
52e38709
DO
2949 return $ret;
2950}
2951
56a797ef
DO
2952// Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2953function dump ($var)
2954{
2955 echo '<div align=left><pre>';
2956 print_r ($var);
2957 echo '</pre></div>';
2958}
2959
9e51318b
DO
2960function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2961{
2962 global $taglist;
2963 // first build top-N chart...
2964 $toplist = array();
2965 foreach ($taglist as $taginfo)
2966 if (isset ($taginfo['refcnt'][$realm]))
2967 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2968 arsort ($toplist, SORT_NUMERIC);
2969 $ret = array();
2970 $done = 0;
2971 foreach (array_keys ($toplist) as $tag_id)
2972 {
2973 $ret[$tag_id] = $taglist[$tag_id];
2974 if (++$done == $limit)
2975 break;
2976 }
2977 // ...then make sure, that every item of the special list is shown
2978 // (using the same sort order)
2979 $extra = array();
2980 foreach ($special_tags as $taginfo)
2981 if (!array_key_exists ($taginfo['id'], $ret))
2982 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2983 arsort ($extra, SORT_NUMERIC);
2984 foreach (array_keys ($extra) as $tag_id)
2985 $ret[] = $taglist[$tag_id];
2986 return $ret;
2987}
2988
7fa7047a
DO
2989function decodeObjectType ($objtype_id, $style = 'r')
2990{
2991 static $types = array();
2992 if (!count ($types))
2993 $types = array
2994 (
2995 'r' => readChapter (CHAP_OBJTYPE),
2996 'a' => readChapter (CHAP_OBJTYPE, 'a'),
2997 'o' => readChapter (CHAP_OBJTYPE, 'o')
2998 );
2999 return $types[$style][$objtype_id];
3000}
3001
0df8c52b
DO
3002function isolatedPermission ($p, $t, $cell)
3003{
3004 // This function is called from both "file" page and a number of other pages,
3005 // which have already fixed security context and authorized the user for it.
3006 // OTOH, it is necessary here to authorize against the current file, which
3007 // means saving the current context and building a new one.
3008 global
3009 $expl_tags,
3010 $impl_tags,
3011 $target_given_tags,
3012 $auto_tags;
3013 // push current context
3014 $orig_expl_tags = $expl_tags;
3015 $orig_impl_tags = $impl_tags;
3016 $orig_target_given_tags = $target_given_tags;
3017 $orig_auto_tags = $auto_tags;
3018 // retarget
3019 fixContext ($cell);
3020 // remember decision
3021 $ret = permitted ($p, $t);
3022 // pop context
3023 $expl_tags = $orig_expl_tags;
3024 $impl_tags = $orig_impl_tags;
3025 $target_given_tags = $orig_target_given_tags;
3026 $auto_tags = $orig_auto_tags;
3027 return $ret;
3028}
3029
3153a326
DO
3030function getPortListPrefs()
3031{
3032 $ret = array();
3033 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
3034 $ret['iif_pick'] = 1;
3035 $ret['oif_picks'] = array();
3036 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
3037 {
3038 $tmp = explode ('=', trim ($tmp));
3039 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
3040 $ret['oif_picks'][$tmp[0]] = $tmp[1];
3041 }
3042 // enforce default value
3043 if (!array_key_exists (1, $ret['oif_picks']))
3044 $ret['oif_picks'][1] = 24;
3045 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
3046 return $ret;
3047}
3048
2dfa1b73
DO
3049// Return data for printNiftySelect() with port type options. All OIF options
3050// for the default IIF will be shown, but only the default OIFs will be present
3051// for each other IIFs. IIFs, for which there is no default OIF, will not
3052// be listed.
3053// This SELECT will be used for the "add new port" form.
3054function getNewPortTypeOptions()
3055{
3056 $ret = array();
3057 $prefs = getPortListPrefs();
3058 foreach (getPortInterfaceCompat() as $row)
3059 {
3060 if ($row['iif_id'] == $prefs['iif_pick'])
3061 $optgroup = $row['iif_name'];
3062 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
3063 $optgroup = 'other';
3064 else
3065 continue;
3066 if (!array_key_exists ($optgroup, $ret))
3067 $ret[$optgroup] = array();
3068 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
3069 }
3070 return $ret;
3071}
3072
8198f2c6
DO
3073// Return a serialized version of VLAN configuration for a port.
3074// If a native VLAN is defined, print it first. All other VLANs
d0dadd80
DO
3075// are tagged and are listed after a plus sign. When no configuration
3076// is set for a port, return "default" string.
de1e0987 3077function serializeVLANPack ($vlanport)
8198f2c6 3078{
4f860864
DO
3079 if (!array_key_exists ('mode', $vlanport))
3080 return 'error';
3081 switch ($vlanport['mode'])
3082 {
3083 case 'none':
3084 return 'none';
3085 case 'access':
3086 $ret = 'A';
3087 break;
3088 case 'trunk':
3089 $ret = 'T';
3090 break;
36a70b71
DO
3091 case 'uplink':
3092 $ret = 'U';
3093 break;
3094 case 'downlink':
3095 $ret = 'D';
3096 break;
4f860864
DO
3097 default:
3098 return 'error';
3099 }
0ab9ec25
AA
3100 if ($vlanport['native'])
3101 $ret .= $vlanport['native'];
e55917ed
AA
3102 $tagged_bits = groupIntsToRanges ($vlanport['allowed'], $vlanport['native']);
3103 if (count ($tagged_bits))
de1e0987 3104 $ret .= '+' . implode (', ', $tagged_bits);
e55917ed
AA
3105 return strlen ($ret) ? $ret : 'default';
3106}
3107
3108function groupIntsToRanges ($list, $exclude_value = NULL)
3109{
3110 $result = array();
3111 sort ($list);
e9be55de 3112 $id_from = $id_to = 0;
e55917ed
AA
3113 $list[] = -1;
3114 foreach ($list as $next_id)
3115 if (!isset ($exclude_value) or $next_id != $exclude_value)
3116 if ($id_to && $next_id == $id_to + 1)
3117 $id_to = $next_id; // merge
3118 else
e9be55de 3119 {
e55917ed
AA
3120 if ($id_to)
3121 $result[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}"; // flush
3122 $id_from = $id_to = $next_id; // start next pair
e9be55de 3123 }
e55917ed 3124 return $result;
8198f2c6
DO
3125}
3126
8846b060
DO
3127// Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
3128// return the numbers as an array of two.
3129function decodeVLANCK ($string)
3130{
3131 $matches = array();
3132 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
3133 throw new InvalidArgException ('VLAN compound key', $string);
3134 return array ($matches[1], $matches[2]);
3135}
3136
ce85f5c8
DO
3137// Return VLAN name formatted for HTML output (note, that input
3138// argument comes from database unescaped).
a72aa89f
DO
3139function formatVLANName ($vlaninfo, $context = 'markup long')
3140{
3141 switch ($context)
3142 {
3143 case 'option':
3144 $ret = $vlaninfo['vlan_id'];
3145 if ($vlaninfo['vlan_descr'] != '')
3146 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
3147 return $ret;
3148 case 'label':
3149 $ret = $vlaninfo['vlan_id'];
3150 if ($vlaninfo['vlan_descr'] != '')
3151 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
3152 return $ret;
0812b506
DO
3153 case 'plain long':
3154 $ret = 'VLAN' . $vlaninfo['vlan_id'];
3155 if ($vlaninfo['vlan_descr'] != '')
1f54e1ba
DO
3156 $ret .= ' (' . niftyString ($vlaninfo['vlan_descr'], 20, FALSE) . ')';
3157 return $ret;
3158 case 'hyperlink':
3159 $ret = '<a href="';
3160 $ret .= makeHref (array ('page' => 'vlan', 'vlan_ck' => $vlaninfo['domain_id'] . '-' . $vlaninfo['vlan_id']));
3161 $ret .= '">' . formatVLANName ($vlaninfo, 'markup long') . '</a>';
0812b506 3162 return $ret;
a72aa89f
DO
3163 case 'markup long':
3164 default:
3165 $ret = 'VLAN' . $vlaninfo['vlan_id'];
3166 $ret .= ' @' . niftyString ($vlaninfo['domain_descr']);
3167 if ($vlaninfo['vlan_descr'] != '')
3168 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
3169 return $ret;
3170 }
ce85f5c8
DO
3171}
3172
e9d357e1
DO
3173// map interface name
3174function ios12ShortenIfName ($ifname)
3175{
680c6167
AA
3176 if (preg_match ('@^eth-trunk(\d+)$@i', $ifname, $m))
3177 return "Eth-Trunk${m[1]}";
20cd20ad 3178 $ifname = preg_replace ('@^(?:[Ee]thernet|Eth)(.+)$@', 'e\\1', $ifname);
e9d357e1 3179 $ifname = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $ifname);
65e557dd 3180 $ifname = preg_replace ('@^(?:GigabitEthernet|GE)(.+)$@', 'gi\\1', $ifname);
e9d357e1 3181 $ifname = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $ifname);
d5f6bc83 3182 $ifname = preg_replace ('@^port-channel(.+)$@i', 'po\\1', $ifname);
65e557dd 3183 $ifname = preg_replace ('@^(?:XGigabitEthernet|XGE)(.+)$@', 'xg\\1', $ifname);
eb8637f0 3184 $ifname = preg_replace ('@^LongReachEthernet(.+)$@', 'lo\\1', $ifname);
d5f6bc83 3185 $ifname = preg_replace ('@^Management(.+)$@', 'ma\\1', $ifname);
e40e90d0 3186 $ifname = preg_replace ('@^Et(\d.*)$@', 'e\\1', $ifname);
1ebbf889 3187 $ifname = strtolower ($ifname);
e9d357e1
DO
3188 return $ifname;
3189}
3190
3af65689
DO
3191# Produce a list of integers from a string in the following format:
3192# A,B,C-D,E-F,G,H,I-J,K ...
f10dd5cc
DO
3193function iosParseVLANString ($string)
3194{
3195 $ret = array();
3196 foreach (explode (',', $string) as $item)
3197 {
3198 $matches = array();
735e323f 3199 $item = trim ($item, ' ');
f10dd5cc
DO
3200 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches))
3201 $ret[] = $matches[1];
3202 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches))
3203 $ret = array_merge ($ret, range ($matches[1], $matches[2]));
3af65689
DO
3204 else
3205 throw new InvalidArgException ('string', $string, 'format mismatch');
f10dd5cc
DO
3206 }
3207 return $ret;
3208}
3209
bb35bb93
DO
3210// Scan given array and return the key, which addresses the first item
3211// with requested column set to given value (or NULL if there is none such).
19350222
DO
3212// Note that 0 and NULL mean completely different things and thus
3213// require strict checking (=== and !===).
dbc00990
DO
3214function scanArrayForItem ($table, $scan_column, $scan_value)
3215{
3216 foreach ($table as $key => $row)
3217 if ($row[$scan_column] == $scan_value)
3218 return $key;
3219 return NULL;
3220}
3221
66658512
DO
3222// Return TRUE, if every value of A1 is present in A2 and vice versa,
3223// regardless of each array's sort order and indexing.
3224function array_values_same ($a1, $a2)
3225{
3226 return !count (array_diff ($a1, $a2)) and !count (array_diff ($a2, $a1));
3227}
3228
56561657
DO
3229# Reindex provided array of arrays by a column value, which is present in
3230# each sub-array and is assumed to be unique. Most often, make "id" column in
3231# a list of cells into the key space.
3232function reindexById ($input, $column_name = 'id')
3233{
6c0bf066 3234 $ret = array();
56561657
DO
3235 foreach ($input as $item)
3236 {
3237 if (! array_key_exists ($column_name, $item))
3238 throw new InvalidArgException ('input', '(array)', 'ID column missing');
3239 if (array_key_exists ($item[$column_name], $ret))
3240 throw new InvalidArgException ('column_name', $column_name, 'duplicate ID value ' . $item[$column_name]);
3241 $ret[$item[$column_name]] = $item;
3242 }
3243 return $ret;
3244}
3245
1ce258f7
DO
3246// Use the VLAN switch template to set VST role for each port of
3247// the provided list. Return resulting list.
bc254f49 3248function apply8021QOrder ($vst_id, $portlist)
25930440 3249{
cc3d6915
DO
3250 $vst = spotEntity ('vst', $vst_id);
3251 amplifyCell ($vst);
1ce258f7
DO
3252 foreach (array_keys ($portlist) as $port_name)
3253 {
3254 foreach ($vst['rules'] as $rule)
3255 if (preg_match ($rule['port_pcre'], $port_name))
25930440
DO
3256 {
3257 $portlist[$port_name]['vst_role'] = $rule['port_role'];
091768aa 3258 $portlist[$port_name]['wrt_vlans'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']);
1ce258f7 3259 continue 2;
25930440 3260 }
1ce258f7
DO
3261 $portlist[$port_name]['vst_role'] = 'none';
3262 }
25930440
DO
3263 return $portlist;
3264}
3265
091768aa
DO
3266// return a sequence of ranges for given string form and port role
3267function buildVLANFilter ($role, $string)
be28b696 3268{
091768aa 3269 // set base
1ce258f7 3270 switch ($role)
be28b696 3271 {
1ce258f7
DO
3272 case 'access': // 1-4094
3273 $min = VLAN_MIN_ID;
3274 $max = VLAN_MAX_ID;
3275 break;
3276 case 'trunk': // 2-4094
3277 case 'uplink':
65da0c15 3278 case 'downlink':
ec523868 3279 case 'anymode':
1ce258f7
DO
3280 $min = VLAN_MIN_ID + 1;
3281 $max = VLAN_MAX_ID;
3282 break;
65da0c15 3283 default: // none
1ce258f7 3284 return array();
be28b696 3285 }
091768aa
DO
3286 if ($string == '') // fast track
3287 return array (array ('from' => $min, 'to' => $max));
3288 // transform
3289 $vlanidlist = array();
1ce258f7
DO
3290 foreach (iosParseVLANString ($string) as $vlan_id)
3291 if ($min <= $vlan_id and $vlan_id <= $max)
091768aa 3292 $vlanidlist[] = $vlan_id;
cc6a6c4e
DO
3293 return listToRanges ($vlanidlist);
3294}
3295
3296// pack set of integers into list of integer ranges
3297// e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11))
ec523868
DO
3298// The second argument, when it is different from 0, limits amount of
3299// items in each generated range.
3300function listToRanges ($vlanidlist, $limit = 0)
cc6a6c4e 3301{
091768aa
DO
3302 sort ($vlanidlist);
3303 $ret = array();
3304 $from = $to = NULL;
3305 foreach ($vlanidlist as $vlan_id)
3306 if ($from == NULL)
ec523868
DO
3307 {
3308 if ($limit == 1)
3309 $ret[] = array ('from' => $vlan_id, 'to' => $vlan_id);
3310 else
3311 $from = $to = $vlan_id;
3312 }
091768aa 3313 elseif ($to + 1 == $vlan_id)
ec523868 3314 {
091768aa 3315 $to = $vlan_id;
ec523868
DO
3316 if ($to - $from + 1 == $limit)
3317 {
3318 // cut accumulated range and start over
3319 $ret[] = array ('from' => $from, 'to' => $to);
3320 $from = $to = NULL;
3321 }
3322 }
091768aa
DO
3323 else
3324 {
3325 $ret[] = array ('from' => $from, 'to' => $to);
3326 $from = $to = $vlan_id;
3327 }
3328 if ($from != NULL)
3329 $ret[] = array ('from' => $from, 'to' => $to);
be28b696
DO
3330 return $ret;
3331}
3332
091768aa
DO
3333// return TRUE, if given VLAN ID belongs to one of filter's ranges
3334function matchVLANFilter ($vlan_id, $vfilter)
3335{
3336 foreach ($vfilter as $range)
3337 if ($range['from'] <= $vlan_id and $vlan_id <= $range['to'])
3338 return TRUE;
3339 return FALSE;
3340}
3341
651c0804 3342function generate8021QDeployOps ($vswitch, $device_vlanlist, $before, $changes)
bcd14540 3343{
651c0804
AA
3344 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3345 $employed_vlans = getEmployedVlans ($vswitch['object_id'], $domain_vlanlist);
3346
bcd14540
DO
3347 // only ignore VLANs, which exist and are explicitly shown as "alien"
3348 $old_managed_vlans = array();
bcd14540
DO
3349 foreach ($device_vlanlist as $vlan_id)
3350 if
3351 (
3352 !array_key_exists ($vlan_id, $domain_vlanlist) or
3353 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien'
3354 )
3355 $old_managed_vlans[] = $vlan_id;
3356 $ports_to_do = array();
651c0804
AA
3357 $ports_to_do_queue1 = array();
3358 $ports_to_do_queue2 = array();
bcd14540
DO
3359 $after = $before;
3360 foreach ($changes as $port_name => $port)
3361 {
651c0804 3362 $changeset = array
bcd14540
DO
3363 (
3364 'old_mode' => $before[$port_name]['mode'],
3365 'old_allowed' => $before[$port_name]['allowed'],
3366 'old_native' => $before[$port_name]['native'],
3367 'new_mode' => $port['mode'],
3368 'new_allowed' => $port['allowed'],
3369 'new_native' => $port['native'],
3370 );
651c0804
AA
3371 // put the ports with employed vlans first, the others - below them
3372 if (! count (array_intersect ($changeset['old_allowed'], $employed_vlans)))
3373 $ports_to_do_queue2[$port_name] = $changeset;
3374 else
3375 $ports_to_do_queue1[$port_name] = $changeset;
bcd14540
DO
3376 $after[$port_name] = $port;
3377 }
f95bc64f
DO
3378 # Two arrays without common keys get merged with "+" operator just fine,
3379 # with an important difference from array_merge() in that the latter
3380 # renumbers numeric keys, and "+" does not. This matters, when port name
3381 # is a number (like in XOS12 system).
3382 $ports_to_do = $ports_to_do_queue1 + $ports_to_do_queue2;
bcd14540
DO
3383 // New VLAN table is a union of:
3384 // 1. all compulsory VLANs
3385 // 2. all "current" non-alien allowed VLANs of those ports, which are left
3386 // intact (regardless if a VLAN exists in VLAN domain, but looking,
3387 // if it is present in device's own VLAN table)
3388 // 3. all "new" allowed VLANs of those ports, which we do "push" now
3389 // Like for old_managed_vlans, a VLANs is never listed, only if it
3390 // exists and belongs to "alien" type.
3391 $new_managed_vlans = array();
108a028f
AA
3392 // We need to count down the number of ports still using specific vlan
3393 // in order to delete it from device as soon as vlan will be removed from the last port
3394 // This array tracks port count:
3395 // * keys are vlan_id's;
3396 // * values are the number of changed ports which were using this vlan in old configuration
3397 $used_vlans = array();
bcd14540
DO
3398 // 1
3399 foreach ($domain_vlanlist as $vlan_id => $vlan)
3400 if ($vlan['vlan_type'] == 'compulsory')
3401 $new_managed_vlans[] = $vlan_id;
3402 // 2
3403 foreach ($before as $port_name => $port)
3404 if (!array_key_exists ($port_name, $changes))
3405 foreach ($port['allowed'] as $vlan_id)
3406 {
3407 if (in_array ($vlan_id, $new_managed_vlans))
3408 continue;
3409 if
3410 (
3411 array_key_exists ($vlan_id, $domain_vlanlist) and
3412 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien'
3413 )
3414 continue;
3415 if (in_array ($vlan_id, $device_vlanlist))
3416 $new_managed_vlans[] = $vlan_id;
3417 }
108a028f
AA
3418 else
3419 foreach ($port['allowed'] as $vlan_id)
3420 {
3421 if (!array_key_exists ($vlan_id, $used_vlans))
3422 $used_vlans[$vlan_id] = 0;
3423 $used_vlans[$vlan_id]++;
3424 }
bcd14540
DO
3425 // 3
3426 foreach ($changes as $port)
10b6b476 3427 foreach ($port['allowed'] as $vlan_id)
bcd14540
DO
3428 if
3429 (
e405f256 3430 isset ($domain_vlanlist[$vlan_id]) and
bcd14540
DO
3431 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' and
3432 !in_array ($vlan_id, $new_managed_vlans)
3433 )
3434 $new_managed_vlans[] = $vlan_id;
108a028f
AA
3435
3436 $vlans_to_add = array_diff ($new_managed_vlans, $old_managed_vlans);
3437 $vlans_to_del = array_diff ($old_managed_vlans, $new_managed_vlans);
bcd14540 3438 $crq = array();
cb021344
AA
3439
3440 // destroy unused VLANs on device
3441 $deleted_vlans = array_diff ($vlans_to_del, array_keys ($used_vlans));
3442 foreach ($deleted_vlans as $vlan_id)
3443 $crq[] = array
3444 (
3445 'opcode' => 'destroy VLAN',
3446 'arg1' => $vlan_id,
3447 );
3448 $vlans_to_del = array_diff ($vlans_to_del, $deleted_vlans);
108a028f
AA
3449
3450 foreach (sortPortList ($ports_to_do) as $port_name => $port)
3451 {
3452 // Before removing each old VLAN as such it is necessary to unassign
3453 // ports from it (to remove VLAN from each ports' list of "allowed"
3454 // VLANs). This change in turn requires, that a port's "native"
3455 // VLAN isn't set to the one being removed from its "allowed" list.
bcd14540
DO
3456 switch ($port['old_mode'] . '->' . $port['new_mode'])
3457 {
3458 case 'trunk->trunk':
3459 // "old" native is set and differs from the "new" native
3460 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3461 $crq[] = array
3462 (
3463 'opcode' => 'unset native',
3464 'arg1' => $port_name,
3465 'arg2' => $port['old_native'],
3466 );
651c0804
AA
3467 $vlans_to_remove = array_diff ($port['old_allowed'], $port['new_allowed']);
3468 $queues = array();
3469 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first
3470 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards
3471 foreach ($queues as $queue)
3472 if (! empty ($queue))
108a028f 3473 {
651c0804
AA
3474 $crq[] = array
3475 (
3476 'opcode' => 'rem allowed',
3477 'port' => $port_name,
3478 'vlans' => $queue,
3479 );
108a028f
AA
3480 foreach ($queue as $vlan_id)
3481 $used_vlans[$vlan_id]--;
3482 }
bcd14540
DO
3483 break;
3484 case 'access->access':
3485 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
108a028f 3486 {
bcd14540
DO
3487 $crq[] = array
3488 (
3489 'opcode' => 'unset access',
3490 'arg1' => $port_name,
3491 'arg2' => $port['old_native'],
3492 );
108a028f
AA
3493 $used_vlans[$port['old_native']]--;
3494 }
bcd14540
DO
3495 break;
3496 case 'access->trunk':
3497 $crq[] = array
3498 (
3499 'opcode' => 'unset access',
3500 'arg1' => $port_name,
3501 'arg2' => $port['old_native'],
3502 );
108a028f 3503 $used_vlans[$port['old_native']]--;
bcd14540
DO
3504 break;
3505 case 'trunk->access':
453c8fb8
DO
3506 if ($port['old_native'])
3507 $crq[] = array
3508 (
3509 'opcode' => 'unset native',
3510 'arg1' => $port_name,
3511 'arg2' => $port['old_native'],
3512 );
651c0804
AA
3513 $vlans_to_remove = $port['old_allowed'];
3514 $queues = array();
3515 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first
3516 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards
3517 foreach ($queues as $queue)
3518 if (! empty ($queue))
108a028f 3519 {
651c0804
AA
3520 $crq[] = array
3521 (
3522 'opcode' => 'rem allowed',
3523 'port' => $port_name,
3524 'vlans' => $queue,
3525 );
108a028f
AA
3526 foreach ($queue as $vlan_id)
3527 $used_vlans[$vlan_id]--;
3528 }
bcd14540
DO
3529 break;
3530 default:
164ba494 3531 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
bcd14540 3532 }
108a028f
AA
3533
3534 // destroy unneeded VLANs on device
3535 $deleted_vlans = array();
3536 foreach ($vlans_to_del as $vlan_id)
3537 if ($used_vlans[$vlan_id] == 0)
3538 {
3539 $crq[] = array
3540 (
3541 'opcode' => 'destroy VLAN',
3542 'arg1' => $vlan_id,
3543 );
108a028f
AA
3544 $deleted_vlans[] = $vlan_id;
3545 }
3546 $vlans_to_del = array_diff ($vlans_to_del, $deleted_vlans);
3547
3548 // create new VLANs on device
3549 $added_vlans = array_intersect ($vlans_to_add, $port['new_allowed']);
3550 foreach ($added_vlans as $vlan_id)
3551 $crq[] = array
3552 (
3553 'opcode' => 'create VLAN',
3554 'arg1' => $vlan_id,
3555 );
3556 $vlans_to_add = array_diff ($vlans_to_add, $added_vlans);
3557
3558 // change port mode if needed
3559 if ($port['old_mode'] != $port['new_mode'])
3560 $crq[] = array
3561 (
3562 'opcode' => 'set mode',
3563 'arg1' => $port_name,
3564 'arg2' => $port['new_mode'],
3565 );
3566
3567 // Now, when all new VLANs are created (queued), it is safe to assign (queue)
3568 // ports to the new VLANs.
bcd14540
DO
3569 switch ($port['old_mode'] . '->' . $port['new_mode'])
3570 {
3571 case 'trunk->trunk':
3572 // For each allowed VLAN, which is present on the "new" list and missing from
3573 // the "old" one, queue a command to assign current port to that VLAN.
cc6a6c4e 3574 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed'])))
bcd14540
DO
3575 $crq[] = array
3576 (
3577 'opcode' => 'add allowed',
cc6a6c4e
DO
3578 'port' => $port_name,
3579 'vlans' => $tmp,
bcd14540
DO
3580 );
3581 // One of the "allowed" VLANs for this port may probably be "native".
3582 // "new native" is set and differs from "old native"
3583 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3584 $crq[] = array
3585 (
3586 'opcode' => 'set native',
3587 'arg1' => $port_name,
3588 'arg2' => $port['new_native'],
3589 );
3590 break;
3591 case 'access->access':
3592 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3593 $crq[] = array
3594 (
3595 'opcode' => 'set access',
3596 'arg1' => $port_name,
3597 'arg2' => $port['new_native'],
3598 );
3599 break;
3600 case 'access->trunk':
cc6a6c4e 3601 if (count ($port['new_allowed']))
bcd14540
DO
3602 $crq[] = array
3603 (
3604 'opcode' => 'add allowed',
cc6a6c4e
DO
3605 'port' => $port_name,
3606 'vlans' => $port['new_allowed'],
bcd14540 3607 );
453c8fb8
DO
3608 if ($port['new_native'])
3609 $crq[] = array
3610 (
3611 'opcode' => 'set native',
3612 'arg1' => $port_name,
3613 'arg2' => $port['new_native'],
3614 );
bcd14540
DO
3615 break;
3616 case 'trunk->access':
bcd14540
DO
3617 $crq[] = array
3618 (
3619 'opcode' => 'set access',
3620 'arg1' => $port_name,
3621 'arg2' => $port['new_native'],
3622 );
3623 break;
3624 default:
164ba494 3625 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
bcd14540 3626 }
108a028f 3627 }
cb021344
AA
3628
3629 // add the rest of VLANs to device (compulsory VLANs)
108a028f
AA
3630 foreach ($vlans_to_add as $vlan_id)
3631 $crq[] = array
3632 (
3633 'opcode' => 'create VLAN',
3634 'arg1' => $vlan_id,
3635 );
3636
0b24ab60
AA
3637 return $crq;
3638}
3639
3640function exportSwitch8021QConfig
3641(
3642 $vswitch,
3643 $device_vlanlist,
3644 $before,
8d6c3668
AA
3645 $changes,
3646 $vlan_names
0b24ab60
AA
3647)
3648{
651c0804 3649 $crq = generate8021QDeployOps ($vswitch, $device_vlanlist, $before, $changes);
5017142e
DO
3650 if (count ($crq))
3651 {
3652 array_unshift ($crq, array ('opcode' => 'begin configuration'));
3653 $crq[] = array ('opcode' => 'end configuration');
3654 if (considerConfiguredConstraint (spotEntity ('object', $vswitch['object_id']), '8021Q_WRI_AFTER_CONFT_LISTSRC'))
3655 $crq[] = array ('opcode' => 'save configuration');
8d6c3668 3656 setDevice8021QConfig ($vswitch['object_id'], $crq, $vlan_names);
5017142e 3657 }
bcd14540
DO
3658 return count ($crq);
3659}
3660
af204724 3661// filter list of changed ports to cancel changes forbidden by VST and domain
af204724
DO
3662function filter8021QChangeRequests
3663(
3664 $domain_vlanlist,
3665 $before, // current saved configuration of all ports
3666 $changes // changed ports with VST markup
3667)
3668{
3669 $domain_immune_vlans = array();
3670 foreach ($domain_vlanlist as $vlan_id => $vlan)
3671 if ($vlan['vlan_type'] == 'alien')
3672 $domain_immune_vlans[] = $vlan_id;
3673 $ret = array();
3674 foreach ($changes as $port_name => $port)
3675 {
ec523868
DO
3676 // VST violation ?
3677 if (!goodModeForVSTRole ($port['mode'], $port['vst_role']))
3678 continue; // ignore change request
af204724 3679 // find and cancel any changes regarding immune VLANs
ec523868 3680 switch ($port['mode'])
034a61c9
DO
3681 {