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