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