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