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