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