2eb7a6f20e6dd584a3ea8de3bbdc4800499605a1
[racktables] / wwwroot / inc / functions.php
1 <?php
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
27 define ('CHAP_OBJTYPE', 1);
28 define ('CHAP_PORTTYPE', 2);
29 // The latter matches both SunOS and Linux-styled formats.
30 define ('RE_L2_IFCFG', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){5}$/i');
31 define ('RE_L2_CISCO', '/^[0-9a-f]{4}(\.[0-9a-f]{4}){2}$/i');
32 define ('RE_L2_HUAWEI', '/^[0-9a-f]{4}(-[0-9a-f]{4}){2}$/i');
33 define ('RE_L2_SOLID', '/^[0-9a-f]{12}$/i');
34 define ('RE_L2_IPCFG', '/^[0-9a-f]{2}(-[0-9a-f]{2}){5}$/i');
35 define ('RE_L2_WWN_COLON', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){7}$/i');
36 define ('RE_L2_WWN_HYPHEN', '/^[0-9a-f]{2}(-[0-9a-f]{2}){7}$/i');
37 define ('RE_L2_WWN_SOLID', '/^[0-9a-f]{16}$/i');
38 define ('RE_IP4_ADDR', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}$#');
39 define ('RE_IP4_NET', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$#');
40 define ('E_8021Q_NOERROR', 0);
41 define ('E_8021Q_VERSION_CONFLICT', 101);
42 define ('E_8021Q_PULL_REMOTE_ERROR', 102);
43 define ('E_8021Q_PUSH_REMOTE_ERROR', 103);
44 define ('E_8021Q_SYNC_DISABLED', 104);
45 define ('VLAN_MIN_ID', 1);
46 define ('VLAN_MAX_ID', 4094);
47 define ('VLAN_DFL_ID', 1);
48 define ('TAB_REMEMBER_TIMEOUT', 300);
49
50 // Entity type by page number mapping is 1:1 atm, but may change later.
51 $etype_by_pageno = array
52 (
53 'ipv4net' => 'ipv4net',
54 'ipv6net' => 'ipv6net',
55 'ipv4rspool' => 'ipv4rspool',
56 'ipv4vs' => 'ipv4vs',
57 'object' => 'object',
58 'rack' => 'rack',
59 'user' => 'user',
60 'file' => 'file',
61 );
62
63 // Rack thumbnail image width summands: "front", "interior" and "rear" elements w/o surrounding border.
64 $rtwidth = array
65 (
66 0 => 9,
67 1 => 21,
68 2 => 9
69 );
70
71 $virtual_obj_types = array
72 (
73 1504,
74 1505,
75 1506,
76 1507
77 );
78
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
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
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
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 ),
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 ),
247 );
248
249 // This function assures that specified argument was passed
250 // and is a number greater than zero.
251 function assertUIntArg ($argname, $allow_zero = FALSE)
252 {
253 if (!isset ($_REQUEST[$argname]))
254 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
255 if (!is_numeric ($_REQUEST[$argname]))
256 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a number');
257 if ($_REQUEST[$argname] < 0)
258 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is less than zero');
259 if (!$allow_zero and $_REQUEST[$argname] == 0)
260 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is zero');
261 }
262
263 function isInteger ($arg, $allow_zero = FALSE)
264 {
265 if (! is_numeric ($arg))
266 return FALSE;
267 if (! $allow_zero and ! $arg)
268 return FALSE;
269 return TRUE;
270 }
271
272 // This function assures that specified argument was passed
273 // and is a non-empty string.
274 function assertStringArg ($argname, $ok_if_empty = FALSE)
275 {
276 if (!isset ($_REQUEST[$argname]))
277 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
278 if (!is_string ($_REQUEST[$argname]))
279 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
280 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
281 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
282 }
283
284 function assertBoolArg ($argname, $ok_if_empty = FALSE)
285 {
286 if (!isset ($_REQUEST[$argname]))
287 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
288 if (!is_string ($_REQUEST[$argname]) or $_REQUEST[$argname] != 'on')
289 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
290 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
291 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
292 }
293
294 // function returns IPv6Address object, null if arg is correct IPv4, or throws an exception
295 function assertIPArg ($argname, $ok_if_empty = FALSE)
296 {
297 assertStringArg ($argname, $ok_if_empty);
298 $ip = $_REQUEST[$argname];
299 if (FALSE !== strpos ($ip, ':'))
300 {
301 $v6address = new IPv6Address;
302 $result = $v6address->parse ($ip);
303 $ret = $v6address;
304 }
305 else
306 {
307 $result = long2ip (ip2long ($ip)) === $ip;
308 $ret = NULL;
309 }
310 if (! $result)
311 throw new InvalidRequestArgException ($argname, $ip, 'parameter is not a valid IPv4 or IPv6 address');
312 return $ret;
313 }
314
315 function assertIPv4Arg ($argname, $ok_if_empty = FALSE)
316 {
317 assertStringArg ($argname, $ok_if_empty);
318 if (strlen ($_REQUEST[$argname]) and long2ip (ip2long ($_REQUEST[$argname])) !== $_REQUEST[$argname])
319 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a valid ipv4 address');
320 }
321
322 // function returns IPv6Address object, or throws an exception
323 function assertIPv6Arg ($argname, $ok_if_empty = FALSE)
324 {
325 assertStringArg ($argname, $ok_if_empty);
326 $ipv6 = new IPv6Address;
327 if (strlen ($_REQUEST[$argname]) and ! $ok_if_empty and ! $ipv6->parse ($_REQUEST[$argname]))
328 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a valid ipv6 address');
329 return $ipv6;
330 }
331
332 function assertPCREArg ($argname)
333 {
334 assertStringArg ($argname, TRUE); // empty pattern is Ok
335 if (FALSE === preg_match ($_REQUEST[$argname], 'test'))
336 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'PCRE validation failed');
337 }
338
339 function isPCRE ($arg)
340 {
341 if (! isset ($arg) or FALSE === @preg_match ($arg, 'test'))
342 return FALSE;
343 return TRUE;
344 }
345
346 function genericAssertion ($argname, $argtype)
347 {
348 global $sic;
349 switch ($argtype)
350 {
351 case 'string':
352 assertStringArg ($argname);
353 break;
354 case 'string0':
355 assertStringArg ($argname, TRUE);
356 break;
357 case 'uint':
358 assertUIntArg ($argname);
359 break;
360 case 'uint0':
361 assertUIntArg ($argname, TRUE);
362 break;
363 case 'inet4':
364 assertIPv4Arg ($argname);
365 break;
366 case 'inet6':
367 assertIPv6Arg ($argname);
368 break;
369 case 'l2address':
370 assertStringArg ($argname);
371 case 'l2address0':
372 assertStringArg ($argname, TRUE);
373 try
374 {
375 l2addressForDatabase ($sic[$argname]);
376 }
377 catch (InvalidArgException $e)
378 {
379 throw new InvalidRequestArgException ($argname, $sic[$argname], 'malformed MAC/WWN address');
380 }
381 break;
382 case 'tag':
383 assertStringArg ($argname);
384 if (!validTagName ($sic[$argname]))
385 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Invalid tag name');
386 break;
387 case 'pcre':
388 assertPCREArg ($argname);
389 break;
390 case 'json':
391 assertStringArg ($argname);
392 if (NULL === json_decode ($sic[$argname], TRUE))
393 throw new InvalidRequestArgException ($argname, '(omitted)', 'Invalid JSON code received from client');
394 break;
395 case 'array':
396 if (! array_key_exists ($argname, $_REQUEST))
397 throw new InvalidRequestArgException ($argname, '(missing argument)');
398 if (! is_array ($_REQUEST[$argname]))
399 throw new InvalidRequestArgException ($argname, '(omitted)', 'argument is not an array');
400 break;
401 case 'enum/attr_type':
402 assertStringArg ($argname);
403 if (!in_array ($sic[$argname], array ('uint', 'float', 'string', 'dict')))
404 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
405 break;
406 case 'enum/vlan_type':
407 assertStringArg ($argname);
408 // "Alien" type is not valid until the logic is fixed to implement it in full.
409 if (!in_array ($sic[$argname], array ('ondemand', 'compulsory')))
410 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
411 break;
412 case 'enum/wdmstd':
413 assertStringArg ($argname);
414 global $wdm_packs;
415 if (! array_key_exists ($sic[$argname], $wdm_packs))
416 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
417 break;
418 case 'enum/ipproto':
419 assertStringArg ($argname);
420 if (!in_array ($sic[$argname], array ('TCP', 'UDP')))
421 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
422 break;
423 case 'enum/inet4alloc':
424 case 'enum/inet6alloc':
425 assertStringArg ($argname);
426 if (!in_array ($sic[$argname], array ('regular', 'shared', 'virtual', 'router')))
427 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
428 break;
429 case 'enum/dqcode':
430 assertStringArg ($argname);
431 global $dqtitle;
432 if (! array_key_exists ($sic[$argname], $dqtitle))
433 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
434 break;
435 case 'iif':
436 if (!array_key_exists ($sic[$argname], getPortIIFOptions()))
437 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
438 break;
439 case 'vlan':
440 case 'vlan1':
441 genericAssertion ($argname, 'uint');
442 if ($argtype == 'vlan' and $sic[$argname] == VLAN_DFL_ID)
443 throw new InvalidRequestArgException ($argname, $sic[$argname], 'default VLAN cannot be changed');
444 if ($sic[$argname] > VLAN_MAX_ID or $sic[$argname] < VLAN_MIN_ID)
445 throw new InvalidRequestArgException ($argname, $sic[$argname], 'out of valid range');
446 break;
447 case 'rackcode/expr':
448 genericAssertion ($argname, 'string0');
449 if ($sic[$argname] == '')
450 return;
451 $parse = spotPayload ($sic[$argname], 'SYNT_EXPR');
452 if ($parse['result'] != 'ACK')
453 throw new InvalidRequestArgException ($argname, $sic[$argname], 'RackCode parsing error');
454 break;
455 default:
456 throw new InvalidArgException ('argtype', $argtype); // comes not from user's input
457 }
458 }
459
460 // Validate and return "bypass" value for the current context, if one is
461 // defined for it, or NULL otherwise.
462 function getBypassValue()
463 {
464 global $page, $pageno, $sic;
465 if (!array_key_exists ('bypass', $page[$pageno]))
466 return NULL;
467 if (!array_key_exists ('bypass_type', $page[$pageno]))
468 throw new RackTablesError ("Internal structure error at node '${pageno}' (bypass_type is not set)", RackTablesError::INTERNAL);
469 genericAssertion ($page[$pageno]['bypass'], $page[$pageno]['bypass_type']);
470 return $sic[$page[$pageno]['bypass']];
471 }
472
473 // Objects of some types should be explicitly shown as
474 // anonymous (labelless). This function is a single place where the
475 // decision about displayed name is made.
476 function setDisplayedName (&$cell)
477 {
478 if ($cell['name'] != '')
479 $cell['dname'] = $cell['name'];
480 else
481 {
482 $cell['atags'][] = array ('tag' => '$nameless');
483 if (considerConfiguredConstraint ($cell, 'NAMEWARN_LISTSRC'))
484 $cell['dname'] = 'ANONYMOUS ' . decodeObjectType ($cell['objtype_id'], 'o');
485 else
486 $cell['dname'] = '[' . decodeObjectType ($cell['objtype_id'], 'o') . ']';
487 }
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.
493 function 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;
510 if (isset ($rackData[$startRow - $height][$locidx]['rowspan']))
511 break 2;
512 if (isset ($rackData[$startRow - $height][$locidx]['colspan']))
513 break 2;
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);
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";
531 return $height;
532 }
533
534 // This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan
535 // attributes.
536 function 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.
547 // Explicitly show even single-cell spanned atoms, because rectHeight()
548 // is expeciting this data for correct calculation.
549 if ($colspan != 0)
550 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
551 else
552 {
553 $colspan = $templateWidth[$template_idx];
554 if ($colspan >= 1)
555 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
556 if ($maxheight >= 1)
557 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
558 }
559 }
560 }
561 }
562 return;
563 }
564
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.
568 function markAllSpans (&$rackData)
569 {
570 for ($i = $rackData['height']; $i > 0; $i--)
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).
576 function markBestSpan (&$rackData, $i)
577 {
578 global $template, $templateWidth;
579 for ($j = 0; $j < 6; $j++)
580 {
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)
591 {
592 $best_template_index = $j;
593 $bestheight = $height[$j];
594 break;
595 }
596 // distribute span marks
597 markSpan ($rackData, $i, $bestheight, $best_template_index);
598 return TRUE;
599 }
600
601 // We can mount 'F' atoms and unmount our own 'T' atoms.
602 function 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.
620 function 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'.
636 function 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
651 // This function highlights specified object (and removes previous highlight).
652 function highlightObject (&$rackData, $object_id)
653 {
654 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
655 for ($locidx = 0; $locidx < 3; $locidx++)
656 if
657 (
658 $rackData[$unit_no][$locidx]['state'] == 'T' and
659 $rackData[$unit_no][$locidx]['object_id'] == $object_id
660 )
661 $rackData[$unit_no][$locidx]['hl'] = 'h';
662 else
663 unset ($rackData[$unit_no][$locidx]['hl']);
664 }
665
666 // This function marks atoms to selected or not depending on their current state.
667 function markupAtomGrid (&$data, $checked_state)
668 {
669 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
670 for ($locidx = 0; $locidx < 3; $locidx++)
671 {
672 if (!($data[$unit_no][$locidx]['enabled'] === TRUE))
673 continue;
674 if ($data[$unit_no][$locidx]['state'] == $checked_state)
675 $data[$unit_no][$locidx]['checked'] = ' checked';
676 else
677 $data[$unit_no][$locidx]['checked'] = '';
678 }
679 }
680
681 // This function is almost a clone of processGridForm(), but doesn't save anything to database
682 // Return value is the changed rack data.
683 // Here we assume that correct filter has already been applied, so we just
684 // set or unset checkbox inputs w/o changing atom state.
685 function mergeGridFormToRack (&$rackData)
686 {
687 $rack_id = $rackData['id'];
688 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
689 for ($locidx = 0; $locidx < 3; $locidx++)
690 {
691 if ($rackData[$unit_no][$locidx]['enabled'] != TRUE)
692 continue;
693 $inputname = "atom_${rack_id}_${unit_no}_${locidx}";
694 if (isset ($_REQUEST[$inputname]) and $_REQUEST[$inputname] == 'on')
695 $rackData[$unit_no][$locidx]['checked'] = ' checked';
696 else
697 $rackData[$unit_no][$locidx]['checked'] = '';
698 }
699 }
700
701 // netmask conversion from length to number
702 function binMaskFromDec ($maskL)
703 {
704 $map_straight = array (
705 0 => 0x00000000,
706 1 => 0x80000000,
707 2 => 0xc0000000,
708 3 => 0xe0000000,
709 4 => 0xf0000000,
710 5 => 0xf8000000,
711 6 => 0xfc000000,
712 7 => 0xfe000000,
713 8 => 0xff000000,
714 9 => 0xff800000,
715 10 => 0xffc00000,
716 11 => 0xffe00000,
717 12 => 0xfff00000,
718 13 => 0xfff80000,
719 14 => 0xfffc0000,
720 15 => 0xfffe0000,
721 16 => 0xffff0000,
722 17 => 0xffff8000,
723 18 => 0xffffc000,
724 19 => 0xffffe000,
725 20 => 0xfffff000,
726 21 => 0xfffff800,
727 22 => 0xfffffc00,
728 23 => 0xfffffe00,
729 24 => 0xffffff00,
730 25 => 0xffffff80,
731 26 => 0xffffffc0,
732 27 => 0xffffffe0,
733 28 => 0xfffffff0,
734 29 => 0xfffffff8,
735 30 => 0xfffffffc,
736 31 => 0xfffffffe,
737 32 => 0xffffffff,
738 );
739 return $map_straight[$maskL];
740 }
741
742 // complementary value
743 function binInvMaskFromDec ($maskL)
744 {
745 $map_compl = array (
746 0 => 0xffffffff,
747 1 => 0x7fffffff,
748 2 => 0x3fffffff,
749 3 => 0x1fffffff,
750 4 => 0x0fffffff,
751 5 => 0x07ffffff,
752 6 => 0x03ffffff,
753 7 => 0x01ffffff,
754 8 => 0x00ffffff,
755 9 => 0x007fffff,
756 10 => 0x003fffff,
757 11 => 0x001fffff,
758 12 => 0x000fffff,
759 13 => 0x0007ffff,
760 14 => 0x0003ffff,
761 15 => 0x0001ffff,
762 16 => 0x0000ffff,
763 17 => 0x00007fff,
764 18 => 0x00003fff,
765 19 => 0x00001fff,
766 20 => 0x00000fff,
767 21 => 0x000007ff,
768 22 => 0x000003ff,
769 23 => 0x000001ff,
770 24 => 0x000000ff,
771 25 => 0x0000007f,
772 26 => 0x0000003f,
773 27 => 0x0000001f,
774 28 => 0x0000000f,
775 29 => 0x00000007,
776 30 => 0x00000003,
777 31 => 0x00000001,
778 32 => 0x00000000,
779 );
780 return $map_compl[$maskL];
781 }
782
783 // This function looks up 'has_problems' flag for 'T' atoms
784 // and modifies 'hl' key. May be, this should be better done
785 // in amplifyCell(). We don't honour 'skipped' key, because
786 // the function is also used for thumb creation.
787 function markupObjectProblems (&$rackData)
788 {
789 for ($i = $rackData['height']; $i > 0; $i--)
790 for ($locidx = 0; $locidx < 3; $locidx++)
791 if ($rackData[$i][$locidx]['state'] == 'T')
792 {
793 $object = spotEntity ('object', $rackData[$i][$locidx]['object_id']);
794 if ($object['has_problems'] == 'yes')
795 {
796 // Object can be already highlighted.
797 if (isset ($rackData[$i][$locidx]['hl']))
798 $rackData[$i][$locidx]['hl'] = $rackData[$i][$locidx]['hl'] . 'w';
799 else
800 $rackData[$i][$locidx]['hl'] = 'w';
801 }
802 }
803 }
804
805 // Return a uniformly (010203040506 or 0102030405060708) formatted address, if it is present
806 // in the provided string, an empty string for an empty string or raise an exception.
807 function l2addressForDatabase ($string)
808 {
809 $string = strtoupper ($string);
810 switch (TRUE)
811 {
812 case ($string == '' or preg_match (RE_L2_SOLID, $string) or preg_match (RE_L2_WWN_SOLID, $string)):
813 return $string;
814 case (preg_match (RE_L2_IFCFG, $string) or preg_match (RE_L2_WWN_COLON, $string)):
815 // reformat output of SunOS ifconfig
816 $ret = '';
817 foreach (explode (':', $string) as $byte)
818 $ret .= (strlen ($byte) == 1 ? '0' : '') . $byte;
819 return $ret;
820 case (preg_match (RE_L2_CISCO, $string)):
821 return str_replace ('.', '', $string);
822 case (preg_match (RE_L2_HUAWEI, $string)):
823 return str_replace ('-', '', $string);
824 case (preg_match (RE_L2_IPCFG, $string) or preg_match (RE_L2_WWN_HYPHEN, $string)):
825 return str_replace ('-', '', $string);
826 default:
827 throw new InvalidArgException ('$string', $string, 'malformed MAC/WWN address');
828 }
829 }
830
831 function l2addressFromDatabase ($string)
832 {
833 switch (strlen ($string))
834 {
835 case 12: // Ethernet
836 case 16: // FireWire/Fibre Channel
837 $ret = implode (':', str_split ($string, 2));
838 break;
839 default:
840 $ret = $string;
841 break;
842 }
843 return $ret;
844 }
845
846 // The following 2 functions return previous and next rack IDs for
847 // a given rack ID. The order of racks is the same as in renderRackspace()
848 // or renderRow().
849 function getPrevIDforRack ($row_id, $rack_id)
850 {
851 $rackList = listCells ('rack', $row_id);
852 doubleLink ($rackList);
853 if (isset ($rackList[$rack_id]['prev_key']))
854 return $rackList[$rack_id]['prev_key'];
855 return NULL;
856 }
857
858 function getNextIDforRack ($row_id, $rack_id)
859 {
860 $rackList = listCells ('rack', $row_id);
861 doubleLink ($rackList);
862 if (isset ($rackList[$rack_id]['next_key']))
863 return $rackList[$rack_id]['next_key'];
864 return NULL;
865 }
866
867 // This function finds previous and next array keys for each array key and
868 // modifies its argument accordingly.
869 function doubleLink (&$array)
870 {
871 $prev_key = NULL;
872 foreach (array_keys ($array) as $key)
873 {
874 if ($prev_key)
875 {
876 $array[$key]['prev_key'] = $prev_key;
877 $array[$prev_key]['next_key'] = $key;
878 }
879 $prev_key = $key;
880 }
881 }
882
883 function sortTokenize ($a, $b)
884 {
885 $aold='';
886 while ($a != $aold)
887 {
888 $aold=$a;
889 $a = preg_replace('/[^a-zA-Z0-9]/',' ',$a);
890 $a = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$a);
891 $a = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$a);
892 }
893
894 $bold='';
895 while ($b != $bold)
896 {
897 $bold=$b;
898 $b = preg_replace('/[^a-zA-Z0-9]/',' ',$b);
899 $b = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$b);
900 $b = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$b);
901 }
902
903
904
905 $ar = explode(' ', $a);
906 $br = explode(' ', $b);
907 for ($i=0; $i<count($ar) && $i<count($br); $i++)
908 {
909 $ret = 0;
910 if (is_numeric($ar[$i]) and is_numeric($br[$i]))
911 $ret = ($ar[$i]==$br[$i])?0:($ar[$i]<$br[$i]?-1:1);
912 else
913 $ret = strcasecmp($ar[$i], $br[$i]);
914 if ($ret != 0)
915 return $ret;
916 }
917 if ($i<count($ar))
918 return 1;
919 if ($i<count($br))
920 return -1;
921 return 0;
922 }
923
924 // This function returns an array of single element of object's FQDN attribute,
925 // if FQDN is set. The next choice is object's common name, if it looks like a
926 // hostname. Otherwise an array of all 'regular' IP addresses of the
927 // object is returned (which may appear 0 and more elements long).
928 function findAllEndpoints ($object_id, $fallback = '')
929 {
930 foreach (getAttrValues ($object_id) as $record)
931 if ($record['id'] == 3 && strlen ($record['value'])) // FQDN
932 return array ($record['value']);
933 $regular = array();
934 foreach (getObjectIPv4Allocations ($object_id) as $dottedquad => $alloc)
935 if ($alloc['type'] == 'regular')
936 $regular[] = $dottedquad;
937 if (!count ($regular) && strlen ($fallback))
938 return array ($fallback);
939 return $regular;
940 }
941
942 // Some records in the dictionary may be written as plain text or as Wiki
943 // link in the following syntax:
944 // 1. word
945 // 2. [[word URL]] // FIXME: this isn't working
946 // 3. [[word word word | URL]]
947 // This function parses the line in $record['value'] and modifies $record:
948 // $record['o_value'] is set to be the first part of link (word word word)
949 // $record['a_value'] is the same, but with %GPASS and %GSKIP macros applied
950 // $record['href'] is set to URL if it is specified in the input value
951 function parseWikiLink (&$record)
952 {
953 if (! preg_match ('/^\[\[(.+)\]\]$/', $record['value'], $matches))
954 $record['o_value'] = $record['value'];
955 else
956 {
957 $s = explode ('|', $matches[1]);
958 if (isset ($s[1]))
959 $record['href'] = trim ($s[1]);
960 $record['o_value'] = trim ($s[0]);
961 }
962 $record['a_value'] = execGMarker ($record['o_value']);
963 }
964
965 // FIXME: should this be saved as "P-data"?
966 function execGMarker ($line)
967 {
968 return preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $line));
969 }
970
971 // rackspace usage for a single rack
972 // (T + W + U) / (height * 3 - A)
973 function getRSUforRack ($data)
974 {
975 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
976 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
977 for ($locidx = 0; $locidx < 3; $locidx++)
978 $counter[$data[$unit_no][$locidx]['state']]++;
979 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
980 }
981
982 // Same for row.
983 function getRSUforRackRow ($rowData)
984 {
985 if (!count ($rowData))
986 return 0;
987 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
988 $total_height = 0;
989 foreach (array_keys ($rowData) as $rack_id)
990 {
991 $data = spotEntity ('rack', $rack_id);
992 amplifyCell ($data);
993 $total_height += $data['height'];
994 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
995 for ($locidx = 0; $locidx < 3; $locidx++)
996 $counter[$data[$unit_no][$locidx]['state']]++;
997 }
998 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
999 }
1000
1001 // Make sure the string is always wrapped with LF characters
1002 function lf_wrap ($str)
1003 {
1004 $ret = trim ($str, "\r\n");
1005 if (strlen ($ret))
1006 $ret .= "\n";
1007 return $ret;
1008 }
1009
1010 // Adopted from Mantis BTS code.
1011 function string_insert_hrefs ($s)
1012 {
1013 if (getConfigVar ('DETECT_URLS') != 'yes')
1014 return $s;
1015 # Find any URL in a string and replace it by a clickable link
1016 $s = preg_replace( '/(([[:alpha:]][-+.[:alnum:]]*):\/\/(%[[:digit:]A-Fa-f]{2}|[-_.!~*\';\/?%^\\\\:@&={\|}+$#\(\),\[\][:alnum:]])+)/se',
1017 "'<a href=\"'.rtrim('\\1','.').'\">\\1</a> [<a href=\"'.rtrim('\\1','.').'\" target=\"_blank\">^</a>]'",
1018 $s);
1019 $s = preg_replace( '/\b' . email_regex_simple() . '\b/i',
1020 '<a href="mailto:\0">\0</a>',
1021 $s);
1022 return $s;
1023 }
1024
1025 // Idem.
1026 function email_regex_simple ()
1027 {
1028 return "(([a-z0-9!#*+\/=?^_{|}~-]+(?:\.[a-z0-9!#*+\/=?^_{|}~-]+)*)" . # recipient
1029 "\@((?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?))"; # @domain
1030 }
1031
1032 // Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
1033 // for the requested object_type_id.
1034 function getAutoPorts ($type_id)
1035 {
1036 $ret = array();
1037 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
1038 foreach ($typemap as $equation)
1039 {
1040 $tmp = explode ('=', $equation);
1041 if (count ($tmp) != 2)
1042 continue;
1043 $objtype_id = $tmp[0];
1044 if ($objtype_id != $type_id)
1045 continue;
1046 $portlist = $tmp[1];
1047 foreach (explode ('+', $portlist) as $product)
1048 {
1049 $tmp = explode ('*', $product);
1050 if (count ($tmp) != 3)
1051 continue;
1052 $nports = $tmp[0];
1053 $port_type = $tmp[1];
1054 $format = $tmp[2];
1055 for ($i = 0; $i < $nports; $i++)
1056 $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i));
1057 }
1058 }
1059 return $ret;
1060 }
1061
1062 // Use pre-served trace to traverse the tree, then place given node where it belongs.
1063 function pokeNode (&$tree, $trace, $key, $value, $threshold = 0)
1064 {
1065 // This function needs the trace to be followed FIFO-way. The fastest
1066 // way to do so is to use array_push() for putting values into the
1067 // list and array_shift() for getting them out. This exposed up to 11%
1068 // performance gain compared to other patterns of array_push/array_unshift/
1069 // array_reverse/array_pop/array_shift conjunction.
1070 $myid = array_shift ($trace);
1071 if (!count ($trace)) // reached the target
1072 {
1073 if (!$threshold or ($threshold and $tree[$myid]['kidc'] + 1 < $threshold))
1074 $tree[$myid]['kids'][$key] = $value;
1075 // Reset accumulated records once, when the limit is reached, not each time
1076 // after that.
1077 if (++$tree[$myid]['kidc'] == $threshold)
1078 $tree[$myid]['kids'] = array();
1079 }
1080 else // not yet
1081 {
1082 $self = __FUNCTION__;
1083 $self ($tree[$myid]['kids'], $trace, $key, $value, $threshold);
1084 }
1085 }
1086
1087 // Likewise traverse the tree with the trace and return the final node.
1088 function peekNode ($tree, $trace, $target_id)
1089 {
1090 $self = __FUNCTION__;
1091 if (NULL === ($next = array_shift ($trace))) // warm
1092 {
1093 foreach ($tree as $node)
1094 if (array_key_exists ('id', $node) and $node['id'] == $target_id) // hot
1095 return $node;
1096 }
1097 else // cold
1098 {
1099 foreach ($tree as $node)
1100 if (array_key_exists ('id', $node) and $node['id'] == $next) // warmer
1101 return $self ($node['kids'], $trace, $target_id);
1102 }
1103 throw new RackTablesError ('inconsistent tree data', RackTablesError::INTERNAL);
1104 }
1105
1106 // Build a tree from the item list and return it. Input and output data is
1107 // indexed by item id (nested items in output are recursively stored in 'kids'
1108 // key, which is in turn indexed by id. Functions, which are ready to handle
1109 // tree collapsion/expansion themselves, may request non-zero threshold value
1110 // for smaller resulting tree.
1111 function treeFromList (&$orig_nodelist, $threshold = 0, $return_main_payload = TRUE)
1112 {
1113 $tree = array();
1114 $nodelist = $orig_nodelist;
1115 // Array equivalent of traceEntity() function.
1116 $trace = array();
1117 // set kidc and kids only once
1118 foreach (array_keys ($nodelist) as $nodeid)
1119 {
1120 $nodelist[$nodeid]['kidc'] = 0;
1121 $nodelist[$nodeid]['kids'] = array();
1122 }
1123 do
1124 {
1125 $nextpass = FALSE;
1126 foreach (array_keys ($nodelist) as $nodeid)
1127 {
1128 // When adding a node to the working tree, book another
1129 // iteration, because the new item could make a way for
1130 // others onto the tree. Also remove any item added from
1131 // the input list, so iteration base shrinks.
1132 // First check if we can assign directly.
1133 if ($nodelist[$nodeid]['parent_id'] == NULL)
1134 {
1135 $tree[$nodeid] = $nodelist[$nodeid];
1136 $trace[$nodeid] = array(); // Trace to root node is empty
1137 unset ($nodelist[$nodeid]);
1138 $nextpass = TRUE;
1139 }
1140 // Now look if it fits somewhere on already built tree.
1141 elseif (isset ($trace[$nodelist[$nodeid]['parent_id']]))
1142 {
1143 // Trace to a node is a trace to its parent plus parent id.
1144 $trace[$nodeid] = $trace[$nodelist[$nodeid]['parent_id']];
1145 $trace[$nodeid][] = $nodelist[$nodeid]['parent_id'];
1146 pokeNode ($tree, $trace[$nodeid], $nodeid, $nodelist[$nodeid], $threshold);
1147 // path to any other node is made of all parent nodes plus the added node itself
1148 unset ($nodelist[$nodeid]);
1149 $nextpass = TRUE;
1150 }
1151 }
1152 }
1153 while ($nextpass);
1154 if (!$return_main_payload)
1155 return $nodelist;
1156 // update each input node with its backtrace route
1157 foreach ($trace as $nodeid => $route)
1158 $orig_nodelist[$nodeid]['trace'] = $route;
1159 return $tree;
1160 }
1161
1162 // Build a tree from the tag list and return everything _except_ the tree.
1163 // IOW, return taginfo items, which have parent_id set and pointing outside
1164 // of the "normal" tree, which originates from the root.
1165 function getOrphanedTags ()
1166 {
1167 global $taglist;
1168 return treeFromList ($taglist, 0, FALSE);
1169 }
1170
1171 // Return the list of missing implicit tags.
1172 function getImplicitTags ($oldtags)
1173 {
1174 global $taglist;
1175 $tmp = array();
1176 foreach ($oldtags as $taginfo)
1177 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']);
1178 // don't call array_unique here, it is in the function we will call now
1179 return buildTagChainFromIds ($tmp);
1180 }
1181
1182 // Minimize the chain: exclude all implicit tags and return the result.
1183 // This function makes use of an external cache with a miss/hit ratio
1184 // about 3/7 (ticket:255).
1185 function getExplicitTagsOnly ($chain)
1186 {
1187 global $taglist, $tagRelCache;
1188 $ret = array();
1189 foreach (array_keys ($chain) as $keyA) // check each A
1190 {
1191 $tagidA = $chain[$keyA]['id'];
1192 // do not include A in result, if A is seen on the trace of any B!=A
1193 foreach (array_keys ($chain) as $keyB)
1194 {
1195 $tagidB = $chain[$keyB]['id'];
1196 if ($tagidA == $tagidB)
1197 continue;
1198 if (!isset ($tagRelCache[$tagidA][$tagidB]))
1199 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']);
1200 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B
1201 continue 2; // skip this A
1202 }
1203 $ret[] = $chain[$keyA];
1204 }
1205 return $ret;
1206 }
1207
1208 // Check, if the given tag is present on the chain (will only work
1209 // for regular tags with tag ID set.
1210 function tagOnChain ($taginfo, $tagchain)
1211 {
1212 if (!isset ($taginfo['id']))
1213 return FALSE;
1214 foreach ($tagchain as $test)
1215 if ($test['id'] == $taginfo['id'])
1216 return TRUE;
1217 return FALSE;
1218 }
1219
1220 function tagNameOnChain ($tagname, $tagchain)
1221 {
1222 foreach ($tagchain as $test)
1223 if ($test['tag'] == $tagname)
1224 return TRUE;
1225 return FALSE;
1226 }
1227
1228 // Return TRUE, if two tags chains differ (order of tags doesn't matter).
1229 // Assume, that neither of the lists contains duplicates.
1230 // FIXME: a faster, than O(x^2) method is possible for this calculation.
1231 function tagChainCmp ($chain1, $chain2)
1232 {
1233 if (count ($chain1) != count ($chain2))
1234 return TRUE;
1235 foreach ($chain1 as $taginfo1)
1236 if (!tagOnChain ($taginfo1, $chain2))
1237 return TRUE;
1238 return FALSE;
1239 }
1240
1241 function redirectIfNecessary ()
1242 {
1243 global
1244 $trigger,
1245 $pageno,
1246 $tabno;
1247
1248 if
1249 (
1250 ! isset ($_REQUEST['tab']) and
1251 isset ($_SESSION['RTLT'][$pageno]) and
1252 getConfigVar ('SHOW_LAST_TAB') == 'yes' and
1253 permitted ($pageno, $_SESSION['RTLT'][$pageno]['tabname']) and
1254 time() - $_SESSION['RTLT'][$pageno]['time'] <= TAB_REMEMBER_TIMEOUT
1255 )
1256 redirectUser ($pageno, $_SESSION['RTLT'][$pageno]['tabname']);
1257
1258 // check if we accidentaly got on a dynamic tab that shouldn't be shown for this object
1259 if
1260 (
1261 isset ($trigger[$pageno][$tabno]) and
1262 !strlen (call_user_func ($trigger[$pageno][$tabno]))
1263 )
1264 {
1265 $_SESSION['RTLT'][$pageno]['dont_remember'] = 1;
1266 redirectUser ($pageno, 'default');
1267 }
1268 }
1269
1270 function prepareNavigation()
1271 {
1272 global
1273 $pageno,
1274 $tabno;
1275 $pageno = (isset ($_REQUEST['page'])) ? $_REQUEST['page'] : 'index';
1276
1277 if (isset ($_REQUEST['tab']))
1278 $tabno = $_REQUEST['tab'];
1279 else
1280 $tabno = 'default';
1281 }
1282
1283 function fixContext ($target = NULL)
1284 {
1285 global
1286 $pageno,
1287 $auto_tags,
1288 $expl_tags,
1289 $impl_tags,
1290 $target_given_tags,
1291 $user_given_tags,
1292 $etype_by_pageno,
1293 $page;
1294
1295 if ($target !== NULL)
1296 {
1297 $target_given_tags = $target['etags'];
1298 // Don't reset autochain, because auth procedures could push stuff there in.
1299 // Another important point is to ignore 'user' realm, so we don't infuse effective
1300 // context with autotags of the displayed account.
1301 if ($target['realm'] != 'user')
1302 $auto_tags = array_merge ($auto_tags, $target['atags']);
1303 }
1304 elseif (array_key_exists ($pageno, $etype_by_pageno))
1305 {
1306 // Each page listed in the map above requires one uint argument.
1307 $target_realm = $etype_by_pageno[$pageno];
1308 assertUIntArg ($page[$pageno]['bypass']);
1309 $target_id = $_REQUEST[$page[$pageno]['bypass']];
1310 $target = spotEntity ($target_realm, $target_id);
1311 $target_given_tags = $target['etags'];
1312 if ($target['realm'] != 'user')
1313 $auto_tags = array_merge ($auto_tags, $target['atags']);
1314 }
1315 // Explicit and implicit chains should be normally empty at this point, so
1316 // overwrite the contents anyway.
1317 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1318 $impl_tags = getImplicitTags ($expl_tags);
1319 }
1320
1321 // Take a list of user-supplied tag IDs to build a list of valid taginfo
1322 // records indexed by tag IDs (tag chain).
1323 function buildTagChainFromIds ($tagidlist)
1324 {
1325 global $taglist;
1326 $ret = array();
1327 foreach (array_unique ($tagidlist) as $tag_id)
1328 if (isset ($taglist[$tag_id]))
1329 $ret[] = $taglist[$tag_id];
1330 return $ret;
1331 }
1332
1333 // Process a given tag tree and return only meaningful branches. The resulting
1334 // (sub)tree will have refcnt leaves on every last branch.
1335 function getObjectiveTagTree ($tree, $realm, $preselect)
1336 {
1337 $self = __FUNCTION__;
1338 $ret = array();
1339 foreach ($tree as $taginfo)
1340 {
1341 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1342 // If the current node addresses something, add it to the result
1343 // regardless of how many sub-nodes it features.
1344 if
1345 (
1346 isset ($taginfo['refcnt'][$realm]) or
1347 count ($subsearch) > 1 or
1348 in_array ($taginfo['id'], $preselect)
1349 )
1350 $ret[] = array
1351 (
1352 'id' => $taginfo['id'],
1353 'tag' => $taginfo['tag'],
1354 'parent_id' => $taginfo['parent_id'],
1355 'refcnt' => $taginfo['refcnt'],
1356 'kids' => $subsearch
1357 );
1358 else
1359 $ret = array_merge ($ret, $subsearch);
1360 }
1361 return $ret;
1362 }
1363
1364 // Preprocess tag tree to get only tags which can effectively reduce given filter result,
1365 // than passes shrinked tag tree to getObjectiveTagTree and return its result.
1366 // This makes sense only if andor mode is 'and', otherwise function does not modify tree.
1367 // 'Given filter' is a pair of $entity_list(filter result) and $preselect(filter data).
1368 // 'Effectively' means reduce to non-empty result.
1369 function getShrinkedTagTree($entity_list, $realm, $preselect) {
1370 global $tagtree;
1371 if ($preselect['andor'] != 'and' || empty($entity_list) && $preselect['is_empty'])
1372 return getObjectiveTagTree($tagtree, $realm, $preselect['tagidlist']);
1373
1374 $used_tags = array(); //associative, keys - tag ids, values - taginfos
1375 foreach ($entity_list as $entity)
1376 {
1377 foreach ($entity['etags'] as $etag)
1378 if (! array_key_exists($etag['id'], $used_tags))
1379 $used_tags[$etag['id']] = 1;
1380 else
1381 $used_tags[$etag['id']]++;
1382
1383 foreach ($entity['itags'] as $itag)
1384 if (! array_key_exists($itag['id'], $used_tags))
1385 $used_tags[$itag['id']] = 0;
1386 }
1387
1388 $shrinked_tree = shrinkSubtree($tagtree, $used_tags, $preselect, $realm);
1389 return getObjectiveTagTree($shrinked_tree, $realm, $preselect['tagidlist']);
1390 }
1391
1392 // deletes item from tag subtree unless it exists in $used_tags and not preselected
1393 function shrinkSubtree($tree, $used_tags, $preselect, $realm) {
1394 $self = __FUNCTION__;
1395
1396 foreach($tree as $i => &$item) {
1397 $item['kids'] = $self($item['kids'], $used_tags, $preselect, $realm);
1398 $item['kidc'] = count($item['kids']);
1399 if
1400 (
1401 ! array_key_exists($item['id'], $used_tags) &&
1402 ! in_array($item['id'], $preselect['tagidlist']) &&
1403 ! $item['kidc']
1404 )
1405 unset($tree[$i]);
1406 else {
1407 $item['refcnt'][$realm] = $used_tags[$item['id']];
1408 if (! $item['refcnt'][$realm])
1409 unset($item['refcnt'][$realm]);
1410 }
1411 }
1412 return $tree;
1413 }
1414
1415 // Get taginfo record by tag name, return NULL, if record doesn't exist.
1416 function getTagByName ($target_name)
1417 {
1418 global $taglist;
1419 foreach ($taglist as $taginfo)
1420 if ($taginfo['tag'] == $target_name)
1421 return $taginfo;
1422 return NULL;
1423 }
1424
1425 // Merge two chains, filtering dupes out. Return the resulting superset.
1426 function mergeTagChains ($chainA, $chainB)
1427 {
1428 // $ret = $chainA;
1429 // Reindex by tag id in any case.
1430 $ret = array();
1431 foreach ($chainA as $tag)
1432 $ret[$tag['id']] = $tag;
1433 foreach ($chainB as $tag)
1434 if (!isset ($ret[$tag['id']]))
1435 $ret[$tag['id']] = $tag;
1436 return $ret;
1437 }
1438
1439 # Return a list consisting of tag ID of the given tree node and IDs of all
1440 # nodes it contains.
1441 function getTagIDListForNode ($treenode)
1442 {
1443 $self = __FUNCTION__;
1444 $ret = array ($treenode['id']);
1445 foreach ($treenode['kids'] as $item)
1446 $ret = array_merge ($ret, $self ($item));
1447 return $ret;
1448 }
1449
1450 function getCellFilter ()
1451 {
1452 global $sic;
1453 global $pageno;
1454 $staticFilter = getConfigVar ('STATIC_FILTER');
1455 if (isset ($_REQUEST['tagfilter']) and is_array ($_REQUEST['tagfilter']))
1456 {
1457 $_REQUEST['cft'] = $_REQUEST['tagfilter'];
1458 unset ($_REQUEST['tagfilter']);
1459 }
1460 $andor_used = FALSE;
1461 //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.
1462 if(isset($_REQUEST['andor']))
1463 {
1464 $andor_used = TRUE;
1465 unset($_SESSION[$pageno]);
1466 }
1467 if (isset ($_SESSION[$pageno]['tagfilter']) and is_array ($_SESSION[$pageno]['tagfilter']) and !(isset($_REQUEST['cft'])) and $staticFilter == 'yes')
1468 {
1469 $_REQUEST['cft'] = $_SESSION[$pageno]['tagfilter'];
1470 }
1471 if (isset ($_SESSION[$pageno]['cfe']) and !(isset($sic['cfe'])) and $staticFilter == 'yes')
1472 {
1473 $sic['cfe'] = $_SESSION[$pageno]['cfe'];
1474 }
1475 if (isset ($_SESSION[$pageno]['andor']) and !(isset($_REQUEST['andor'])) and $staticFilter == 'yes')
1476 {
1477 $_REQUEST['andor'] = $_SESSION[$pageno]['andor'];
1478 }
1479
1480
1481 $ret = array
1482 (
1483 'tagidlist' => array(),
1484 'tnamelist' => array(),
1485 'pnamelist' => array(),
1486 'andor' => '',
1487 'text' => '',
1488 'extratext' => '',
1489 'expression' => array(),
1490 'urlextra' => '', // Just put text here and let makeHref call urlencode().
1491 'is_empty' => TRUE,
1492 );
1493 switch (TRUE)
1494 {
1495 case (!isset ($_REQUEST['andor'])):
1496 $andor2 = getConfigVar ('FILTER_DEFAULT_ANDOR');
1497 break;
1498 case ($_REQUEST['andor'] == 'and'):
1499 case ($_REQUEST['andor'] == 'or'):
1500 $_SESSION[$pageno]['andor'] = $_REQUEST['andor'];
1501 $ret['andor'] = $andor2 = $_REQUEST['andor'];
1502 break;
1503 default:
1504 showWarning ('Invalid and/or switch value in submitted form');
1505 return NULL;
1506 }
1507 $andor1 = '';
1508 // Both tags and predicates, which don't exist, should be
1509 // handled somehow. Discard them silently for now.
1510 if (isset ($_REQUEST['cft']) and is_array ($_REQUEST['cft']))
1511 {
1512 $_SESSION[$pageno]['tagfilter'] = $_REQUEST['cft'];
1513 global $taglist;
1514 foreach ($_REQUEST['cft'] as $req_id)
1515 if (isset ($taglist[$req_id]))
1516 {
1517 $ret['tagidlist'][] = $req_id;
1518 $ret['tnamelist'][] = $taglist[$req_id]['tag'];
1519 $andor_used = $andor_used || (trim($andor1) != '');
1520 $ret['text'] .= $andor1 . '{' . $taglist[$req_id]['tag'] . '}';
1521 $andor1 = ' ' . $andor2 . ' ';
1522 $ret['urlextra'] .= '&cft[]=' . $req_id;
1523 }
1524 }
1525 if (isset ($_REQUEST['cfp']) and is_array ($_REQUEST['cfp']))
1526 {
1527 global $pTable;
1528 foreach ($_REQUEST['cfp'] as $req_name)
1529 if (isset ($pTable[$req_name]))
1530 {
1531 $ret['pnamelist'][] = $req_name;
1532 $andor_used = $andor_used || (trim($andor1) != '');
1533 $ret['text'] .= $andor1 . '[' . $req_name . ']';
1534 $andor1 = ' ' . $andor2 . ' ';
1535 $ret['urlextra'] .= '&cfp[]=' . $req_name;
1536 }
1537 }
1538 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1539 if (isset ($sic['cfe']))
1540 {
1541 $_SESSION[$pageno]['cfe'] = $sic['cfe'];
1542 // Only consider extra text, when it is a correct RackCode expression.
1543 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1544 if ($parse['result'] == 'ACK')
1545 {
1546 $ret['extratext'] = trim ($sic['cfe']);
1547 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1548 }
1549 }
1550 $finaltext = array();
1551 if (strlen ($ret['text']))
1552 $finaltext[] = '(' . $ret['text'] . ')';
1553 if (strlen ($ret['extratext']))
1554 $finaltext[] = '(' . $ret['extratext'] . ')';
1555 $andor_used = $andor_used || (count($finaltext) > 1);
1556 $finaltext = implode (' ' . $andor2 . ' ', $finaltext);
1557 if (strlen ($finaltext))
1558 {
1559 $ret['is_empty'] = FALSE;
1560 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1561 $ret['expression'] = $parse['result'] == 'ACK' ? $parse['load'] : NULL;
1562 // It's not quite fair enough to put the blame of the whole text onto
1563 // non-empty "extra" portion of it, but it's the only user-generated portion
1564 // of it, thus the most probable cause of parse error.
1565 if (strlen ($ret['extratext']))
1566 $ret['extraclass'] = $parse['result'] == 'ACK' ? 'validation-success' : 'validation-error';
1567 }
1568 if (! $andor_used)
1569 $ret['andor'] = getConfigVar ('FILTER_DEFAULT_ANDOR');
1570 else
1571 $ret['urlextra'] .= '&andor=' . $ret['andor'];
1572 return $ret;
1573 }
1574
1575 // Return an empty message log.
1576 function emptyLog ()
1577 {
1578 return array
1579 (
1580 'v' => 2,
1581 'm' => array()
1582 );
1583 }
1584
1585 // Return a message log consisting of only one message.
1586 function oneLiner ($code, $args = array())
1587 {
1588 $ret = emptyLog();
1589 $ret['m'][] = count ($args) ? array ('c' => $code, 'a' => $args) : array ('c' => $code);
1590 return $ret;
1591 }
1592
1593 // Merge message payload from two message logs given and return the result.
1594 function mergeLogs ($log1, $log2)
1595 {
1596 $ret = emptyLog();
1597 $ret['m'] = array_merge ($log1['m'], $log2['m']);
1598 return $ret;
1599 }
1600
1601 function redirectUser ($p, $t)
1602 {
1603 global $page;
1604 $l = "index.php?page=${p}&tab=${t}";
1605 if (isset ($page[$p]['bypass']) and isset ($_REQUEST[$page[$p]['bypass']]))
1606 $l .= '&' . $page[$p]['bypass'] . '=' . $_REQUEST[$page[$p]['bypass']];
1607 if (isset ($page[$p]['bypass_tabs']))
1608 foreach ($page[$p]['bypass_tabs'] as $param_name)
1609 if (isset ($_REQUEST[$param_name]))
1610 $l .= '&' . urlencode ($param_name) . '=' . urlencode ($_REQUEST[$param_name]);
1611 header ("Location: " . $l);
1612 die;
1613 }
1614
1615 function getRackCodeStats ()
1616 {
1617 global $rackCode;
1618 $defc = $grantc = $modc = 0;
1619 foreach ($rackCode as $s)
1620 switch ($s['type'])
1621 {
1622 case 'SYNT_DEFINITION':
1623 $defc++;
1624 break;
1625 case 'SYNT_GRANT':
1626 $grantc++;
1627 break;
1628 case 'SYNT_CTXMOD':
1629 $modc++;
1630 break;
1631 default:
1632 break;
1633 }
1634 $ret = array
1635 (
1636 'Definition sentences' => $defc,
1637 'Grant sentences' => $grantc,
1638 'Context mod sentences' => $modc
1639 );
1640 return $ret;
1641 }
1642
1643 function getRackImageWidth ()
1644 {
1645 global $rtwidth;
1646 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
1647 }
1648
1649 function getRackImageHeight ($units)
1650 {
1651 return 3 + 3 + $units * 2;
1652 }
1653
1654 // Perform substitutions and return resulting string
1655 // used solely by buildLVSConfig()
1656 function apply_macros ($macros, $subject, &$error_macro_stat)
1657 {
1658 // clear all text before last %RESET% macro
1659 $reset_keyword = '%RESET%';
1660 $reset_position = mb_strpos($subject, $reset_keyword, 0);
1661 if ($reset_position === FALSE)
1662 $ret = $subject;
1663 else
1664 $ret = trim
1665 (
1666 mb_substr($subject, $reset_position + mb_strlen($reset_keyword)),
1667 "\n\r"
1668 );
1669
1670 foreach ($macros as $search => $replace)
1671 {
1672 if (empty($replace))
1673 {
1674 $replace = "<span class=\"msg_error\">$search</span>";
1675 $count = 0;
1676 $ret = str_replace ($search, $replace, $ret, $count);
1677 if ($count)
1678 {
1679 if (array_key_exists($search, $error_macro_stat))
1680 $error_macro_stat[$search] += $count;
1681 else
1682 $error_macro_stat[$search] = $count;
1683 }
1684 }
1685 else
1686 $ret = str_replace ($search, $replace, $ret);
1687 }
1688 return $ret;
1689 }
1690
1691 // throws RTBuildLVSConfigError exception if undefined macros found
1692 function buildLVSConfig ($object_id)
1693 {
1694 $oInfo = spotEntity ('object', $object_id);
1695 $defaults = getSLBDefaults (TRUE);
1696 $lbconfig = getSLBConfig ($object_id);
1697 if ($lbconfig === NULL)
1698 {
1699 showWarning ('getSLBConfig() failed');
1700 return;
1701 }
1702 $newconfig = "#\n#\n# This configuration has been generated automatically by RackTables\n";
1703 $newconfig .= "# for object_id == ${object_id}\n# object name: ${oInfo['name']}\n#\n#\n\n\n";
1704
1705 $error_stat = array();
1706 foreach ($lbconfig as $vs_id => $vsinfo)
1707 {
1708 $newconfig .= "########################################################\n" .
1709 "# VS (id == ${vs_id}): " . (!strlen ($vsinfo['vs_name']) ? 'NO NAME' : $vsinfo['vs_name']) . "\n" .
1710 "# RS pool (id == ${vsinfo['pool_id']}): " . (!strlen ($vsinfo['pool_name']) ? 'ANONYMOUS' : $vsinfo['pool_name']) . "\n" .
1711 "########################################################\n";
1712 # The order of inheritance is: VS -> LB -> pool [ -> RS ]
1713 $macros = array
1714 (
1715 '%VIP%' => $vsinfo['vip'],
1716 '%VPORT%' => $vsinfo['vport'],
1717 '%PROTO%' => $vsinfo['proto'],
1718 '%VNAME%' => $vsinfo['vs_name'],
1719 '%RSPOOLNAME%' => $vsinfo['pool_name'],
1720 '%PRIO%' => $vsinfo['prio']
1721 );
1722 $newconfig .= "virtual_server ${vsinfo['vip']} ${vsinfo['vport']} {\n";
1723 $newconfig .= "\tprotocol ${vsinfo['proto']}\n";
1724 $newconfig .= lf_wrap (apply_macros
1725 (
1726 $macros,
1727 lf_wrap ($defaults['vs']) .
1728 lf_wrap ($vsinfo['vs_vsconfig']) .
1729 lf_wrap ($vsinfo['lb_vsconfig']) .
1730 lf_wrap ($vsinfo['pool_vsconfig']),
1731 $error_stat
1732 ));
1733 foreach ($vsinfo['rslist'] as $rs)
1734 {
1735 if (!strlen ($rs['rsport']))
1736 $rs['rsport'] = $vsinfo['vport'];
1737 $macros['%RSIP%'] = $rs['rsip'];
1738 $macros['%RSPORT%'] = $rs['rsport'];
1739 $newconfig .= "\treal_server ${rs['rsip']} ${rs['rsport']} {\n";
1740 $newconfig .= lf_wrap (apply_macros
1741 (
1742 $macros,
1743 lf_wrap ($defaults['rs']) .
1744 lf_wrap ($vsinfo['vs_rsconfig']) .
1745 lf_wrap ($vsinfo['lb_rsconfig']) .
1746 lf_wrap ($vsinfo['pool_rsconfig']) .
1747 lf_wrap ($rs['rs_rsconfig']),
1748 $error_stat
1749 ));
1750 $newconfig .= "\t}\n";
1751 }
1752 $newconfig .= "}\n\n\n";
1753 }
1754 if (! empty($error_stat))
1755 {
1756 $error_messages = array();
1757 foreach ($error_stat as $macro => $count)
1758 $error_messages[] = "Error: macro $macro can not be empty ($count occurences)";
1759 throw new RTBuildLVSConfigError($error_messages, $newconfig, $object_id);
1760 }
1761
1762 // FIXME: deal somehow with Mac-styled text, the below replacement will screw it up
1763 return dos2unix ($newconfig);
1764 }
1765
1766 // Indicate occupation state of each IP address: none, ordinary or problematic.
1767 function markupIPAddrList (&$addrlist)
1768 {
1769 foreach (array_keys ($addrlist) as $ip_bin)
1770 {
1771 $refc = array
1772 (
1773 'shared' => 0, // virtual
1774 'virtual' => 0, // loopback
1775 'regular' => 0, // connected host
1776 'router' => 0 // connected gateway
1777 );
1778 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1779 $refc[$a['type']]++;
1780 $nvirtloopback = ($refc['shared'] + $refc['virtual'] > 0) ? 1 : 0; // modulus of virtual + shared
1781 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
1782 $nrealms = $nreserved + $nvirtloopback + $refc['regular'] + $refc['router']; // latter two are connected and router allocations
1783
1784 if ($nrealms == 1)
1785 $addrlist[$ip_bin]['class'] = 'trbusy';
1786 elseif ($nrealms > 1)
1787 $addrlist[$ip_bin]['class'] = 'trerror';
1788 else
1789 $addrlist[$ip_bin]['class'] = '';
1790 }
1791 }
1792
1793 // Scan the given address list (returned by scanIPv4Space/scanIPv6Space) and return a list of all routers found.
1794 function findRouters ($addrlist)
1795 {
1796 $ret = array();
1797 foreach ($addrlist as $addr)
1798 foreach ($addr['allocs'] as $alloc)
1799 if ($alloc['type'] == 'router')
1800 $ret[] = array
1801 (
1802 'id' => $alloc['object_id'],
1803 'iface' => $alloc['name'],
1804 'dname' => $alloc['object_name'],
1805 'addr' => $addr['ip']
1806 );
1807 return $ret;
1808 }
1809
1810 // Assist in tag chain sorting.
1811 function taginfoCmp ($tagA, $tagB)
1812 {
1813 return $tagA['ci'] - $tagB['ci'];
1814 }
1815
1816 // Compare networks. When sorting a tree, the records on the list will have
1817 // distinct base IP addresses.
1818 // "The comparison function must return an integer less than, equal to, or greater
1819 // than zero if the first argument is considered to be respectively less than,
1820 // equal to, or greater than the second." (c) PHP manual
1821 function IPv4NetworkCmp ($netA, $netB)
1822 {
1823 // On 64-bit systems this function can be reduced to just this:
1824 if (PHP_INT_SIZE == 8)
1825 return $netA['ip_bin'] - $netB['ip_bin'];
1826 // There's a problem just substracting one u32 integer from another,
1827 // because the result may happen big enough to become a negative i32
1828 // integer itself (PHP tries to cast everything it sees to signed int)
1829 // The comparison below must treat positive and negative values of both
1830 // arguments.
1831 // Equal values give instant decision regardless of their [equal] sign.
1832 if ($netA['ip_bin'] == $netB['ip_bin'])
1833 return 0;
1834 // Same-signed values compete arithmetically within one of i32 contiguous ranges:
1835 // 0x00000001~0x7fffffff 1~2147483647
1836 // 0 doesn't have any sign, and network 0.0.0.0 isn't allowed
1837 // 0x80000000~0xffffffff -2147483648~-1
1838 $signA = $netA['ip_bin'] / abs ($netA['ip_bin']);
1839 $signB = $netB['ip_bin'] / abs ($netB['ip_bin']);
1840 if ($signA == $signB)
1841 {
1842 if ($netA['ip_bin'] > $netB['ip_bin'])
1843 return 1;
1844 else
1845 return -1;
1846 }
1847 else // With only one of two values being negative, it... wins!
1848 {
1849 if ($netA['ip_bin'] < $netB['ip_bin'])
1850 return 1;
1851 else
1852 return -1;
1853 }
1854 }
1855
1856 function IPv6NetworkCmp ($netA, $netB)
1857 {
1858 return strcmp ($netA['ip_bin']->getBin(), $netB['ip_bin']->getBin());
1859 }
1860
1861 // Modify the given tag tree so, that each level's items are sorted alphabetically.
1862 function sortTree (&$tree, $sortfunc = '')
1863 {
1864 if (!strlen ($sortfunc))
1865 return;
1866 $self = __FUNCTION__;
1867 usort ($tree, $sortfunc);
1868 // Don't make a mistake of directly iterating over the items of current level, because this way
1869 // the sorting will be performed on a _copy_ if each item, not the item itself.
1870 foreach (array_keys ($tree) as $tagid)
1871 $self ($tree[$tagid]['kids'], $sortfunc);
1872 }
1873
1874 function iptree_fill (&$netdata)
1875 {
1876 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
1877 return;
1878 // If we really have nested prefixes, they must fit into the tree.
1879 $worktree = array
1880 (
1881 'ip_bin' => $netdata['ip_bin'],
1882 'mask' => $netdata['mask']
1883 );
1884 foreach ($netdata['kids'] as $pfx)
1885 iptree_embed ($worktree, $pfx);
1886 $netdata['kids'] = iptree_construct ($worktree);
1887 $netdata['kidc'] = count ($netdata['kids']);
1888 }
1889
1890 function ipv6tree_fill (&$netdata)
1891 {
1892 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
1893 return;
1894 // If we really have nested prefixes, they must fit into the tree.
1895 $worktree = array
1896 (
1897 'ip_bin' => $netdata['ip_bin'],
1898 'mask' => $netdata['mask']
1899 );
1900 foreach ($netdata['kids'] as $pfx)
1901 ipv6tree_embed ($worktree, $pfx);
1902 $netdata['kids'] = ipv6tree_construct ($worktree);
1903 $netdata['kidc'] = count ($netdata['kids']);
1904 }
1905
1906 function iptree_construct ($node)
1907 {
1908 $self = __FUNCTION__;
1909
1910 if (!isset ($node['right']))
1911 {
1912 if (!isset ($node['ip']))
1913 {
1914 $node['ip'] = long2ip ($node['ip_bin']);
1915 $node['kids'] = array();
1916 $node['kidc'] = 0;
1917 $node['name'] = '';
1918 }
1919 return array ($node);
1920 }
1921 else
1922 return array_merge ($self ($node['left']), $self ($node['right']));
1923 }
1924
1925 function ipv6tree_construct ($node)
1926 {
1927 $self = __FUNCTION__;
1928
1929 if (!isset ($node['right']))
1930 {
1931 if (!isset ($node['ip']))
1932 {
1933 $node['ip'] = $node['ip_bin']->format();
1934 $node['kids'] = array();
1935 $node['kidc'] = 0;
1936 $node['name'] = '';
1937 }
1938 return array ($node);
1939 }
1940 else
1941 return array_merge ($self ($node['left']), $self ($node['right']));
1942 }
1943
1944 function iptree_embed (&$node, $pfx)
1945 {
1946 $self = __FUNCTION__;
1947
1948 // hit?
1949 if ($node['ip_bin'] == $pfx['ip_bin'] and $node['mask'] == $pfx['mask'])
1950 {
1951 $node = $pfx;
1952 return;
1953 }
1954 if ($node['mask'] == $pfx['mask'])
1955 throw new RackTablesError ('the recurring loop lost control', RackTablesError::INTERNAL);
1956
1957 // split?
1958 if (!isset ($node['right']))
1959 {
1960 // Fill in db_first/db_last to make it possible to run scanIPv4Space() on the node.
1961 $node['left']['mask'] = $node['mask'] + 1;
1962 $node['left']['ip_bin'] = $node['ip_bin'];
1963 $node['left']['db_first'] = sprintf ('%u', $node['left']['ip_bin']);
1964 $node['left']['db_last'] = sprintf ('%u', $node['left']['ip_bin'] | binInvMaskFromDec ($node['left']['mask']));
1965
1966 $node['right']['mask'] = $node['mask'] + 1;
1967 $node['right']['ip_bin'] = $node['ip_bin'] + binInvMaskFromDec ($node['mask'] + 1) + 1;
1968 $node['right']['db_first'] = sprintf ('%u', $node['right']['ip_bin']);
1969 $node['right']['db_last'] = sprintf ('%u', $node['right']['ip_bin'] | binInvMaskFromDec ($node['right']['mask']));
1970 }
1971
1972 // repeat!
1973 if (($node['left']['ip_bin'] & binMaskFromDec ($node['left']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1974 $self ($node['left'], $pfx);
1975 elseif (($node['right']['ip_bin'] & binMaskFromDec ($node['right']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1976 $self ($node['right'], $pfx);
1977 else
1978 throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
1979 }
1980
1981 function ipv6tree_embed (&$node, $pfx)
1982 {
1983 $self = __FUNCTION__;
1984
1985 // hit?
1986 if ($node['ip_bin'] == $pfx['ip_bin'] and $node['mask'] == $pfx['mask'])
1987 {
1988 $node = $pfx;
1989 return;
1990 }
1991 if ($node['mask'] == $pfx['mask'])
1992 throw new RackTablesError ('the recurring loop lost control', RackTablesError::INTERNAL);
1993
1994 // split?
1995 if (!isset ($node['right']))
1996 {
1997 $node['left']['mask'] = $node['mask'] + 1;
1998 $node['left']['ip_bin'] = $node['ip_bin'];
1999 $node['left']['db_first'] = $node['ip_bin']->get_first_subnet_address ($node['mask'] + 1);
2000 $node['left']['db_last'] = $node['ip_bin']->get_last_subnet_address ($node['mask'] + 1);
2001
2002 $node['right']['mask'] = $node['mask'] + 1;
2003 $node['right']['ip_bin'] = $node['ip_bin']->get_last_subnet_address ($node['mask'] + 1)->next();
2004 $node['right']['db_first'] = $node['right']['ip_bin'];
2005 $node['right']['db_last'] = $node['right']['ip_bin']->get_last_subnet_address ($node['mask'] + 1);
2006 }
2007
2008 // repeat!
2009 if ($node['left']['db_first'] == $pfx['ip_bin']->get_first_subnet_address ($node['left']['mask']))
2010 $self ($node['left'], $pfx);
2011 elseif ($node['right']['db_first'] == $pfx['ip_bin']->get_first_subnet_address ($node['left']['mask']))
2012 $self ($node['right'], $pfx);
2013 else
2014 throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
2015 }
2016
2017 function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
2018 {
2019 if (!strlen ($func))
2020 return;
2021 $self = __FUNCTION__;
2022 foreach (array_keys ($tree) as $key)
2023 {
2024 $func ($tree[$key]);
2025 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
2026 continue;
2027 $self ($tree[$key]['kids'], $func);
2028 }
2029 }
2030
2031 function loadIPv4AddrList (&$netinfo)
2032 {
2033 loadOwnIPv4Addresses ($netinfo);
2034 markupIPAddrList ($netinfo['addrlist']);
2035 }
2036
2037 function countOwnIPv4Addresses (&$node)
2038 {
2039 $node['addrt'] = 0;
2040 if (empty ($node['kids']))
2041 $node['addrt'] = binInvMaskFromDec ($node['mask']) + 1;
2042 else
2043 foreach ($node['kids'] as $nested)
2044 if (!isset ($nested['id'])) // spare
2045 $node['addrt'] += binInvMaskFromDec ($nested['mask']) + 1;
2046 }
2047
2048 function nodeIsCollapsed ($node)
2049 {
2050 return $node['symbol'] == 'node-collapsed';
2051 }
2052
2053 // implies countOwnIPv4Addresses
2054 function loadOwnIPv4Addresses (&$node)
2055 {
2056 $toscan = array();
2057 $node['addrt'] = 0;
2058 if (!isset ($node['kids']) or !count ($node['kids']))
2059 {
2060 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
2061 $node['addrt'] = $node['db_last'] - $node['db_first'] + 1;
2062 }
2063 else
2064 {
2065 $node['addrt'] = 0;
2066 foreach ($node['kids'] as $nested)
2067 if (!isset ($nested['id'])) // spare
2068 {
2069 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
2070 $node['addrt'] += $nested['db_last'] - $nested['db_first'] + 1;
2071 }
2072 }
2073 $node['addrlist'] = scanIPv4Space ($toscan);
2074 $node['addrc'] = count ($node['addrlist']);
2075 }
2076
2077 function loadIPv6AddrList (&$netinfo)
2078 {
2079 loadOwnIPv6Addresses ($netinfo);
2080 markupIPAddrList ($netinfo['addrlist']);
2081 }
2082
2083 function loadOwnIPv6Addresses (&$node)
2084 {
2085 $toscan = array();
2086 $node['addrt'] = 0;
2087 if (empty ($node['kids']))
2088 $toscan[] = array ('first' => $node['ip_bin'], 'last' => $node['ip_bin']->get_last_subnet_address ($node['mask']));
2089 else
2090 foreach ($node['kids'] as $nested)
2091 if (!isset ($nested['id'])) // spare
2092 $toscan[] = array ('first' => $nested['ip_bin'], 'last' => $nested['ip_bin']->get_last_subnet_address ($nested['mask']));
2093 $node['addrlist'] = scanIPv6Space ($toscan);
2094 $node['addrc'] = count ($node['addrlist']);
2095 }
2096
2097 function prepareIPv4Tree ($netlist, $expanded_id = 0)
2098 {
2099 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
2100 // so perform necessary pre-processing to make orphans belong to root. This trick
2101 // was earlier performed by getIPv4NetworkList().
2102 $netids = array_keys ($netlist);
2103 foreach ($netids as $cid)
2104 if (!in_array ($netlist[$cid]['parent_id'], $netids))
2105 $netlist[$cid]['parent_id'] = NULL;
2106 $tree = treeFromList ($netlist); // medium call
2107 sortTree ($tree, 'IPv4NetworkCmp');
2108 // complement the tree before markup to make the spare networks have "symbol" set
2109 treeApplyFunc ($tree, 'iptree_fill');
2110 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
2111 // count addresses after the markup to skip computation for hidden tree nodes
2112 treeApplyFunc ($tree, 'countOwnIPv4Addresses', 'nodeIsCollapsed');
2113 return $tree;
2114 }
2115
2116 function prepareIPv6Tree ($netlist, $expanded_id = 0)
2117 {
2118 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
2119 // so perform necessary pre-processing to make orphans belong to root. This trick
2120 // was earlier performed by getIPv4NetworkList().
2121 $netids = array_keys ($netlist);
2122 foreach ($netids as $cid)
2123 if (!in_array ($netlist[$cid]['parent_id'], $netids))
2124 $netlist[$cid]['parent_id'] = NULL;
2125 $tree = treeFromList ($netlist); // medium call
2126 sortTree ($tree, 'IPv6NetworkCmp');
2127 // complement the tree before markup to make the spare networks have "symbol" set
2128 treeApplyFunc ($tree, 'ipv6tree_fill');
2129 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
2130 return $tree;
2131 }
2132
2133 # Traverse IPv4/IPv6 tree and return a list of all networks, which
2134 # exist in DB and don't have any sub-networks.
2135 function getTerminalNetworks ($tree)
2136 {
2137 $self = __FUNCTION__;
2138 $ret = array();
2139 foreach ($tree as $node)
2140 if ($node['kidc'] == 0 and isset ($node['realm']))
2141 $ret[] = $node;
2142 else
2143 $ret = array_merge ($ret, $self ($node['kids']));
2144 return $ret;
2145 }
2146
2147 // Check all items of the tree recursively, until the requested target id is
2148 // found. Mark all items leading to this item as "expanded", collapsing all
2149 // the rest, which exceed the given threshold (if the threshold is given).
2150 function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
2151 {
2152 $self = __FUNCTION__;
2153 $ret = FALSE;
2154 foreach (array_keys ($tree) as $key)
2155 {
2156 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
2157 $below = $self ($tree[$key]['kids'], $threshold, $target);
2158 if (!$tree[$key]['kidc']) // terminal node
2159 $tree[$key]['symbol'] = 'spacer';
2160 elseif ($tree[$key]['kidc'] < $threshold)
2161 $tree[$key]['symbol'] = 'node-expanded-static';
2162 elseif ($here or $below)
2163 $tree[$key]['symbol'] = 'node-expanded';
2164 else
2165 $tree[$key]['symbol'] = 'node-collapsed';
2166 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
2167 }
2168 return $ret;
2169 }
2170
2171 // Convert entity name to human-readable value
2172 function formatEntityName ($name) {
2173 switch ($name)
2174 {
2175 case 'ipv4net':
2176 return 'IPv4 Network';
2177 case 'ipv6net':
2178 return 'IPv6 Network';
2179 case 'ipv4rspool':
2180 return 'IPv4 RS Pool';
2181 case 'ipv4vs':
2182 return 'IPv4 Virtual Service';
2183 case 'object':
2184 return 'Object';
2185 case 'rack':
2186 return 'Rack';
2187 case 'user':
2188 return 'User';
2189 }
2190 return 'invalid';
2191 }
2192
2193 // Display hrefs for all of a file's parents. If scissors are requested,
2194 // prepend cutting button to each of them.
2195 function serializeFileLinks ($links, $scissors = FALSE)
2196 {
2197 $comma = '';
2198 $ret = '';
2199 foreach ($links as $link_id => $li)
2200 {
2201 switch ($li['entity_type'])
2202 {
2203 case 'ipv4net':
2204 $params = "page=ipv4net&id=";
2205 break;
2206 case 'ipv6net':
2207 $params = "page=ipv6net&id=";
2208 break;
2209 case 'ipv4rspool':
2210 $params = "page=ipv4rspool&pool_id=";
2211 break;
2212 case 'ipv4vs':
2213 $params = "page=ipv4vs&vs_id=";
2214 break;
2215 case 'object':
2216 $params = "page=object&object_id=";
2217 break;
2218 case 'rack':
2219 $params = "page=rack&rack_id=";
2220 break;
2221 case 'user':
2222 $params = "page=user&user_id=";
2223 break;
2224 }
2225 $ret .= $comma;
2226 if ($scissors)
2227 {
2228 $ret .= "<a href='" . makeHrefProcess(array('op'=>'unlinkFile', 'link_id'=>$link_id)) . "'";
2229 $ret .= getImageHREF ('cut') . '</a> ';
2230 }
2231 $ret .= sprintf("<a href='index.php?%s%s'>%s</a>", $params, $li['entity_id'], $li['name']);
2232 $comma = '<br>';
2233 }
2234 return $ret;
2235 }
2236
2237 // Convert filesize to appropriate unit and make it human-readable
2238 function formatFileSize ($bytes) {
2239 // bytes
2240 if($bytes < 1024) // bytes
2241 return "${bytes} bytes";
2242
2243 // kilobytes
2244 if ($bytes < 1024000)
2245 return sprintf ("%.1fk", round (($bytes / 1024), 1));
2246
2247 // megabytes
2248 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
2249 }
2250
2251 // Reverse of formatFileSize, it converts human-readable value to bytes
2252 function convertToBytes ($value) {
2253 $value = trim($value);
2254 $last = strtolower($value[strlen($value)-1]);
2255 switch ($last)
2256 {
2257 case 'g':
2258 $value *= 1024;
2259 case 'm':
2260 $value *= 1024;
2261 case 'k':
2262 $value *= 1024;
2263 }
2264
2265 return $value;
2266 }
2267
2268 function ip_quad2long ($ip)
2269 {
2270 return sprintf("%u", ip2long($ip));
2271 }
2272
2273 function ip_long2quad ($quad)
2274 {
2275 return long2ip($quad);
2276 }
2277
2278 // make "A" HTML element
2279 function mkA ($text, $nextpage, $bypass = NULL, $nexttab = NULL)
2280 {
2281 global $page, $tab;
2282 if (!mb_strlen ($text))
2283 throw new InvalidArgException ('text', $text);
2284 if (!array_key_exists ($nextpage, $page))
2285 throw new InvalidArgException ('nextpage', $nextpage);
2286 $args = array ('page' => $nextpage);
2287 if ($nexttab !== NULL)
2288 {
2289 if (!array_key_exists ($nexttab, $tab[$nextpage]))
2290 throw new InvalidArgException ('nexttab', $nexttab);
2291 $args['tab'] = $nexttab;
2292 }
2293 if (array_key_exists ('bypass', $page[$nextpage]))
2294 {
2295 if ($bypass === NULL)
2296 throw new InvalidArgException ('bypass', $bypass);
2297 $args[$page[$nextpage]['bypass']] = $bypass;
2298 }
2299 return '<a href="' . makeHref ($args) . '">' . $text . '</a>';
2300 }
2301
2302 // make "HREF" HTML attribute
2303 function makeHref($params = array())
2304 {
2305 $ret = 'index.php?';
2306 $first = true;
2307 foreach($params as $key=>$value)
2308 {
2309 if (!$first)
2310 $ret.='&';
2311 $ret .= urlencode($key).'='.urlencode($value);
2312 $first = false;
2313 }
2314 return $ret;
2315 }
2316
2317 function makeHrefProcess($params = array())
2318 {
2319 global $pageno, $tabno;
2320 $ret = '?module=redirect&';
2321 $first = true;
2322 if (!isset($params['page']))
2323 $params['page'] = $pageno;
2324 if (!isset($params['tab']))
2325 $params['tab'] = $tabno;
2326 foreach($params as $key=>$value)
2327 {
2328 if (!$first)
2329 $ret.='&';
2330 $ret .= urlencode($key).'='.urlencode($value);
2331 $first = false;
2332 }
2333 return $ret;
2334 }
2335
2336 function makeHrefForHelper ($helper_name, $params = array())
2337 {
2338 $ret = '?module=popup&helper=' . $helper_name;
2339 foreach($params as $key=>$value)
2340 $ret .= '&'.urlencode($key).'='.urlencode($value);
2341 return $ret;
2342 }
2343
2344 // Process the given list of records to build data suitable for printNiftySelect()
2345 // (like it was formerly executed by printSelect()). Screen out vendors according
2346 // to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
2347 // selected OPTION is protected from being screened.
2348 function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
2349 {
2350 $ret = array();
2351 // Always keep "other" OPTGROUP at the SELECT bottom.
2352 $therest = array();
2353 foreach ($recordList as $dict_key => $dict_value)
2354 if (strpos ($dict_value, '%GSKIP%') !== FALSE)
2355 {
2356 $tmp = explode ('%GSKIP%', $dict_value, 2);
2357 $ret[$tmp[0]][$dict_key] = $tmp[1];
2358 }
2359 elseif (strpos ($dict_value, '%GPASS%') !== FALSE)
2360 {
2361 $tmp = explode ('%GPASS%', $dict_value, 2);
2362 $ret[$tmp[0]][$dict_key] = $tmp[1];
2363 }
2364 else
2365 $therest[$dict_key] = $dict_value;
2366 if ($object_type_id != 0)
2367 {
2368 $screenlist = array();
2369 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
2370 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
2371 $screenlist[] = $regs[1];
2372 }
2373 foreach (array_keys ($ret) as $vendor)
2374 if (in_array ($vendor, $screenlist))
2375 {
2376 $ok_to_screen = TRUE;
2377 if ($existing_value)
2378 foreach (array_keys ($ret[$vendor]) as $recordkey)
2379 if ($recordkey == $existing_value)
2380 {
2381 $ok_to_screen = FALSE;
2382 break;
2383 }
2384 if ($ok_to_screen)
2385 unset ($ret[$vendor]);
2386 }
2387 }
2388 $ret['other'] = $therest;
2389 return $ret;
2390 }
2391
2392 function unix2dos ($text)
2393 {
2394 return str_replace ("\n", "\r\n", $text);
2395 }
2396
2397 function buildPredicateTable ($parsetree)
2398 {
2399 $ret = array();
2400 foreach ($parsetree as $sentence)
2401 if ($sentence['type'] == 'SYNT_DEFINITION')
2402 $ret[$sentence['term']] = $sentence['definition'];
2403 // Now we have predicate table filled in with the latest definitions of each
2404 // particular predicate met. This isn't as chik, as on-the-fly predicate
2405 // overloading during allow/deny scan, but quite sufficient for this task.
2406 return $ret;
2407 }
2408
2409 // Take a list of records and filter against given RackCode expression. Return
2410 // the original list intact, if there was no filter requested, but return an
2411 // empty list, if there was an error.
2412 function filterCellList ($list_in, $expression = array())
2413 {
2414 if ($expression === NULL)
2415 return array();
2416 if (!count ($expression))
2417 return $list_in;
2418 $list_out = array();
2419 foreach ($list_in as $item_key => $item_value)
2420 if (TRUE === judgeCell ($item_value, $expression))
2421 $list_out[$item_key] = $item_value;
2422 return $list_out;
2423 }
2424
2425 function eval_expression ($expr, $tagchain, $ptable, $silent = FALSE)
2426 {
2427 $self = __FUNCTION__;
2428 switch ($expr['type'])
2429 {
2430 // Return true, if given tag is present on the tag chain.
2431 case 'LEX_TAG':
2432 case 'LEX_AUTOTAG':
2433 foreach ($tagchain as $tagInfo)
2434 if ($expr['load'] == $tagInfo['tag'])
2435 return TRUE;
2436 return FALSE;
2437 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it.
2438 $pname = $expr['load'];
2439 if (!isset ($ptable[$pname]))
2440 {
2441 if (!$silent)
2442 showWarning ("Predicate '${pname}' is referenced before declaration");
2443 return NULL;
2444 }
2445 return $self ($ptable[$pname], $tagchain, $ptable);
2446 case 'LEX_TRUE':
2447 return TRUE;
2448 case 'LEX_FALSE':
2449 return FALSE;
2450 case 'SYNT_NOT_EXPR':
2451 $tmp = $self ($expr['load'], $tagchain, $ptable);
2452 if ($tmp === TRUE)
2453 return FALSE;
2454 elseif ($tmp === FALSE)
2455 return TRUE;
2456 else
2457 return $tmp;
2458 case 'SYNT_AND_EXPR': // binary AND
2459 if (FALSE == $self ($expr['left'], $tagchain, $ptable))
2460 return FALSE; // early failure
2461 return $self ($expr['right'], $tagchain, $ptable);
2462 case 'SYNT_EXPR': // binary OR
2463 if (TRUE == $self ($expr['left'], $tagchain, $ptable))
2464 return TRUE; // early success
2465 return $self ($expr['right'], $tagchain, $ptable);
2466 default:
2467 if (!$silent)
2468 showWarning ("Evaluation error, cannot process expression type '${expr['type']}'");
2469 return NULL;
2470 break;
2471 }
2472 }
2473
2474 // Tell, if the given expression is true for the given entity. Take complete record on input.
2475 function judgeCell ($cell, $expression)
2476 {
2477 global $pTable;
2478 return eval_expression
2479 (
2480 $expression,
2481 array_merge
2482 (
2483 $cell['etags'],
2484 $cell['itags'],
2485 $cell['atags']
2486 ),
2487 $pTable,
2488 TRUE
2489 );
2490 }
2491
2492 function judgeContext ($expression)
2493 {
2494 global $pTable, $expl_tags, $impl_tags, $auto_tags;
2495 return eval_expression
2496 (
2497 $expression,
2498 array_merge
2499 (
2500 $expl_tags,
2501 $impl_tags,
2502 $auto_tags
2503 ),
2504 $pTable,
2505 TRUE
2506 );
2507 }
2508
2509 // Tell, if a constraint from config option permits current context.
2510 function considerConfiguredConstraintInContext ($varname)
2511 {
2512 if (!strlen (getConfigVar ($varname)))
2513 return TRUE; // no restriction
2514 global $parseCache;
2515 if (!isset ($parseCache[$varname]))
2516 // getConfigVar() doesn't re-read the value from DB because of its
2517 // own cache, so there is no race condition here between two calls.
2518 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2519 if ($parseCache[$varname]['result'] != 'ACK')
2520 return FALSE; // constraint set, but cannot be used due to compilation error
2521 return judgeContext ($parseCache[$varname]['load']);
2522 }
2523
2524 // Tell, if a constraint from config option permits given record.
2525 function considerConfiguredConstraint ($cell, $varname)
2526 {
2527 if (!strlen (getConfigVar ($varname)))
2528 return TRUE; // no restriction
2529 global $parseCache;
2530 if (!isset ($parseCache[$varname]))
2531 // getConfigVar() doesn't re-read the value from DB because of its
2532 // own cache, so there is no race condition here between two calls.
2533 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2534 if ($parseCache[$varname]['result'] != 'ACK')
2535 return FALSE; // constraint set, but cannot be used due to compilation error
2536 return judgeCell ($cell, $parseCache[$varname]['load']);
2537 }
2538
2539 // Tell, if the given arbitrary RackCode text addresses the given record
2540 // (an empty text matches any record).
2541 // An undefined $cell means current context.
2542 function considerGivenConstraint ($cell, $filtertext)
2543 {
2544 if ($filtertext == '')
2545 return TRUE;
2546 $parse = spotPayload ($filtertext, 'SYNT_EXPR');
2547 if ($parse['result'] != 'ACK')
2548 throw new InvalidRequestArgException ('filtertext', $filtertext, 'RackCode parsing error');
2549 if (isset ($cell))
2550 return judgeCell ($cell, $parse['load']);
2551 else
2552 return judgeContext ($parse['load']);
2553 }
2554
2555 // Return list of records in the given realm, which conform to
2556 // the given RackCode expression. If the realm is unknown or text
2557 // doesn't validate as a RackCode expression, return NULL.
2558 // Otherwise (successful scan) return a list of all matched
2559 // records, even if the list is empty (array() !== NULL). If the
2560 // text is an empty string, return all found records in the given
2561 // realm.
2562 function scanRealmByText ($realm = NULL, $ftext = '')
2563 {
2564 switch ($realm)
2565 {
2566 case 'object':
2567 case 'rack':
2568 case 'user':
2569 case 'ipv4net':
2570 case 'ipv6net':
2571 case 'file':
2572 case 'ipv4vs':
2573 case 'ipv4rspool':
2574 if (!strlen ($ftext = trim ($ftext)))
2575 $fexpr = array();
2576 else
2577 {
2578 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2579 if ($fparse['result'] != 'ACK')
2580 return NULL;
2581 $fexpr = $fparse['load'];
2582 }
2583 return filterCellList (listCells ($realm), $fexpr);
2584 default:
2585 throw new InvalidArgException ('$realm', $realm);
2586 }
2587
2588 }
2589
2590 function getIPv4VSOptions ()
2591 {
2592 $ret = array();
2593 foreach (listCells ('ipv4vs') as $vsid => $vsinfo)
2594 $ret[$vsid] = $vsinfo['dname'] . (!strlen ($vsinfo['name']) ? '' : " (${vsinfo['name']})");
2595 return $ret;
2596 }
2597
2598 function getIPv4RSPoolOptions ()
2599 {
2600 $ret = array();
2601 foreach (listCells ('ipv4rspool') as $pool_id => $poolInfo)
2602 $ret[$pool_id] = $poolInfo['name'];
2603 return $ret;
2604 }
2605
2606 // Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2607 function dump ($var)
2608 {
2609 echo '<div align=left><pre>';
2610 print_r ($var);
2611 echo '</pre></div>';
2612 }
2613
2614 function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2615 {
2616 global $taglist;
2617 // first build top-N chart...
2618 $toplist = array();
2619 foreach ($taglist as $taginfo)
2620 if (isset ($taginfo['refcnt'][$realm]))
2621 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2622 arsort ($toplist, SORT_NUMERIC);
2623 $ret = array();
2624 $done = 0;
2625 foreach (array_keys ($toplist) as $tag_id)
2626 {
2627 $ret[$tag_id] = $taglist[$tag_id];
2628 if (++$done == $limit)
2629 break;
2630 }
2631 // ...then make sure, that every item of the special list is shown
2632 // (using the same sort order)
2633 $extra = array();
2634 foreach ($special_tags as $taginfo)
2635 if (!array_key_exists ($taginfo['id'], $ret))
2636 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2637 arsort ($extra, SORT_NUMERIC);
2638 foreach (array_keys ($extra) as $tag_id)
2639 $ret[] = $taglist[$tag_id];
2640 return $ret;
2641 }
2642
2643 function decodeObjectType ($objtype_id, $style = 'r')
2644 {
2645 static $types = array();
2646 if (!count ($types))
2647 $types = array
2648 (
2649 'r' => readChapter (CHAP_OBJTYPE),
2650 'a' => readChapter (CHAP_OBJTYPE, 'a'),
2651 'o' => readChapter (CHAP_OBJTYPE, 'o')
2652 );
2653 return $types[$style][$objtype_id];
2654 }
2655
2656 function isolatedPermission ($p, $t, $cell)
2657 {
2658 // This function is called from both "file" page and a number of other pages,
2659 // which have already fixed security context and authorized the user for it.
2660 // OTOH, it is necessary here to authorize against the current file, which
2661 // means saving the current context and building a new one.
2662 global
2663 $expl_tags,
2664 $impl_tags,
2665 $target_given_tags,
2666 $auto_tags;
2667 // push current context
2668 $orig_expl_tags = $expl_tags;
2669 $orig_impl_tags = $impl_tags;
2670 $orig_target_given_tags = $target_given_tags;
2671 $orig_auto_tags = $auto_tags;
2672 // retarget
2673 fixContext ($cell);
2674 // remember decision
2675 $ret = permitted ($p, $t);
2676 // pop context
2677 $expl_tags = $orig_expl_tags;
2678 $impl_tags = $orig_impl_tags;
2679 $target_given_tags = $orig_target_given_tags;
2680 $auto_tags = $orig_auto_tags;
2681 return $ret;
2682 }
2683
2684 function getPortListPrefs()
2685 {
2686 $ret = array();
2687 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
2688 $ret['iif_pick'] = 1;
2689 $ret['oif_picks'] = array();
2690 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
2691 {
2692 $tmp = explode ('=', trim ($tmp));
2693 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
2694 $ret['oif_picks'][$tmp[0]] = $tmp[1];
2695 }
2696 // enforce default value
2697 if (!array_key_exists (1, $ret['oif_picks']))
2698 $ret['oif_picks'][1] = 24;
2699 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
2700 return $ret;
2701 }
2702
2703 // Return data for printNiftySelect() with port type options. All OIF options
2704 // for the default IIF will be shown, but only the default OIFs will be present
2705 // for each other IIFs. IIFs, for which there is no default OIF, will not
2706 // be listed.
2707 // This SELECT will be used for the "add new port" form.
2708 function getNewPortTypeOptions()
2709 {
2710 $ret = array();
2711 $prefs = getPortListPrefs();
2712 foreach (getPortInterfaceCompat() as $row)
2713 {
2714 if ($row['iif_id'] == $prefs['iif_pick'])
2715 $optgroup = $row['iif_name'];
2716 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
2717 $optgroup = 'other';
2718 else
2719 continue;
2720 if (!array_key_exists ($optgroup, $ret))
2721 $ret[$optgroup] = array();
2722 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
2723 }
2724 return $ret;
2725 }
2726
2727 // Return a serialized version of VLAN configuration for a port.
2728 // If a native VLAN is defined, print it first. All other VLANs
2729 // are tagged and are listed after a plus sign. When no configuration
2730 // is set for a port, return "default" string.
2731 function serializeVLANPack ($vlanport)
2732 {
2733 if (!array_key_exists ('mode', $vlanport))
2734 return 'error';
2735 switch ($vlanport['mode'])
2736 {
2737 case 'none':
2738 return 'none';
2739 case 'access':
2740 $ret = 'A';
2741 break;
2742 case 'trunk':
2743 $ret = 'T';
2744 break;
2745 case 'uplink':
2746 $ret = 'U';
2747 break;
2748 case 'downlink':
2749 $ret = 'D';
2750 break;
2751 default:
2752 return 'error';
2753 }
2754 if ($vlanport['native'])
2755 $ret .= $vlanport['native'];
2756 $tagged_bits = groupIntsToRanges ($vlanport['allowed'], $vlanport['native']);
2757 if (count ($tagged_bits))
2758 $ret .= '+' . implode (', ', $tagged_bits);
2759 return strlen ($ret) ? $ret : 'default';
2760 }
2761
2762 function groupIntsToRanges ($list, $exclude_value = NULL)
2763 {
2764 $result = array();
2765 sort ($list);
2766 $id_from = $id_to = 0;
2767 $list[] = -1;
2768 foreach ($list as $next_id)
2769 if (!isset ($exclude_value) or $next_id != $exclude_value)
2770 if ($id_to && $next_id == $id_to + 1)
2771 $id_to = $next_id; // merge
2772 else
2773 {
2774 if ($id_to)
2775 $result[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}"; // flush
2776 $id_from = $id_to = $next_id; // start next pair
2777 }
2778 return $result;
2779 }
2780
2781 // Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
2782 // return the numbers as an array of two.
2783 function decodeVLANCK ($string)
2784 {
2785 $matches = array();
2786 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
2787 throw new InvalidArgException ('VLAN compound key', $string);
2788 return array ($matches[1], $matches[2]);
2789 }
2790
2791 // Return VLAN name formatted for HTML output (note, that input
2792 // argument comes from database unescaped).
2793 function formatVLANName ($vlaninfo, $context = 'markup long')
2794 {
2795 switch ($context)
2796 {
2797 case 'option':
2798 $ret = $vlaninfo['vlan_id'];
2799 if ($vlaninfo['vlan_descr'] != '')
2800 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
2801 return $ret;
2802 case 'label':
2803 $ret = $vlaninfo['vlan_id'];
2804 if ($vlaninfo['vlan_descr'] != '')
2805 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2806 return $ret;
2807 case 'plain long':
2808 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2809 if ($vlaninfo['vlan_descr'] != '')
2810 $ret .= ' (' . niftyString ($vlaninfo['vlan_descr'], 20, FALSE) . ')';
2811 return $ret;
2812 case 'hyperlink':
2813 $ret = '<a href="';
2814 $ret .= makeHref (array ('page' => 'vlan', 'vlan_ck' => $vlaninfo['domain_id'] . '-' . $vlaninfo['vlan_id']));
2815 $ret .= '">' . formatVLANName ($vlaninfo, 'markup long') . '</a>';
2816 return $ret;
2817 case 'markup long':
2818 default:
2819 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2820 $ret .= ' @' . niftyString ($vlaninfo['domain_descr']);
2821 if ($vlaninfo['vlan_descr'] != '')
2822 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2823 return $ret;
2824 }
2825 }
2826
2827 // map interface name
2828 function ios12ShortenIfName ($ifname)
2829 {
2830 if (preg_match ('@^eth-trunk(\d+)$@i', $ifname, $m))
2831 return "Eth-Trunk${m[1]}";
2832 $ifname = preg_replace ('@^Eth(?:ernet)?(.+)$@', 'e\\1', $ifname);
2833 $ifname = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $ifname);
2834 $ifname = preg_replace ('@^(?:GigabitEthernet|GE)(.+)$@', 'gi\\1', $ifname);
2835 $ifname = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $ifname);
2836 $ifname = preg_replace ('@^Port-channel(.+)$@', 'po\\1', $ifname);
2837 $ifname = preg_replace ('@^(?:XGigabitEthernet|XGE)(.+)$@', 'xg\\1', $ifname);
2838 $ifname = strtolower ($ifname);
2839 return $ifname;
2840 }
2841
2842 function iosParseVLANString ($string)
2843 {
2844 $ret = array();
2845 foreach (explode (',', $string) as $item)
2846 {
2847 $matches = array();
2848 $item = trim ($item, ' ');
2849 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches))
2850 $ret[] = $matches[1];
2851 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches))
2852 $ret = array_merge ($ret, range ($matches[1], $matches[2]));
2853 }
2854 return $ret;
2855 }
2856
2857 // Scan given array and return the key, which addresses the first item
2858 // with requested column set to given value (or NULL if there is none such).
2859 // Note that 0 and NULL mean completely different things and thus
2860 // require strict checking (=== and !===).
2861 function scanArrayForItem ($table, $scan_column, $scan_value)
2862 {
2863 foreach ($table as $key => $row)
2864 if ($row[$scan_column] == $scan_value)
2865 return $key;
2866 return NULL;
2867 }
2868
2869 // Return TRUE, if every value of A1 is present in A2 and vice versa,
2870 // regardless of each array's sort order and indexing.
2871 function array_values_same ($a1, $a2)
2872 {
2873 return !count (array_diff ($a1, $a2)) and !count (array_diff ($a2, $a1));
2874 }
2875
2876 // Use the VLAN switch template to set VST role for each port of
2877 // the provided list. Return resulting list.
2878 function apply8021QOrder ($vst_id, $portlist)
2879 {
2880 $vst = getVLANSwitchTemplate ($vst_id);
2881 foreach (array_keys ($portlist) as $port_name)
2882 {
2883 foreach ($vst['rules'] as $rule)
2884 if (preg_match ($rule['port_pcre'], $port_name))
2885 {
2886 $portlist[$port_name]['vst_role'] = $rule['port_role'];
2887 $portlist[$port_name]['wrt_vlans'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']);
2888 continue 2;
2889 }
2890 $portlist[$port_name]['vst_role'] = 'none';
2891 }
2892 return $portlist;
2893 }
2894
2895 // return a sequence of ranges for given string form and port role
2896 function buildVLANFilter ($role, $string)
2897 {
2898 // set base
2899 switch ($role)
2900 {
2901 case 'access': // 1-4094
2902 $min = VLAN_MIN_ID;
2903 $max = VLAN_MAX_ID;
2904 break;
2905 case 'trunk': // 2-4094
2906 case 'uplink':
2907 case 'downlink':
2908 case 'anymode':
2909 $min = VLAN_MIN_ID + 1;
2910 $max = VLAN_MAX_ID;
2911 break;
2912 default: // none
2913 return array();
2914 }
2915 if ($string == '') // fast track
2916 return array (array ('from' => $min, 'to' => $max));
2917 // transform
2918 $vlanidlist = array();
2919 foreach (iosParseVLANString ($string) as $vlan_id)
2920 if ($min <= $vlan_id and $vlan_id <= $max)
2921 $vlanidlist[] = $vlan_id;
2922 return listToRanges ($vlanidlist);
2923 }
2924
2925 // pack set of integers into list of integer ranges
2926 // e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11))
2927 // The second argument, when it is different from 0, limits amount of
2928 // items in each generated range.
2929 function listToRanges ($vlanidlist, $limit = 0)
2930 {
2931 sort ($vlanidlist);
2932 $ret = array();
2933 $from = $to = NULL;
2934 foreach ($vlanidlist as $vlan_id)
2935 if ($from == NULL)
2936 {
2937 if ($limit == 1)
2938 $ret[] = array ('from' => $vlan_id, 'to' => $vlan_id);
2939 else
2940 $from = $to = $vlan_id;
2941 }
2942 elseif ($to + 1 == $vlan_id)
2943 {
2944 $to = $vlan_id;
2945 if ($to - $from + 1 == $limit)
2946 {
2947 // cut accumulated range and start over
2948 $ret[] = array ('from' => $from, 'to' => $to);
2949 $from = $to = NULL;
2950 }
2951 }
2952 else
2953 {
2954 $ret[] = array ('from' => $from, 'to' => $to);
2955 $from = $to = $vlan_id;
2956 }
2957 if ($from != NULL)
2958 $ret[] = array ('from' => $from, 'to' => $to);
2959 return $ret;
2960 }
2961
2962 // return TRUE, if given VLAN ID belongs to one of filter's ranges
2963 function matchVLANFilter ($vlan_id, $vfilter)
2964 {
2965 foreach ($vfilter as $range)
2966 if ($range['from'] <= $vlan_id and $vlan_id <= $range['to'])
2967 return TRUE;
2968 return FALSE;
2969 }
2970
2971 function generate8021QDeployOps ($vswitch, $device_vlanlist, $before, $changes)
2972 {
2973 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
2974 $employed_vlans = getEmployedVlans ($vswitch['object_id'], $domain_vlanlist);
2975
2976 // only ignore VLANs, which exist and are explicitly shown as "alien"
2977 $old_managed_vlans = array();
2978 foreach ($device_vlanlist as $vlan_id)
2979 if
2980 (
2981 !array_key_exists ($vlan_id, $domain_vlanlist) or
2982 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien'
2983 )
2984 $old_managed_vlans[] = $vlan_id;
2985 $ports_to_do = array();
2986 $ports_to_do_queue1 = array();
2987 $ports_to_do_queue2 = array();
2988 $after = $before;
2989 foreach ($changes as $port_name => $port)
2990 {
2991 $changeset = array
2992 (
2993 'old_mode' => $before[$port_name]['mode'],
2994 'old_allowed' => $before[$port_name]['allowed'],
2995 'old_native' => $before[$port_name]['native'],
2996 'new_mode' => $port['mode'],
2997 'new_allowed' => $port['allowed'],
2998 'new_native' => $port['native'],
2999 );
3000 // put the ports with employed vlans first, the others - below them
3001 if (! count (array_intersect ($changeset['old_allowed'], $employed_vlans)))
3002 $ports_to_do_queue2[$port_name] = $changeset;
3003 else
3004 $ports_to_do_queue1[$port_name] = $changeset;
3005 $after[$port_name] = $port;
3006 }
3007 $ports_to_do = array_merge ($ports_to_do_queue1, $ports_to_do_queue2);
3008 // New VLAN table is a union of:
3009 // 1. all compulsory VLANs
3010 // 2. all "current" non-alien allowed VLANs of those ports, which are left
3011 // intact (regardless if a VLAN exists in VLAN domain, but looking,
3012 // if it is present in device's own VLAN table)
3013 // 3. all "new" allowed VLANs of those ports, which we do "push" now
3014 // Like for old_managed_vlans, a VLANs is never listed, only if it
3015 // exists and belongs to "alien" type.
3016 $new_managed_vlans = array();
3017 // 1
3018 foreach ($domain_vlanlist as $vlan_id => $vlan)
3019 if ($vlan['vlan_type'] == 'compulsory')
3020 $new_managed_vlans[] = $vlan_id;
3021 // 2
3022 foreach ($before as $port_name => $port)
3023 if (!array_key_exists ($port_name, $changes))
3024 foreach ($port['allowed'] as $vlan_id)
3025 {
3026 if (in_array ($vlan_id, $new_managed_vlans))
3027 continue;
3028 if
3029 (
3030 array_key_exists ($vlan_id, $domain_vlanlist) and
3031 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien'
3032 )
3033 continue;
3034 if (in_array ($vlan_id, $device_vlanlist))
3035 $new_managed_vlans[] = $vlan_id;
3036 }
3037 // 3
3038 foreach ($changes as $port)
3039 foreach ($port['allowed'] as $vlan_id)
3040 if
3041 (
3042 isset ($domain_vlanlist[$vlan_id]) and
3043 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' and
3044 !in_array ($vlan_id, $new_managed_vlans)
3045 )
3046 $new_managed_vlans[] = $vlan_id;
3047 $crq = array();
3048 // Before removing each old VLAN as such it is necessary to unassign
3049 // ports from it (to remove VLAN from each ports' list of "allowed"
3050 // VLANs). This change in turn requires, that a port's "native"
3051 // VLAN isn't set to the one being removed from its "allowed" list.
3052 foreach ($ports_to_do as $port_name => $port)
3053 switch ($port['old_mode'] . '->' . $port['new_mode'])
3054 {
3055 case 'trunk->trunk':
3056 // "old" native is set and differs from the "new" native
3057 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3058 $crq[] = array
3059 (
3060 'opcode' => 'unset native',
3061 'arg1' => $port_name,
3062 'arg2' => $port['old_native'],
3063 );
3064 $vlans_to_remove = array_diff ($port['old_allowed'], $port['new_allowed']);
3065 $queues = array();
3066 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first
3067 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards
3068 foreach ($queues as $queue)
3069 if (! empty ($queue))
3070 $crq[] = array
3071 (
3072 'opcode' => 'rem allowed',
3073 'port' => $port_name,
3074 'vlans' => $queue,
3075 );
3076 break;
3077 case 'access->access':
3078 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3079 $crq[] = array
3080 (
3081 'opcode' => 'unset access',
3082 'arg1' => $port_name,
3083 'arg2' => $port['old_native'],
3084 );
3085 break;
3086 case 'access->trunk':
3087 $crq[] = array
3088 (
3089 'opcode' => 'unset access',
3090 'arg1' => $port_name,
3091 'arg2' => $port['old_native'],
3092 );
3093 break;
3094 case 'trunk->access':
3095 if ($port['old_native'])
3096 $crq[] = array
3097 (
3098 'opcode' => 'unset native',
3099 'arg1' => $port_name,
3100 'arg2' => $port['old_native'],
3101 );
3102 $vlans_to_remove = $port['old_allowed'];
3103 $queues = array();
3104 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first
3105 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards
3106 foreach ($queues as $queue)
3107 if (! empty ($queue))
3108 $crq[] = array
3109 (
3110 'opcode' => 'rem allowed',
3111 'port' => $port_name,
3112 'vlans' => $queue,
3113 );
3114 break;
3115 default:
3116 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
3117 }
3118 // Now it is safe to unconfigure VLANs, which still exist on device,
3119 // but are not present on the "new" list.
3120 // FIXME: put all IDs into one pseudo-command to make it easier
3121 // for translators to create/destroy VLANs in batches, where
3122 // target platform allows them to do.
3123 foreach (array_diff ($old_managed_vlans, $new_managed_vlans) as $vlan_id)
3124 $crq[] = array
3125 (
3126 'opcode' => 'destroy VLAN',
3127 'arg1' => $vlan_id,
3128 );
3129 // Configure VLANs, which must be present on the device, but are not yet.
3130 foreach (array_diff ($new_managed_vlans, $old_managed_vlans) as $vlan_id)
3131 $crq[] = array
3132 (
3133 'opcode' => 'create VLAN',
3134 'arg1' => $vlan_id,
3135 );
3136 // Now, when all new VLANs are created (queued), it is safe to assign (queue)
3137 // ports to the new VLANs.
3138 foreach ($ports_to_do as $port_name => $port)
3139 switch ($port['old_mode'] . '->' . $port['new_mode'])
3140 {
3141 case 'trunk->trunk':
3142 // For each allowed VLAN, which is present on the "new" list and missing from
3143 // the "old" one, queue a command to assign current port to that VLAN.
3144 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed'])))
3145 $crq[] = array