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