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