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