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