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