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