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