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