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