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