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