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