r4456 make $delayauth array 1-dimensional
[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
7ddbcf59 2510// Tell, if a constraint from config option permits given record.
99446891 2511// An undefined $cell means current context.
212c9d8a 2512function considerConfiguredConstraint ($cell, $varname)
c6bc0ac5 2513{
7ddbcf59 2514 if (!strlen (getConfigVar ($varname)))
c6bc0ac5 2515 return TRUE; // no restriction
7ddbcf59
DO
2516 global $parseCache;
2517 if (!isset ($parseCache[$varname]))
2518 // getConfigVar() doesn't re-read the value from DB because of its
2519 // own cache, so there is no race condition here between two calls.
2520 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2521 if ($parseCache[$varname]['result'] != 'ACK')
2522 return FALSE; // constraint set, but cannot be used due to compilation error
99446891
AA
2523 if (isset ($cell))
2524 return judgeCell ($cell, $parseCache[$varname]['load']);
2525 else
2526 return judgeContext ($parseCache[$varname]['load']);
c6bc0ac5
DO
2527}
2528
682e77d1
DO
2529// Tell, if the given arbitrary RackCode text addresses the given record
2530// (an empty text matches any record).
68292868 2531// An undefined $cell means current context.
682e77d1
DO
2532function considerGivenConstraint ($cell, $filtertext)
2533{
2534 if ($filtertext == '')
2535 return TRUE;
2536 $parse = spotPayload ($filtertext, 'SYNT_EXPR');
2537 if ($parse['result'] != 'ACK')
2538 throw new InvalidRequestArgException ('filtertext', $filtertext, 'RackCode parsing error');
68292868
AA
2539 if (isset ($cell))
2540 return judgeCell ($cell, $parse['load']);
2541 else
2542 return judgeContext ($parse['load']);
682e77d1
DO
2543}
2544
17112e81
DO
2545// Return list of records in the given realm, which conform to
2546// the given RackCode expression. If the realm is unknown or text
2547// doesn't validate as a RackCode expression, return NULL.
2548// Otherwise (successful scan) return a list of all matched
2549// records, even if the list is empty (array() !== NULL). If the
2550// text is an empty string, return all found records in the given
2551// realm.
2552function scanRealmByText ($realm = NULL, $ftext = '')
2553{
2554 switch ($realm)
2555 {
2556 case 'object':
95e4b0f5 2557 case 'rack':
17112e81
DO
2558 case 'user':
2559 case 'ipv4net':
7fa4b0e4 2560 case 'ipv6net':
17112e81 2561 case 'file':
39776127 2562 case 'ipv4vs':
41780975 2563 case 'ipv4rspool':
17112e81
DO
2564 if (!strlen ($ftext = trim ($ftext)))
2565 $fexpr = array();
2566 else
2567 {
2568 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2569 if ($fparse['result'] != 'ACK')
2570 return NULL;
2571 $fexpr = $fparse['load'];
2572 }
2573 return filterCellList (listCells ($realm), $fexpr);
2574 default:
c5f84f48 2575 throw new InvalidArgException ('$realm', $realm);
17112e81
DO
2576 }
2577
2578}
39776127
DO
2579
2580function getIPv4VSOptions ()
2581{
2582 $ret = array();
2583 foreach (listCells ('ipv4vs') as $vsid => $vsinfo)
59a83bd8 2584 $ret[$vsid] = $vsinfo['dname'] . (!strlen ($vsinfo['name']) ? '' : " (${vsinfo['name']})");
39776127
DO
2585 return $ret;
2586}
2587
41780975
DO
2588function getIPv4RSPoolOptions ()
2589{
2590 $ret = array();
2591 foreach (listCells ('ipv4rspool') as $pool_id => $poolInfo)
2592 $ret[$pool_id] = $poolInfo['name'];
d16af52f
DO
2593 return $ret;
2594}
2595
cc3d6915
DO
2596function getVSTOptions()
2597{
2598 $ret = array();
2599 foreach (listCells ('vst') as $vst)
2600 $ret[$vst['id']] = niftyString ($vst['description'], 30, FALSE);
2601 return $ret;
2602}
2603
56a797ef
DO
2604// Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2605function dump ($var)
2606{
2607 echo '<div align=left><pre>';
2608 print_r ($var);
2609 echo '</pre></div>';
2610}
2611
9e51318b
DO
2612function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2613{
2614 global $taglist;
2615 // first build top-N chart...
2616 $toplist = array();
2617 foreach ($taglist as $taginfo)
2618 if (isset ($taginfo['refcnt'][$realm]))
2619 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2620 arsort ($toplist, SORT_NUMERIC);
2621 $ret = array();
2622 $done = 0;
2623 foreach (array_keys ($toplist) as $tag_id)
2624 {
2625 $ret[$tag_id] = $taglist[$tag_id];
2626 if (++$done == $limit)
2627 break;
2628 }
2629 // ...then make sure, that every item of the special list is shown
2630 // (using the same sort order)
2631 $extra = array();
2632 foreach ($special_tags as $taginfo)
2633 if (!array_key_exists ($taginfo['id'], $ret))
2634 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2635 arsort ($extra, SORT_NUMERIC);
2636 foreach (array_keys ($extra) as $tag_id)
2637 $ret[] = $taglist[$tag_id];
2638 return $ret;
2639}
2640
7fa7047a
DO
2641function decodeObjectType ($objtype_id, $style = 'r')
2642{
2643 static $types = array();
2644 if (!count ($types))
2645 $types = array
2646 (
2647 'r' => readChapter (CHAP_OBJTYPE),
2648 'a' => readChapter (CHAP_OBJTYPE, 'a'),
2649 'o' => readChapter (CHAP_OBJTYPE, 'o')
2650 );
2651 return $types[$style][$objtype_id];
2652}
2653
0df8c52b
DO
2654function isolatedPermission ($p, $t, $cell)
2655{
2656 // This function is called from both "file" page and a number of other pages,
2657 // which have already fixed security context and authorized the user for it.
2658 // OTOH, it is necessary here to authorize against the current file, which
2659 // means saving the current context and building a new one.
2660 global
2661 $expl_tags,
2662 $impl_tags,
2663 $target_given_tags,
2664 $auto_tags;
2665 // push current context
2666 $orig_expl_tags = $expl_tags;
2667 $orig_impl_tags = $impl_tags;
2668 $orig_target_given_tags = $target_given_tags;
2669 $orig_auto_tags = $auto_tags;
2670 // retarget
2671 fixContext ($cell);
2672 // remember decision
2673 $ret = permitted ($p, $t);
2674 // pop context
2675 $expl_tags = $orig_expl_tags;
2676 $impl_tags = $orig_impl_tags;
2677 $target_given_tags = $orig_target_given_tags;
2678 $auto_tags = $orig_auto_tags;
2679 return $ret;
2680}
2681
3153a326
DO
2682function getPortListPrefs()
2683{
2684 $ret = array();
2685 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
2686 $ret['iif_pick'] = 1;
2687 $ret['oif_picks'] = array();
2688 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
2689 {
2690 $tmp = explode ('=', trim ($tmp));
2691 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
2692 $ret['oif_picks'][$tmp[0]] = $tmp[1];
2693 }
2694 // enforce default value
2695 if (!array_key_exists (1, $ret['oif_picks']))
2696 $ret['oif_picks'][1] = 24;
2697 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
2698 return $ret;
2699}
2700
2dfa1b73
DO
2701// Return data for printNiftySelect() with port type options. All OIF options
2702// for the default IIF will be shown, but only the default OIFs will be present
2703// for each other IIFs. IIFs, for which there is no default OIF, will not
2704// be listed.
2705// This SELECT will be used for the "add new port" form.
2706function getNewPortTypeOptions()
2707{
2708 $ret = array();
2709 $prefs = getPortListPrefs();
2710 foreach (getPortInterfaceCompat() as $row)
2711 {
2712 if ($row['iif_id'] == $prefs['iif_pick'])
2713 $optgroup = $row['iif_name'];
2714 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
2715 $optgroup = 'other';
2716 else
2717 continue;
2718 if (!array_key_exists ($optgroup, $ret))
2719 $ret[$optgroup] = array();
2720 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
2721 }
2722 return $ret;
2723}
2724
8198f2c6
DO
2725// Return a serialized version of VLAN configuration for a port.
2726// If a native VLAN is defined, print it first. All other VLANs
d0dadd80
DO
2727// are tagged and are listed after a plus sign. When no configuration
2728// is set for a port, return "default" string.
4f860864 2729function serializeVLANPack ($vlanport)
8198f2c6 2730{
4f860864
DO
2731 if (!array_key_exists ('mode', $vlanport))
2732 return 'error';
2733 switch ($vlanport['mode'])
2734 {
2735 case 'none':
2736 return 'none';
2737 case 'access':
2738 $ret = 'A';
2739 break;
2740 case 'trunk':
2741 $ret = 'T';
2742 break;
36a70b71
DO
2743 case 'uplink':
2744 $ret = 'U';
2745 break;
2746 case 'downlink':
2747 $ret = 'D';
2748 break;
4f860864
DO
2749 default:
2750 return 'error';
2751 }
0ab9ec25
AA
2752 if ($vlanport['native'])
2753 $ret .= $vlanport['native'];
e55917ed
AA
2754 $tagged_bits = groupIntsToRanges ($vlanport['allowed'], $vlanport['native']);
2755 if (count ($tagged_bits))
2756 $ret .= '+' . implode (', ', $tagged_bits);
2757 return strlen ($ret) ? $ret : 'default';
2758}
2759
2760function groupIntsToRanges ($list, $exclude_value = NULL)
2761{
2762 $result = array();
2763 sort ($list);
e9be55de 2764 $id_from = $id_to = 0;
e55917ed
AA
2765 $list[] = -1;
2766 foreach ($list as $next_id)
2767 if (!isset ($exclude_value) or $next_id != $exclude_value)
2768 if ($id_to && $next_id == $id_to + 1)
2769 $id_to = $next_id; // merge
2770 else
e9be55de 2771 {
e55917ed
AA
2772 if ($id_to)
2773 $result[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}"; // flush
2774 $id_from = $id_to = $next_id; // start next pair
e9be55de 2775 }
e55917ed 2776 return $result;
8198f2c6
DO
2777}
2778
8846b060
DO
2779// Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
2780// return the numbers as an array of two.
2781function decodeVLANCK ($string)
2782{
2783 $matches = array();
2784 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
2785 throw new InvalidArgException ('VLAN compound key', $string);
2786 return array ($matches[1], $matches[2]);
2787}
2788
ce85f5c8
DO
2789// Return VLAN name formatted for HTML output (note, that input
2790// argument comes from database unescaped).
a72aa89f
DO
2791function formatVLANName ($vlaninfo, $context = 'markup long')
2792{
2793 switch ($context)
2794 {
2795 case 'option':
2796 $ret = $vlaninfo['vlan_id'];
2797 if ($vlaninfo['vlan_descr'] != '')
2798 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
2799 return $ret;
2800 case 'label':
2801 $ret = $vlaninfo['vlan_id'];
2802 if ($vlaninfo['vlan_descr'] != '')
2803 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2804 return $ret;
0812b506
DO
2805 case 'plain long':
2806 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2807 if ($vlaninfo['vlan_descr'] != '')
1f54e1ba
DO
2808 $ret .= ' (' . niftyString ($vlaninfo['vlan_descr'], 20, FALSE) . ')';
2809 return $ret;
2810 case 'hyperlink':
2811 $ret = '<a href="';
2812 $ret .= makeHref (array ('page' => 'vlan', 'vlan_ck' => $vlaninfo['domain_id'] . '-' . $vlaninfo['vlan_id']));
2813 $ret .= '">' . formatVLANName ($vlaninfo, 'markup long') . '</a>';
0812b506 2814 return $ret;
a72aa89f
DO
2815 case 'markup long':
2816 default:
2817 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2818 $ret .= ' @' . niftyString ($vlaninfo['domain_descr']);
2819 if ($vlaninfo['vlan_descr'] != '')
2820 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2821 return $ret;
2822 }
ce85f5c8
DO
2823}
2824
e9d357e1
DO
2825// map interface name
2826function ios12ShortenIfName ($ifname)
2827{
680c6167
AA
2828 if (preg_match ('@^eth-trunk(\d+)$@i', $ifname, $m))
2829 return "Eth-Trunk${m[1]}";
87615093 2830 $ifname = preg_replace ('@^Eth(?:ernet)?(.+)$@', 'e\\1', $ifname);
e9d357e1 2831 $ifname = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $ifname);
65e557dd 2832 $ifname = preg_replace ('@^(?:GigabitEthernet|GE)(.+)$@', 'gi\\1', $ifname);
e9d357e1
DO
2833 $ifname = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $ifname);
2834 $ifname = preg_replace ('@^Port-channel(.+)$@', 'po\\1', $ifname);
65e557dd 2835 $ifname = preg_replace ('@^(?:XGigabitEthernet|XGE)(.+)$@', 'xg\\1', $ifname);
1ebbf889 2836 $ifname = strtolower ($ifname);
e9d357e1
DO
2837 return $ifname;
2838}
2839
f10dd5cc
DO
2840function iosParseVLANString ($string)
2841{
2842 $ret = array();
2843 foreach (explode (',', $string) as $item)
2844 {
2845 $matches = array();
735e323f 2846 $item = trim ($item, ' ');
f10dd5cc
DO
2847 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches))
2848 $ret[] = $matches[1];
2849 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches))
2850 $ret = array_merge ($ret, range ($matches[1], $matches[2]));
2851 }
2852 return $ret;
2853}
2854
bb35bb93
DO
2855// Scan given array and return the key, which addresses the first item
2856// with requested column set to given value (or NULL if there is none such).
19350222
DO
2857// Note that 0 and NULL mean completely different things and thus
2858// require strict checking (=== and !===).
dbc00990
DO
2859function scanArrayForItem ($table, $scan_column, $scan_value)
2860{
2861 foreach ($table as $key => $row)
2862 if ($row[$scan_column] == $scan_value)
2863 return $key;
2864 return NULL;
2865}
2866
66658512
DO
2867// Return TRUE, if every value of A1 is present in A2 and vice versa,
2868// regardless of each array's sort order and indexing.
2869function array_values_same ($a1, $a2)
2870{
2871 return !count (array_diff ($a1, $a2)) and !count (array_diff ($a2, $a1));
2872}
2873
1ce258f7
DO
2874// Use the VLAN switch template to set VST role for each port of
2875// the provided list. Return resulting list.
bc254f49 2876function apply8021QOrder ($vst_id, $portlist)
25930440 2877{
cc3d6915
DO
2878 $vst = spotEntity ('vst', $vst_id);
2879 amplifyCell ($vst);
1ce258f7
DO
2880 foreach (array_keys ($portlist) as $port_name)
2881 {
2882 foreach ($vst['rules'] as $rule)
2883 if (preg_match ($rule['port_pcre'], $port_name))
25930440
DO
2884 {
2885 $portlist[$port_name]['vst_role'] = $rule['port_role'];
091768aa 2886 $portlist[$port_name]['wrt_vlans'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']);
1ce258f7 2887 continue 2;
25930440 2888 }
1ce258f7
DO
2889 $portlist[$port_name]['vst_role'] = 'none';
2890 }
25930440
DO
2891 return $portlist;
2892}
2893
091768aa
DO
2894// return a sequence of ranges for given string form and port role
2895function buildVLANFilter ($role, $string)
be28b696 2896{
091768aa 2897 // set base
1ce258f7 2898 switch ($role)
be28b696 2899 {
1ce258f7
DO
2900 case 'access': // 1-4094
2901 $min = VLAN_MIN_ID;
2902 $max = VLAN_MAX_ID;
2903 break;
2904 case 'trunk': // 2-4094
2905 case 'uplink':
65da0c15 2906 case 'downlink':
ec523868 2907 case 'anymode':
1ce258f7
DO
2908 $min = VLAN_MIN_ID + 1;
2909 $max = VLAN_MAX_ID;
2910 break;
65da0c15 2911 default: // none
1ce258f7 2912 return array();
be28b696 2913 }
091768aa
DO
2914 if ($string == '') // fast track
2915 return array (array ('from' => $min, 'to' => $max));
2916 // transform
2917 $vlanidlist = array();
1ce258f7
DO
2918 foreach (iosParseVLANString ($string) as $vlan_id)
2919 if ($min <= $vlan_id and $vlan_id <= $max)
091768aa 2920 $vlanidlist[] = $vlan_id;
cc6a6c4e
DO
2921 return listToRanges ($vlanidlist);
2922}
2923
2924// pack set of integers into list of integer ranges
2925// e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11))
ec523868
DO
2926// The second argument, when it is different from 0, limits amount of
2927// items in each generated range.
2928function listToRanges ($vlanidlist, $limit = 0)
cc6a6c4e 2929{
091768aa
DO
2930 sort ($vlanidlist);
2931 $ret = array();
2932 $from = $to = NULL;
2933 foreach ($vlanidlist as $vlan_id)
2934 if ($from == NULL)
ec523868
DO
2935 {
2936 if ($limit == 1)
2937 $ret[] = array ('from' => $vlan_id, 'to' => $vlan_id);
2938 else
2939 $from = $to = $vlan_id;
2940 }
091768aa 2941 elseif ($to + 1 == $vlan_id)
ec523868 2942 {
091768aa 2943 $to = $vlan_id;
ec523868
DO
2944 if ($to - $from + 1 == $limit)
2945 {
2946 // cut accumulated range and start over
2947 $ret[] = array ('from' => $from, 'to' => $to);
2948 $from = $to = NULL;
2949 }
2950 }
091768aa
DO
2951 else
2952 {
2953 $ret[] = array ('from' => $from, 'to' => $to);
2954 $from = $to = $vlan_id;
2955 }
2956 if ($from != NULL)
2957 $ret[] = array ('from' => $from, 'to' => $to);
be28b696
DO
2958 return $ret;
2959}
2960
091768aa
DO
2961// return TRUE, if given VLAN ID belongs to one of filter's ranges
2962function matchVLANFilter ($vlan_id, $vfilter)
2963{
2964 foreach ($vfilter as $range)
2965 if ($range['from'] <= $vlan_id and $vlan_id <= $range['to'])
2966 return TRUE;
2967 return FALSE;
2968}
2969
651c0804 2970function generate8021QDeployOps ($vswitch, $device_vlanlist, $before, $changes)
bcd14540 2971{
651c0804
AA
2972 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
2973 $employed_vlans = getEmployedVlans ($vswitch['object_id'], $domain_vlanlist);
2974
bcd14540
DO
2975 // only ignore VLANs, which exist and are explicitly shown as "alien"
2976 $old_managed_vlans = array();
bcd14540
DO
2977 foreach ($device_vlanlist as $vlan_id)
2978 if
2979 (
2980 !array_key_exists ($vlan_id, $domain_vlanlist) or
2981 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien'
2982 )
2983 $old_managed_vlans[] = $vlan_id;
2984 $ports_to_do = array();
651c0804
AA
2985 $ports_to_do_queue1 = array();
2986 $ports_to_do_queue2 = array();
bcd14540
DO
2987 $after = $before;
2988 foreach ($changes as $port_name => $port)
2989 {
651c0804 2990 $changeset = array
bcd14540
DO
2991 (
2992 'old_mode' => $before[$port_name]['mode'],
2993 'old_allowed' => $before[$port_name]['allowed'],
2994 'old_native' => $before[$port_name]['native'],
2995 'new_mode' => $port['mode'],
2996 'new_allowed' => $port['allowed'],
2997 'new_native' => $port['native'],
2998 );
651c0804
AA
2999 // put the ports with employed vlans first, the others - below them
3000 if (! count (array_intersect ($changeset['old_allowed'], $employed_vlans)))
3001 $ports_to_do_queue2[$port_name] = $changeset;
3002 else
3003 $ports_to_do_queue1[$port_name] = $changeset;
bcd14540
DO
3004 $after[$port_name] = $port;
3005 }
651c0804 3006 $ports_to_do = array_merge ($ports_to_do_queue1, $ports_to_do_queue2);
bcd14540
DO
3007 // New VLAN table is a union of:
3008 // 1. all compulsory VLANs
3009 // 2. all "current" non-alien allowed VLANs of those ports, which are left
3010 // intact (regardless if a VLAN exists in VLAN domain, but looking,
3011 // if it is present in device's own VLAN table)
3012 // 3. all "new" allowed VLANs of those ports, which we do "push" now
3013 // Like for old_managed_vlans, a VLANs is never listed, only if it
3014 // exists and belongs to "alien" type.
3015 $new_managed_vlans = array();
3016 // 1
3017 foreach ($domain_vlanlist as $vlan_id => $vlan)
3018 if ($vlan['vlan_type'] == 'compulsory')
3019 $new_managed_vlans[] = $vlan_id;
3020 // 2
3021 foreach ($before as $port_name => $port)
3022 if (!array_key_exists ($port_name, $changes))
3023 foreach ($port['allowed'] as $vlan_id)
3024 {
3025 if (in_array ($vlan_id, $new_managed_vlans))
3026 continue;
3027 if
3028 (
3029 array_key_exists ($vlan_id, $domain_vlanlist) and
3030 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien'
3031 )
3032 continue;
3033 if (in_array ($vlan_id, $device_vlanlist))
3034 $new_managed_vlans[] = $vlan_id;
3035 }
3036 // 3
3037 foreach ($changes as $port)
10b6b476 3038 foreach ($port['allowed'] as $vlan_id)
bcd14540
DO
3039 if
3040 (
e405f256 3041 isset ($domain_vlanlist[$vlan_id]) and
bcd14540
DO
3042 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' and
3043 !in_array ($vlan_id, $new_managed_vlans)
3044 )
3045 $new_managed_vlans[] = $vlan_id;
3046 $crq = array();
3047 // Before removing each old VLAN as such it is necessary to unassign
3048 // ports from it (to remove VLAN from each ports' list of "allowed"
3049 // VLANs). This change in turn requires, that a port's "native"
3050 // VLAN isn't set to the one being removed from its "allowed" list.
3051 foreach ($ports_to_do as $port_name => $port)
3052 switch ($port['old_mode'] . '->' . $port['new_mode'])
3053 {
3054 case 'trunk->trunk':
3055 // "old" native is set and differs from the "new" native
3056 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3057 $crq[] = array
3058 (
3059 'opcode' => 'unset native',
3060 'arg1' => $port_name,
3061 'arg2' => $port['old_native'],
3062 );
651c0804
AA
3063 $vlans_to_remove = array_diff ($port['old_allowed'], $port['new_allowed']);
3064 $queues = array();
3065 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first
3066 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards
3067 foreach ($queues as $queue)
3068 if (! empty ($queue))
3069 $crq[] = array
3070 (
3071 'opcode' => 'rem allowed',
3072 'port' => $port_name,
3073 'vlans' => $queue,
3074 );
bcd14540
DO
3075 break;
3076 case 'access->access':
3077 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3078 $crq[] = array
3079 (
3080 'opcode' => 'unset access',
3081 'arg1' => $port_name,
3082 'arg2' => $port['old_native'],
3083 );
3084 break;
3085 case 'access->trunk':
3086 $crq[] = array
3087 (
3088 'opcode' => 'unset access',
3089 'arg1' => $port_name,
3090 'arg2' => $port['old_native'],
3091 );
3092 break;
3093 case 'trunk->access':
453c8fb8
DO
3094 if ($port['old_native'])
3095 $crq[] = array
3096 (
3097 'opcode' => 'unset native',
3098 'arg1' => $port_name,
3099 'arg2' => $port['old_native'],
3100 );
651c0804
AA
3101 $vlans_to_remove = $port['old_allowed'];
3102 $queues = array();
3103 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first
3104 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards
3105 foreach ($queues as $queue)
3106 if (! empty ($queue))
3107 $crq[] = array
3108 (
3109 'opcode' => 'rem allowed',
3110 'port' => $port_name,
3111 'vlans' => $queue,
3112 );
bcd14540
DO
3113 break;
3114 default:
164ba494 3115 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
bcd14540
DO
3116 }
3117 // Now it is safe to unconfigure VLANs, which still exist on device,
3118 // but are not present on the "new" list.
3119 // FIXME: put all IDs into one pseudo-command to make it easier
3120 // for translators to create/destroy VLANs in batches, where
3121 // target platform allows them to do.
3122 foreach (array_diff ($old_managed_vlans, $new_managed_vlans) as $vlan_id)
3123 $crq[] = array
3124 (
3125 'opcode' => 'destroy VLAN',
3126 'arg1' => $vlan_id,
3127 );
3128 // Configure VLANs, which must be present on the device, but are not yet.
3129 foreach (array_diff ($new_managed_vlans, $old_managed_vlans) as $vlan_id)
3130 $crq[] = array
3131 (
3132 'opcode' => 'create VLAN',
3133 'arg1' => $vlan_id,
3134 );
3135 // Now, when all new VLANs are created (queued), it is safe to assign (queue)
3136 // ports to the new VLANs.
3137 foreach ($ports_to_do as $port_name => $port)
3138 switch ($port['old_mode'] . '->' . $port['new_mode'])
3139 {
3140 case 'trunk->trunk':
3141 // For each allowed VLAN, which is present on the "new" list and missing from
3142 // the "old" one, queue a command to assign current port to that VLAN.
cc6a6c4e 3143 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed'])))
bcd14540
DO
3144 $crq[] = array
3145 (
3146 'opcode' => 'add allowed',
cc6a6c4e
DO
3147 'port' => $port_name,
3148 'vlans' => $tmp,
bcd14540
DO
3149 );
3150 // One of the "allowed" VLANs for this port may probably be "native".
3151 // "new native" is set and differs from "old native"
3152 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3153 $crq[] = array
3154 (
3155 'opcode' => 'set native',
3156 'arg1' => $port_name,
3157 'arg2' => $port['new_native'],
3158 );
3159 break;
3160 case 'access->access':
3161 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3162 $crq[] = array
3163 (
3164 'opcode' => 'set access',
3165 'arg1' => $port_name,
3166 'arg2' => $port['new_native'],
3167 );
3168 break;
3169 case 'access->trunk':
3170 $crq[] = array
3171 (
3172 'opcode' => 'set mode',
3173 'arg1' => $port_name,
3174 'arg2' => $port['new_mode'],
3175 );
cc6a6c4e 3176 if (count ($port['new_allowed']))
bcd14540
DO
3177 $crq[] = array
3178 (
3179 'opcode' => 'add allowed',
cc6a6c4e
DO
3180 'port' => $port_name,
3181 'vlans' => $port['new_allowed'],
bcd14540 3182 );
453c8fb8
DO
3183 if ($port['new_native'])
3184 $crq[] = array
3185 (
3186 'opcode' => 'set native',
3187 'arg1' => $port_name,
3188 'arg2' => $port['new_native'],
3189 );
bcd14540
DO
3190 break;
3191 case 'trunk->access':
3192 $crq[] = array
3193 (
3194 'opcode' => 'set mode',
3195 'arg1' => $port_name,
3196 'arg2' => $port['new_mode'],
3197 );
3198 $crq[] = array
3199 (
3200 'opcode' => 'set access',
3201 'arg1' => $port_name,
3202 'arg2' => $port['new_native'],
3203 );
3204 break;
3205 default:
164ba494 3206 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
bcd14540 3207 }
0b24ab60
AA
3208 return $crq;
3209}
3210
3211function exportSwitch8021QConfig
3212(
3213 $vswitch,
3214 $device_vlanlist,
3215 $before,
8d6c3668
AA
3216 $changes,
3217 $vlan_names
0b24ab60
AA
3218)
3219{
651c0804 3220 $crq = generate8021QDeployOps ($vswitch, $device_vlanlist, $before, $changes);
5017142e
DO
3221 if (count ($crq))
3222 {
3223 array_unshift ($crq, array ('opcode' => 'begin configuration'));
3224 $crq[] = array ('opcode' => 'end configuration');
3225 if (considerConfiguredConstraint (spotEntity ('object', $vswitch['object_id']), '8021Q_WRI_AFTER_CONFT_LISTSRC'))
3226 $crq[] = array ('opcode' => 'save configuration');
8d6c3668 3227 setDevice8021QConfig ($vswitch['object_id'], $crq, $vlan_names);
5017142e 3228 }
bcd14540
DO
3229 return count ($crq);
3230}
3231
af204724 3232// filter list of changed ports to cancel changes forbidden by VST and domain
af204724
DO
3233function filter8021QChangeRequests
3234(
3235 $domain_vlanlist,
3236 $before, // current saved configuration of all ports
3237 $changes // changed ports with VST markup
3238)
3239{
3240 $domain_immune_vlans = array();
3241 foreach ($domain_vlanlist as $vlan_id => $vlan)
3242 if ($vlan['vlan_type'] == 'alien')
3243 $domain_immune_vlans[] = $vlan_id;
3244 $ret = array();
3245 foreach ($changes as $port_name => $port)
3246 {
ec523868
DO
3247 // VST violation ?
3248 if (!goodModeForVSTRole ($port['mode'], $port['vst_role']))
3249 continue; // ignore change request
af204724 3250 // find and cancel any changes regarding immune VLANs
ec523868 3251 switch ($port['mode'])
034a61c9
DO
3252 {
3253 case 'access':
034a61c9 3254 foreach ($domain_immune_vlans as $immune)
af204724
DO
3255 // Reverting an attempt to set an access port from
3256 // "normal" VLAN to immune one (or vice versa) requires
3257 // special handling, becase the calling function has
3258 // discarded the old contents of 'allowed' for current port.
af204724
DO
3259 if
3260 (
3261 $before[$port_name]['native'] == $immune or
3262 $port['native'] == $immune
3263 )
3264 {
3265 $port['native'] = $before[$port_name]['native'];
3266 $port['allowed'] = array ($port['native']);
034a61c9
DO
3267 // Such reversal happens either once or never for an
3268 // access port.
3269 break;
af204724 3270 }
034a61c9
DO
3271 break;
3272 case 'trunk':
034a61c9 3273 foreach ($domain_immune_vlans as $immune)
af204724
DO
3274 if (in_array ($immune, $before[$port_name]['allowed'])) // was allowed before
3275 {
3276 if (!in_array ($immune, $port['allowed']))
3277 $port['allowed'][] = $immune; // restore
3278 if ($before[$port_name]['native'] == $immune) // and was native
3279 $port['native'] = $immune; // also restore
3280 }
3281 else // wasn't
3282 {
3283 if (in_array ($immune, $port['allowed']))
3284 unset ($port['allowed'][array_search ($immune, $port['allowed'])]); // cancel
3285 if ($port['native'] == $immune)
3286 $port['native'] = $before[$port_name]['native'];
3287 }
034a61c9
DO
3288 break;
3289 default:
ec523868 3290 throw new InvalidArgException ('mode', $port['mode']);
034a61c9
DO
3291 }
3292 // save work
af204724
DO
3293 $ret[$port_name] = $port;
3294 }
3295 return $ret;
3296}
3297
651c0804 3298function getEmployedVlans ($object_id, $domain_vlanlist)
4741e9c3 3299{
651c0804
AA
3300 $employed = array(); // keyed by vlan_id. Value is dummy int
3301 // find persistent VLANs in domain
3302 foreach ($domain_vlanlist as $vlan_id => $vlan)
3303 if ($vlan['vlan_type'] == 'compulsory')
3304 $employed[$vlan_id] = 1;
9ce5cf73
AA
3305
3306 // find VLANs for object's L3 allocations
3307 $cell = spotEntity ('object', $object_id);
3308 amplifyCell ($cell);
3309 foreach (array ('ipv4', 'ipv6') as $family)
3310 {
3311 $seen_nets = array();
3312 foreach ($cell[$family] as $ip => $allocation)
3313 {
3314 if ($family == 'ipv6')
3315 $ip = new IPv6Address ($ip);
3316 if ($net_id = ($family == 'ipv6' ? getIPv6AddressNetworkId ($ip) : getIPv4AddressNetworkId ($ip)))
3317 {
3318 if (! isset($seen_nets[$net_id]))
3319 $seen_nets[$net_id] = 1;
3320 else
3321 continue;
3322 $net = spotEntity ("${family}net", $net_id);
3323 amplifyCell ($net);
3324 foreach ($net['8021q'] as $vlan)
651c0804
AA
3325 if (! isset ($employed[$vlan['vlan_id']]))
3326 $employed[$vlan['vlan_id']] = 1;
9ce5cf73
AA
3327 }
3328 }
3329 }
651c0804
AA
3330 return array_keys ($employed);
3331}
9ce5cf73 3332
651c0804
AA
3333// take port list with order applied and return uplink ports in the same format
3334function produceUplinkPorts ($domain_vlanlist, $portlist, $object_id)
3335{
3336 $ret = array();
3337
3338 $employed = getEmployedVlans ($object_id, $domain_vlanlist);
ca7f0af4
DO
3339 foreach ($portlist as $port_name => $port)
3340 if ($port['vst_role'] != 'uplink')
3341 foreach ($port['allowed'] as $vlan_id)
3342 if (!in_array ($vlan_id, $employed))
3343 $employed[] = $vlan_id;
651c0804 3344
ca7f0af4
DO
3345 foreach ($portlist as $port_name => $port)
3346 if ($port['vst_role'] == 'uplink')
3347 {
1ce258f7
DO
3348 $employed_here = array();
3349 foreach ($employed as $vlan_id)
091768aa 3350 if (matchVLANFilter ($vlan_id, $port['wrt_vlans']))
1ce258f7 3351 $employed_here[] = $vlan_id;
ca7f0af4
DO
3352 $ret[$port_name] = array
3353 (
034a61c9 3354 'vst_role' => 'uplink',
ca7f0af4
DO
3355 'mode' => 'trunk',
3356 'allowed' => $employed_here,
3357 'native' => 0,
3358 );
3359 }
4741e9c3
DO
3360 return $ret;
3361}
3362
9c45ea37
DO
3363function same8021QConfigs ($a, $b)
3364{
3365 return $a['mode'] == $b['mode'] &&
3366 array_values_same ($a['allowed'], $b['allowed']) &&
3367 $a['native'] == $b['native'];
3368}
3369
ec523868
DO
3370// Return TRUE, if the port can be edited by the user.
3371function editable8021QPort ($port)
3372{
3373 return in_array ($port['vst_role'], array ('trunk', 'access', 'anymode'));
3374}
3375
3376// Decide, whether the given 802.1Q port mode is permitted by
3377// VST port role.
3378function goodModeForVSTRole ($mode, $role)
3379{
3380 switch ($mode)
3381 {
3382 case 'access':
3383 return in_array ($role, array ('access', 'anymode'));
3384 case 'trunk':
3385 return in_array ($role, array ('trunk', 'uplink', 'downlink', 'anymode'));
3386 default:
3387 throw new InvalidArgException ('mode', $mode);
3388 }
3389}
3390
26dec09b
DO
3391/*
3392
3393Relation between desired (D), cached (C) and running (R)
3394copies of switch ports (P) list.
3395
3396 D C R
3397+---+ +---+ +---+
3398| P |-----| P |-? +--| P |
3399+---+ +---+ / +---+
3400| P |-----| P |--+ ?-| P |
3401+---+ +---+ +---+
3402| P |-----| P |-------| P |
3403+---+ +---+ +---+
3404| P |-----| P |--+ ?-| P |
3405+---+ +---+ \ +---+
3406| P |-----| P |--+ +--| P |
3407+---+ +---+ \ +---+
3408 +--| P |
3409 +---+
3410 ?-| P |
3411 +---+
3412
3413A modified local version of a port in "conflict" state ignores remote
3414changes until remote change maintains its difference. Once both edits
3415match, the local copy "locks" on the remote and starts tracking it.
3416
3417v
3418a "o" -- remOte version
3419l "l" -- Local version
3420u "b" -- Both versions
3421e
3422
3423^
3424| o b
3425| o
3426| l l l l l l b b
3427| o o b
3428| o b
3429|
3430| o
3431|
3432|
34330----------------------------------------------> time
3434
3435*/
be28b696
DO
3436function get8021QSyncOptions
3437(
3438 $vswitch,
3439 $D, // desired config
3440 $C, // cached config
3441 $R // running-config
3442)
3443{
3444 $default_port = array
3445 (
3446 'mode' => 'access',
3447 'allowed' => array (VLAN_DFL_ID),
3448 'native' => VLAN_DFL_ID,
3449 );
ab25b0d0 3450 $ret = array();
24832534 3451 $allports = array();
ab25b0d0 3452 foreach (array_unique (array_merge (array_keys ($C), array_keys ($R))) as $pn)
d3082829
DO
3453 $allports[$pn] = array();
3454 foreach (apply8021QOrder ($vswitch['template_id'], $allports) as $pn => $port)
be28b696 3455 {
7a375475
DO
3456 // catch anomalies early
3457 if ($port['vst_role'] == 'none')
3458 {
3459 if ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and !array_key_exists ($pn, $C))
3460 $ret[$pn] = array ('status' => 'none');
3461 else
3462 $ret[$pn] = array
3463 (
3464 'status' => 'martian_conflict',
3465 'left' => array_key_exists ($pn, $C) ? $C[$pn] : array ('mode' => 'none'),
3466 'right' => array_key_exists ($pn, $R) ? $R[$pn] : array ('mode' => 'none'),
3467 );
3468 continue;
3469 }
3470 elseif ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and array_key_exists ($pn, $C))
3471 {
3472 $ret[$pn] = array
3473 (
3474 'status' => 'martian_conflict',
3475 'left' => array_key_exists ($pn, $C) ? $C[$pn] : array ('mode' => 'none'),
3476 'right' => array_key_exists ($pn, $R) ? $R[$pn] : array ('mode' => 'none'),
3477 );
3478 continue;
3479 }
26dec09b
DO
3480 // (DC_): port missing from device
3481 if (!array_key_exists ($pn, $R))
be28b696 3482 {
ef016293
DO
3483 $ret[$pn] = array ('left' => $D[$pn]);
3484 if (same8021QConfigs ($D[$pn], $default_port))
3485 $ret[$pn]['status'] = 'ok_to_delete';
3486 else
3487 {
3488 $ret[$pn]['status'] = 'delete_conflict';
3489 $ret[$pn]['lastseen'] = $C[$pn];
3490 }
be28b696
DO
3491 continue;
3492 }
26dec09b
DO
3493 // (__R): port missing from DB
3494 if (!array_key_exists ($pn, $C))
be28b696 3495 {
7a375475
DO
3496 // Allow importing any configuration, which passes basic
3497 // validation. If port mode doesn't match its VST role,
3498 // this will be handled later WRT each port.
ab25b0d0
DO
3499 $ret[$pn] = array
3500 (
7a375475 3501 'status' => acceptable8021QConfig ($R[$pn]) ? 'ok_to_add' : 'add_conflict',
ab25b0d0
DO
3502 'right' => $R[$pn],
3503 );
be28b696
DO
3504 continue;
3505 }
3506 $D_eq_C = same8021QConfigs ($D[$pn], $C[$pn]);
3507 $C_eq_R = same8021QConfigs ($C[$pn], $R[$pn]);
26dec09b 3508 // (DCR), D = C = R: data in sync
be28b696 3509 if ($D_eq_C and $C_eq_R) // implies D == R
ab25b0d0
DO
3510 {
3511 $ret[$pn] = array
3512 (
3513 'status' => 'in_sync',
3514 'both' => $R[$pn],
3515 );
be28b696 3516 continue;
ab25b0d0 3517 }
26dec09b 3518 // (DCR), D = C: no local edit in the way
be28b696 3519 if ($D_eq_C)
ab25b0d0
DO
3520 $ret[$pn] = array
3521 (
3522 'status' => 'ok_to_pull',
3523 'left' => $D[$pn],
3524 'right' => $R[$pn],
3525 );
26dec09b 3526 // (DCR), C = R: no remote edit in the way
be28b696 3527 elseif ($C_eq_R)
ab25b0d0
DO
3528 $ret[$pn] = array
3529 (
3530 'status' => 'ok_to_push',
3531 'left' => $D[$pn],
3532 'right' => $R[$pn],
3533 );
26dec09b 3534 // (DCR), D = R: end of version conflict, restore tracking
be28b696 3535 elseif (same8021QConfigs ($D[$pn], $R[$pn]))
ab25b0d0
DO
3536 $ret[$pn] = array
3537 (
d973196a 3538 'status' => 'ok_to_merge',
ab25b0d0
DO
3539 'both' => $R[$pn],
3540 );
26dec09b 3541 else // D != C, C != R, D != R: version conflict
ab25b0d0
DO
3542 $ret[$pn] = array
3543 (
ec523868 3544 'status' => editable8021QPort ($port) ?
d3082829
DO
3545 // In case the port is normally updated by user, let him
3546 // resolve the conflict. If the system manages this port,
3547 // arrange the data to let remote version go down.
3548 'merge_conflict' : 'ok_to_push_with_merge',
ab25b0d0
DO
3549 'left' => $D[$pn],
3550 'right' => $R[$pn],
3551 );
be28b696 3552 }
ab25b0d0 3553 return $ret;
be28b696
DO
3554}
3555
ca5d4cbc 3556// return number of records updated successfully of FALSE, if a conflict was in the way
d973196a 3557function exec8021QDeploy ($object_id, $do_push)
ca5d4cbc
DO
3558{
3559 global $dbxlink;
b3a27170 3560 $nsaved = $npushed = $nsaved_uplinks = 0;
ca5d4cbc
DO
3561 $dbxlink->beginTransaction();
3562 if (NULL === $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE'))
3563 throw new InvalidArgException ('object_id', $object_id, 'VLAN domain is not set for this object');
3564 $D = getStored8021QConfig ($vswitch['object_id'], 'desired');
3565 $C = getStored8021QConfig ($vswitch['object_id'], 'cached');
d973196a
DO
3566 try
3567 {
3568 $R = getRunning8021QConfig ($vswitch['object_id']);
3569 }
3a089a44 3570 catch (RTGatewayError $e)
d973196a 3571 {
c1aa3ada
DO
3572 usePreparedExecuteBlade
3573 (
3574 'UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?',
3575 array (E_8021Q_PULL_REMOTE_ERROR, $vswitch['object_id'])
3576 );
d973196a
DO
3577 $dbxlink->commit();
3578 return 0;
3579 }
ca5d4cbc 3580 $conflict = FALSE;
ca5d4cbc 3581 $ok_to_push = array();
d973196a 3582 foreach (get8021QSyncOptions ($vswitch, $D, $C, $R['portdata']) as $pn => $port)
ca5d4cbc
DO
3583 {
3584 // always update cache with new data from switch
3585 switch ($port['status'])
3586 {
d973196a 3587 case 'ok_to_merge':
f82a94da 3588 // FIXME: this can be logged
d973196a 3589 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['both']);
ca5d4cbc
DO
3590 break;
3591 case 'ok_to_delete':
d973196a 3592 $nsaved += del8021QPort ($vswitch['object_id'], $pn);
ca5d4cbc
DO
3593 break;
3594 case 'ok_to_add':
d973196a 3595 $nsaved += add8021QPort ($vswitch['object_id'], $pn, $port['right']);
ca5d4cbc
DO
3596 break;
3597 case 'delete_conflict':
3598 case 'merge_conflict':
7a375475
DO
3599 case 'add_conflict':
3600 case 'martian_conflict':
ca5d4cbc
DO
3601 $conflict = TRUE;
3602 break;
3603 case 'ok_to_pull':
f82a94da 3604 // FIXME: this can be logged
d973196a
DO
3605 upd8021QPort ('desired', $vswitch['object_id'], $pn, $port['right']);
3606 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3607 $nsaved++;
ca5d4cbc 3608 break;
d3082829
DO
3609 case 'ok_to_push_with_merge':
3610 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3611 // fall through
ca5d4cbc
DO
3612 case 'ok_to_push':
3613 $ok_to_push[$pn] = $port['left'];
3614 break;
3615 }
3616 }
32c7fad1
DO
3617 // redo uplinks unconditionally
3618 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3619 $Dnew = apply8021QOrder ($vswitch['template_id'], getStored8021QConfig ($vswitch['object_id'], 'desired'));
034a61c9
DO
3620 // Take new "desired" configuration and derive uplink port configuration
3621 // from it. Then cancel changes to immune VLANs and save resulting
3622 // changes (if any left).
9ce5cf73 3623 $new_uplinks = filter8021QChangeRequests ($domain_vlanlist, $Dnew, produceUplinkPorts ($domain_vlanlist, $Dnew, $vswitch['object_id']));
b3a27170
DO
3624 $nsaved_uplinks += replace8021QPorts ('desired', $vswitch['object_id'], $Dnew, $new_uplinks);
3625 if ($nsaved + $nsaved_uplinks)
94080e1c 3626 {
32c7fad1
DO
3627 // saved configuration has changed (either "user" ports have changed,
3628 // or uplinks, or both), so bump revision number up)
c1aa3ada
DO
3629 usePreparedExecuteBlade
3630 (
3631 'UPDATE VLANSwitch SET mutex_rev=mutex_rev+1, last_change=NOW(), out_of_sync="yes" WHERE object_id=?',
3632 array ($vswitch['object_id'])
3633 );
ca5d4cbc 3634 }
d973196a 3635 if ($conflict)
c1aa3ada
DO
3636 usePreparedExecuteBlade
3637 (
3638 'UPDATE VLANSwitch SET out_of_sync="yes", last_errno=?, last_error_ts=NOW() WHERE object_id=?',
3639 array (E_8021Q_VERSION_CONFLICT, $vswitch['object_id'])
3640 );
52ce8bd7 3641 else
d973196a 3642 {
c1aa3ada
DO
3643 usePreparedExecuteBlade
3644 (
3645 'UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?',
3646 array (E_8021Q_NOERROR, $vswitch['object_id'])
3647 );
4799a8df
DO
3648 // Modified uplinks are very likely to differ from those in R-copy,
3649 // so don't mark device as clean, if this happened. This can cost
3650 // us an additional, empty round of sync, but at least out_of_sync
3651 // won't be mistakenly set to 'no'.
3652 // FIXME: A cleaner way of coupling pull and push operations would
3653 // be to split this function into two.
3654 if (!count ($ok_to_push) and !$nsaved_uplinks)
c1aa3ada
DO
3655 usePreparedExecuteBlade
3656 (
3657 'UPDATE VLANSwitch SET out_of_sync="no" WHERE object_id=?',
3658 array ($vswitch['object_id'])
3659 );
52ce8bd7 3660 elseif ($do_push)
d973196a 3661 {
c1aa3ada
DO
3662 usePreparedExecuteBlade
3663 (
3664 'UPDATE VLANSwitch SET last_push_started=NOW() WHERE object_id=?',
3665 array ($vswitch['object_id'])
3666 );
52ce8bd7
DO
3667 try
3668 {
8cbdec04 3669 $vlan_names = isset ($R['vlannames']) ? $R['vlannames'] : array();
8d6c3668 3670 $npushed += exportSwitch8021QConfig ($vswitch, $R['vlanlist'], $R['portdata'], $ok_to_push, $vlan_names);
52ce8bd7
DO
3671 // update cache for ports deployed
3672 replace8021QPorts ('cached', $vswitch['object_id'], $R['portdata'], $ok_to_push);
c1aa3ada
DO
3673 usePreparedExecuteBlade
3674 (
3675 'UPDATE VLANSwitch SET last_push_finished=NOW(), out_of_sync="no", last_errno=? WHERE object_id=?',
3676 array (E_8021Q_NOERROR, $vswitch['object_id'])
3677 );
52ce8bd7 3678 }
3a089a44 3679 catch (RTGatewayError $r)
52ce8bd7 3680 {
c1aa3ada
DO
3681 usePreparedExecuteBlade
3682 (
3683 'UPDATE VLANSwitch SET out_of_sync="yes", last_error_ts=NOW(), last_errno=? WHERE object_id=?',
3684 array (E_8021Q_PUSH_REMOTE_ERROR, $vswitch['object_id'])
3685 );
52ce8bd7 3686 }
ca5d4cbc
DO
3687 }
3688 }
3689 $dbxlink->commit();
b3a27170
DO
3690 // start downlink work only after unlocking current object to make deadlocks less likely to happen
3691 // TODO: only process changed uplink ports
3692 if ($nsaved_uplinks)
3693 initiateUplinksReverb ($vswitch['object_id'], $new_uplinks);
3694 return $nsaved + $npushed + $nsaved_uplinks;
ca5d4cbc
DO
3695}
3696
538d9cf8
DO
3697function strerror8021Q ($errno)
3698{
3699 switch ($errno)
3700 {
57acf3f5
DO
3701 case E_8021Q_VERSION_CONFLICT:
3702 return 'pull failed due to version conflict';
3703 case E_8021Q_PULL_REMOTE_ERROR:
3704 return 'pull failed due to remote error';
3705 case E_8021Q_PUSH_REMOTE_ERROR:
3706 return 'push failed due to remote error';
5701baec
DO
3707 case E_8021Q_SYNC_DISABLED:
3708 return 'sync disabled by operator';
57acf3f5
DO
3709 default:
3710 return "unknown error code ${errno}";