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