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