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