r3941 #372: some interface issues related with tag-tree-shrink-feature (commit #3927)
[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;
3dcf526a 1233 if ($preselect['andor'] != 'and' || empty($entity_list) && $preselect['is_empty'])
95857b5c
DO
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 1310 }
3dcf526a 1311 $andor_used = FALSE;
b48268d8
RF
1312 //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.
1313 if(isset($_REQUEST['andor']))
1314 {
3dcf526a 1315 $andor_used = TRUE;
b48268d8 1316 unset($_SESSION[$pageno]);
23cdc7e9 1317 }
01b9a31a 1318 if (isset ($_SESSION[$pageno]['tagfilter']) and is_array ($_SESSION[$pageno]['tagfilter']) and !(isset($_REQUEST['cft'])) and $staticFilter == 'yes')
b8970c7e 1319 {
eca0114c 1320 $_REQUEST['cft'] = $_SESSION[$pageno]['tagfilter'];
b8970c7e 1321 }
01b9a31a 1322 if (isset ($_SESSION[$pageno]['cfe']) and !(isset($sic['cfe'])) and $staticFilter == 'yes')
b8970c7e 1323 {
eca0114c 1324 $sic['cfe'] = $_SESSION[$pageno]['cfe'];
b8970c7e 1325 }
01b9a31a 1326 if (isset ($_SESSION[$pageno]['andor']) and !(isset($_REQUEST['andor'])) and $staticFilter == 'yes')
b8970c7e 1327 {
eca0114c 1328 $_REQUEST['andor'] = $_SESSION[$pageno]['andor'];
b8970c7e
RF
1329 }
1330
eca0114c 1331
3d670bba
DO
1332 $ret = array
1333 (
23cdc7e9
DO
1334 'tagidlist' => array(),
1335 'tnamelist' => array(),
1336 'pnamelist' => array(),
1337 'andor' => '',
3d670bba 1338 'text' => '',
23cdc7e9
DO
1339 'extratext' => '',
1340 'expression' => array(),
a8efc03e 1341 'urlextra' => '', // Just put text here and let makeHref call urlencode().
3dcf526a 1342 'is_empty' => TRUE,
3d670bba 1343 );
23cdc7e9 1344 switch (TRUE)
31c941ec 1345 {
23cdc7e9
DO
1346 case (!isset ($_REQUEST['andor'])):
1347 $andor2 = getConfigVar ('FILTER_DEFAULT_ANDOR');
3d670bba 1348 break;
23cdc7e9
DO
1349 case ($_REQUEST['andor'] == 'and'):
1350 case ($_REQUEST['andor'] == 'or'):
eca0114c 1351 $_SESSION[$pageno]['andor'] = $_REQUEST['andor'];
23cdc7e9 1352 $ret['andor'] = $andor2 = $_REQUEST['andor'];
3d670bba
DO
1353 break;
1354 default:
0cc24e9a 1355 showWarning ('Invalid and/or switch value in submitted form', __FUNCTION__);
3d670bba 1356 return NULL;
31c941ec 1357 }
23cdc7e9
DO
1358 $andor1 = '';
1359 // Both tags and predicates, which don't exist, should be
1360 // handled somehow. Discard them silently for now.
1361 if (isset ($_REQUEST['cft']) and is_array ($_REQUEST['cft']))
31c941ec 1362 {
eca0114c 1363 $_SESSION[$pageno]['tagfilter'] = $_REQUEST['cft'];
23cdc7e9
DO
1364 global $taglist;
1365 foreach ($_REQUEST['cft'] as $req_id)
1366 if (isset ($taglist[$req_id]))
1367 {
1368 $ret['tagidlist'][] = $req_id;
1369 $ret['tnamelist'][] = $taglist[$req_id]['tag'];
3dcf526a 1370 $andor_used = $andor_used || (trim($andor1) != '');
23cdc7e9
DO
1371 $ret['text'] .= $andor1 . '{' . $taglist[$req_id]['tag'] . '}';
1372 $andor1 = ' ' . $andor2 . ' ';
a8efc03e 1373 $ret['urlextra'] .= '&cft[]=' . $req_id;
23cdc7e9
DO
1374 }
1375 }
1376 if (isset ($_REQUEST['cfp']) and is_array ($_REQUEST['cfp']))
1377 {
1378 global $pTable;
1379 foreach ($_REQUEST['cfp'] as $req_name)
1380 if (isset ($pTable[$req_name]))
1381 {
1382 $ret['pnamelist'][] = $req_name;
3dcf526a 1383 $andor_used = $andor_used || (trim($andor1) != '');
23cdc7e9
DO
1384 $ret['text'] .= $andor1 . '[' . $req_name . ']';
1385 $andor1 = ' ' . $andor2 . ' ';
a8efc03e 1386 $ret['urlextra'] .= '&cfp[]=' . $req_name;
23cdc7e9
DO
1387 }
1388 }
7da7450c
DO
1389 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1390 if (isset ($sic['cfe']))
a8efc03e 1391 {
eca0114c 1392 $_SESSION[$pageno]['cfe'] = $sic['cfe'];
7da7450c
DO
1393 // Only consider extra text, when it is a correct RackCode expression.
1394 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1395 if ($parse['result'] == 'ACK')
1396 {
1397 $ret['extratext'] = trim ($sic['cfe']);
1398 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1399 }
a8efc03e 1400 }
23cdc7e9
DO
1401 $finaltext = array();
1402 if (strlen ($ret['text']))
1403 $finaltext[] = '(' . $ret['text'] . ')';
1404 if (strlen ($ret['extratext']))
1405 $finaltext[] = '(' . $ret['extratext'] . ')';
3dcf526a 1406 $andor_used = $andor_used || (count($finaltext) > 1);
23cdc7e9
DO
1407 $finaltext = implode (' ' . $andor2 . ' ', $finaltext);
1408 if (strlen ($finaltext))
1409 {
3dcf526a 1410 $ret['is_empty'] = FALSE;
23cdc7e9
DO
1411 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1412 $ret['expression'] = $parse['result'] == 'ACK' ? $parse['load'] : NULL;
8c699525
DO
1413 // It's not quite fair enough to put the blame of the whole text onto
1414 // non-empty "extra" portion of it, but it's the only user-generated portion
1415 // of it, thus the most probable cause of parse error.
1416 if (strlen ($ret['extratext']))
1417 $ret['extraclass'] = $parse['result'] == 'ACK' ? 'validation-success' : 'validation-error';
31c941ec 1418 }
3dcf526a
DO
1419 if (! $andor_used)
1420 $ret['andor'] = getConfigVar ('FILTER_DEFAULT_ANDOR');
1421 else
1422 $ret['urlextra'] .= '&andor=' . $ret['andor'];
3d670bba 1423 return $ret;
31c941ec
DO
1424}
1425
db55cf54
DO
1426// Return an empty message log.
1427function emptyLog ()
1428{
1429 return array
1430 (
1431 'v' => 2,
1432 'm' => array()
1433 );
1434}
1435
2987fc1f
DO
1436// Return a message log consisting of only one message.
1437function oneLiner ($code, $args = array())
1438{
db55cf54 1439 $ret = emptyLog();
2987fc1f
DO
1440 $ret['m'][] = count ($args) ? array ('c' => $code, 'a' => $args) : array ('c' => $code);
1441 return $ret;
46f92ff7
DO
1442}
1443
e53b1246
DO
1444// Merge message payload from two message logs given and return the result.
1445function mergeLogs ($log1, $log2)
1446{
1447 $ret = emptyLog();
1448 $ret['m'] = array_merge ($log1['m'], $log2['m']);
1449 return $ret;
1450}
1451
9f54e6e9 1452function validTagName ($s, $allow_autotag = FALSE)
2eeeca80 1453{
84986395 1454 if (1 == preg_match (TAGNAME_REGEXP, $s))
9f54e6e9 1455 return TRUE;
84986395 1456 if ($allow_autotag and 1 == preg_match (AUTOTAGNAME_REGEXP, $s))
9f54e6e9
DO
1457 return TRUE;
1458 return FALSE;
2eeeca80
DO
1459}
1460
53bae67b
DO
1461function redirectUser ($p, $t)
1462{
790a60e8
DO
1463 global $page;
1464 $l = "index.php?page=${p}&tab=${t}";
53bae67b
DO
1465 if (isset ($page[$p]['bypass']) and isset ($_REQUEST[$page[$p]['bypass']]))
1466 $l .= '&' . $page[$p]['bypass'] . '=' . $_REQUEST[$page[$p]['bypass']];
1467 header ("Location: " . $l);
1468 die;
1469}
1470
9f3e5caa
DO
1471function getRackCodeStats ()
1472{
1473 global $rackCode;
914b439b 1474 $defc = $grantc = $modc = 0;
9f3e5caa
DO
1475 foreach ($rackCode as $s)
1476 switch ($s['type'])
1477 {
1478 case 'SYNT_DEFINITION':
1479 $defc++;
1480 break;
1481 case 'SYNT_GRANT':
1482 $grantc++;
1483 break;
914b439b
DO
1484 case 'SYNT_CTXMOD':
1485 $modc++;
1486 break;
9f3e5caa
DO
1487 default:
1488 break;
1489 }
914b439b
DO
1490 $ret = array
1491 (
1492 'Definition sentences' => $defc,
1493 'Grant sentences' => $grantc,
1494 'Context mod sentences' => $modc
1495 );
9f3e5caa
DO
1496 return $ret;
1497}
1498
d5157018
DO
1499function getRackImageWidth ()
1500{
529eac25
DO
1501 global $rtwidth;
1502 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
d5157018
DO
1503}
1504
1505function getRackImageHeight ($units)
1506{
1507 return 3 + 3 + $units * 2;
1508}
1509
2987fc1f
DO
1510// Perform substitutions and return resulting string
1511// used solely by buildLVSConfig()
1512function apply_macros ($macros, $subject)
1513{
1514 $ret = $subject;
1515 foreach ($macros as $search => $replace)
1516 $ret = str_replace ($search, $replace, $ret);
1517 return $ret;
1518}
1519
1520function buildLVSConfig ($object_id = 0)
1521{
1522 if ($object_id <= 0)
1523 {
0cc24e9a 1524 showWarning ('Invalid argument', __FUNCTION__);
2987fc1f
DO
1525 return;
1526 }
6297d584 1527 $oInfo = spotEntity ('object', $object_id);
2987fc1f
DO
1528 $lbconfig = getSLBConfig ($object_id);
1529 if ($lbconfig === NULL)
1530 {
0cc24e9a 1531 showWarning ('getSLBConfig() failed', __FUNCTION__);
2987fc1f
DO
1532 return;
1533 }
1534 $newconfig = "#\n#\n# This configuration has been generated automatically by RackTables\n";
1535 $newconfig .= "# for object_id == ${object_id}\n# object name: ${oInfo['name']}\n#\n#\n\n\n";
1536 foreach ($lbconfig as $vs_id => $vsinfo)
1537 {
1538 $newconfig .= "########################################################\n" .
59a83bd8
DO
1539 "# VS (id == ${vs_id}): " . (!strlen ($vsinfo['vs_name']) ? 'NO NAME' : $vsinfo['vs_name']) . "\n" .
1540 "# RS pool (id == ${vsinfo['pool_id']}): " . (!strlen ($vsinfo['pool_name']) ? 'ANONYMOUS' : $vsinfo['pool_name']) . "\n" .
2987fc1f
DO
1541 "########################################################\n";
1542 # The order of inheritance is: VS -> LB -> pool [ -> RS ]
1543 $macros = array
1544 (
1545 '%VIP%' => $vsinfo['vip'],
1546 '%VPORT%' => $vsinfo['vport'],
1547 '%PROTO%' => $vsinfo['proto'],
1548 '%VNAME%' => $vsinfo['vs_name'],
1549 '%RSPOOLNAME%' => $vsinfo['pool_name']
1550 );
1551 $newconfig .= "virtual_server ${vsinfo['vip']} ${vsinfo['vport']} {\n";
1552 $newconfig .= "\tprotocol ${vsinfo['proto']}\n";
1553 $newconfig .= apply_macros
1554 (
1555 $macros,
1556 lf_wrap ($vsinfo['vs_vsconfig']) .
1557 lf_wrap ($vsinfo['lb_vsconfig']) .
1558 lf_wrap ($vsinfo['pool_vsconfig'])
1559 );
1560 foreach ($vsinfo['rslist'] as $rs)
1561 {
59a83bd8 1562 if (!strlen ($rs['rsport']))
79a9edb4 1563 $rs['rsport'] = $vsinfo['vport'];
2987fc1f
DO
1564 $macros['%RSIP%'] = $rs['rsip'];
1565 $macros['%RSPORT%'] = $rs['rsport'];
1566 $newconfig .= "\treal_server ${rs['rsip']} ${rs['rsport']} {\n";
1567 $newconfig .= apply_macros
1568 (
1569 $macros,
1570 lf_wrap ($vsinfo['vs_rsconfig']) .
1571 lf_wrap ($vsinfo['lb_rsconfig']) .
1572 lf_wrap ($vsinfo['pool_rsconfig']) .
1573 lf_wrap ($rs['rs_rsconfig'])
1574 );
1575 $newconfig .= "\t}\n";
1576 }
1577 $newconfig .= "}\n\n\n";
1578 }
3dd72e34 1579 // FIXME: deal somehow with Mac-styled text, the below replacement will screw it up
4a123eec 1580 return dos2unix ($newconfig);
2987fc1f
DO
1581}
1582
2d318652
DO
1583// Indicate occupation state of each IP address: none, ordinary or problematic.
1584function markupIPv4AddrList (&$addrlist)
1585{
1586 foreach (array_keys ($addrlist) as $ip_bin)
1587 {
d983f70a
DO
1588 $refc = array
1589 (
00c5d8cb
DO
1590 'shared' => 0, // virtual
1591 'virtual' => 0, // loopback
1592 'regular' => 0, // connected host
1593 'router' => 0 // connected gateway
d983f70a
DO
1594 );
1595 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1596 $refc[$a['type']]++;
00c5d8cb 1597 $nvirtloopback = ($refc['shared'] + $refc['virtual'] > 0) ? 1 : 0; // modulus of virtual + shared
d983f70a
DO
1598 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
1599 $nrealms = $nreserved + $nvirtloopback + $refc['regular'] + $refc['router']; // latter two are connected and router allocations
2d318652
DO
1600
1601 if ($nrealms == 1)
1602 $addrlist[$ip_bin]['class'] = 'trbusy';
1603 elseif ($nrealms > 1)
1604 $addrlist[$ip_bin]['class'] = 'trerror';
1605 else
1606 $addrlist[$ip_bin]['class'] = '';
1607 }
1608}
1609
04d619d0
DO
1610// Scan the given address list (returned by scanIPv4Space) and return a list of all routers found.
1611function findRouters ($addrlist)
1612{
1613 $ret = array();
1614 foreach ($addrlist as $addr)
1615 foreach ($addr['allocs'] as $alloc)
1616 if ($alloc['type'] == 'router')
1617 $ret[] = array
1618 (
1619 'id' => $alloc['object_id'],
1620 'iface' => $alloc['name'],
1621 'dname' => $alloc['object_name'],
1622 'addr' => $addr['ip']
1623 );
1624 return $ret;
1625}
1626
fb7a4967
DO
1627// Assist in tag chain sorting.
1628function taginfoCmp ($tagA, $tagB)
1629{
1630 return $tagA['ci'] - $tagB['ci'];
1631}
1632
1327d9dd
DO
1633// Compare networks. When sorting a tree, the records on the list will have
1634// distinct base IP addresses.
3444ecf2
DO
1635// "The comparison function must return an integer less than, equal to, or greater
1636// than zero if the first argument is considered to be respectively less than,
1637// equal to, or greater than the second." (c) PHP manual
1327d9dd
DO
1638function IPv4NetworkCmp ($netA, $netB)
1639{
7f68bc8b
DO
1640 // On 64-bit systems this function can be reduced to just this:
1641 if (PHP_INT_SIZE == 8)
1642 return $netA['ip_bin'] - $netB['ip_bin'];
2d75c30b
DO
1643 // There's a problem just substracting one u32 integer from another,
1644 // because the result may happen big enough to become a negative i32
1645 // integer itself (PHP tries to cast everything it sees to signed int)
3444ecf2
DO
1646 // The comparison below must treat positive and negative values of both
1647 // arguments.
1648 // Equal values give instant decision regardless of their [equal] sign.
1649 if ($netA['ip_bin'] == $netB['ip_bin'])
2d75c30b 1650 return 0;
3444ecf2
DO
1651 // Same-signed values compete arithmetically within one of i32 contiguous ranges:
1652 // 0x00000001~0x7fffffff 1~2147483647
1653 // 0 doesn't have any sign, and network 0.0.0.0 isn't allowed
1654 // 0x80000000~0xffffffff -2147483648~-1
1655 $signA = $netA['ip_bin'] / abs ($netA['ip_bin']);
1656 $signB = $netB['ip_bin'] / abs ($netB['ip_bin']);
1657 if ($signA == $signB)
1658 {
1659 if ($netA['ip_bin'] > $netB['ip_bin'])
1660 return 1;
1661 else
1662 return -1;
1663 }
1664 else // With only one of two values being negative, it... wins!
1665 {
1666 if ($netA['ip_bin'] < $netB['ip_bin'])
1667 return 1;
1668 else
1669 return -1;
1670 }
1327d9dd
DO
1671}
1672
fb7a4967 1673// Modify the given tag tree so, that each level's items are sorted alphabetically.
1327d9dd 1674function sortTree (&$tree, $sortfunc = '')
fb7a4967 1675{
59a83bd8 1676 if (!strlen ($sortfunc))
1327d9dd 1677 return;
51b6651a 1678 $self = __FUNCTION__;
1327d9dd 1679 usort ($tree, $sortfunc);
fb7a4967
DO
1680 // Don't make a mistake of directly iterating over the items of current level, because this way
1681 // the sorting will be performed on a _copy_ if each item, not the item itself.
1682 foreach (array_keys ($tree) as $tagid)
51b6651a 1683 $self ($tree[$tagid]['kids'], $sortfunc);
fb7a4967
DO
1684}
1685
0137d53c
DO
1686function iptree_fill (&$netdata)
1687{
59a83bd8 1688 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
0137d53c 1689 return;
a987ff52 1690 // If we really have nested prefixes, they must fit into the tree.
0137d53c
DO
1691 $worktree = array
1692 (
1693 'ip_bin' => $netdata['ip_bin'],
1694 'mask' => $netdata['mask']
1695 );
1696 foreach ($netdata['kids'] as $pfx)
1697 iptree_embed ($worktree, $pfx);
1698 $netdata['kids'] = iptree_construct ($worktree);
1699 $netdata['kidc'] = count ($netdata['kids']);
1700}
1701
1702function iptree_construct ($node)
1703{
1704 $self = __FUNCTION__;
1705
1706 if (!isset ($node['right']))
1707 {
1708 if (!isset ($node['ip']))
1709 {
1710 $node['ip'] = long2ip ($node['ip_bin']);
1711 $node['kids'] = array();
fec0c8da 1712 $node['kidc'] = 0;
0137d53c
DO
1713 $node['name'] = '';
1714 }
1715 return array ($node);
1716 }
1717 else
1718 return array_merge ($self ($node['left']), $self ($node['right']));
1719}
1720
1721function iptree_embed (&$node, $pfx)
1722{
1723 $self = __FUNCTION__;
1724
1725 // hit?
1726 if ($node['ip_bin'] == $pfx['ip_bin'] and $node['mask'] == $pfx['mask'])
1727 {
1728 $node = $pfx;
1729 return;
1730 }
1731 if ($node['mask'] == $pfx['mask'])
3a089a44 1732 throw new RackTablesError ('the recurring loop lost control', RackTablesError::INTERNAL);
0137d53c
DO
1733
1734 // split?
1735 if (!isset ($node['right']))
1736 {
a987ff52
DO
1737 // Fill in db_first/db_last to make it possible to run scanIPv4Space() on the node.
1738 $node['left']['mask'] = $node['mask'] + 1;
0137d53c 1739 $node['left']['ip_bin'] = $node['ip_bin'];
a987ff52
DO
1740 $node['left']['db_first'] = sprintf ('%u', $node['left']['ip_bin']);
1741 $node['left']['db_last'] = sprintf ('%u', $node['left']['ip_bin'] | binInvMaskFromDec ($node['left']['mask']));
1742
1743 $node['right']['mask'] = $node['mask'] + 1;
0137d53c 1744 $node['right']['ip_bin'] = $node['ip_bin'] + binInvMaskFromDec ($node['mask'] + 1) + 1;
a987ff52
DO
1745 $node['right']['db_first'] = sprintf ('%u', $node['right']['ip_bin']);
1746 $node['right']['db_last'] = sprintf ('%u', $node['right']['ip_bin'] | binInvMaskFromDec ($node['right']['mask']));
0137d53c
DO
1747 }
1748
1749 // repeat!
1750 if (($node['left']['ip_bin'] & binMaskFromDec ($node['left']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1751 $self ($node['left'], $pfx);
1752 elseif (($node['right']['ip_bin'] & binMaskFromDec ($node['right']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1753 $self ($node['right'], $pfx);
1754 else
3a089a44 1755 throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
0137d53c
DO
1756}
1757
3b81cb98 1758function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
0137d53c 1759{
59a83bd8 1760 if (!strlen ($func))
0137d53c
DO
1761 return;
1762 $self = __FUNCTION__;
1763 foreach (array_keys ($tree) as $key)
1764 {
1765 $func ($tree[$key]);
59a83bd8 1766 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
c3315231 1767 continue;
0137d53c
DO
1768 $self ($tree[$key]['kids'], $func);
1769 }
1770}
1771
a987ff52
DO
1772function loadIPv4AddrList (&$netinfo)
1773{
1774 loadOwnIPv4Addresses ($netinfo);
1775 markupIPv4AddrList ($netinfo['addrlist']);
1776}
b18d26dc 1777
b6b87070 1778function countOwnIPv4Addresses (&$node)
b18d26dc 1779{
b6b87070 1780 $toscan = array();
737a3f72 1781 $node['addrt'] = 0;
4d0dbd87
DO
1782 $node['mask_bin'] = binMaskFromDec ($node['mask']);
1783 $node['mask_bin_inv'] = binInvMaskFromDec ($node['mask']);
1784 $node['db_first'] = sprintf ('%u', 0x00000000 + $node['ip_bin'] & $node['mask_bin']);
1785 $node['db_last'] = sprintf ('%u', 0x00000000 + $node['ip_bin'] | ($node['mask_bin_inv']));
59a83bd8 1786 if (!count ($node['kids']))
737a3f72 1787 {
f7414fa5 1788 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
737a3f72
DO
1789 $node['addrt'] = binInvMaskFromDec ($node['mask']) + 1;
1790 }
b18d26dc 1791 else
b6b87070 1792 foreach ($node['kids'] as $nested)
b18d26dc 1793 if (!isset ($nested['id'])) // spare
737a3f72 1794 {
f7414fa5 1795 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
737a3f72
DO
1796 $node['addrt'] += binInvMaskFromDec ($nested['mask']) + 1;
1797 }
4d0dbd87
DO
1798 // Don't do anything more, because the displaying function will load the addresses anyway.
1799 return;
f7414fa5 1800 $node['addrc'] = count (scanIPv4Space ($toscan));
b6b87070
DO
1801}
1802
c3315231
DO
1803function nodeIsCollapsed ($node)
1804{
1805 return $node['symbol'] == 'node-collapsed';
1806}
1807
b6b87070
DO
1808function loadOwnIPv4Addresses (&$node)
1809{
1810 $toscan = array();
43eb71f1 1811 if (!isset ($node['kids']) or !count ($node['kids']))
f7414fa5 1812 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
b6b87070
DO
1813 else
1814 foreach ($node['kids'] as $nested)
1815 if (!isset ($nested['id'])) // spare
f7414fa5
DO
1816 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
1817 $node['addrlist'] = scanIPv4Space ($toscan);
b6b87070 1818 $node['addrc'] = count ($node['addrlist']);
b18d26dc
DO
1819}
1820
fec0c8da
DO
1821function prepareIPv4Tree ($netlist, $expanded_id = 0)
1822{
573214e0
DO
1823 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
1824 // so perform necessary pre-processing to make orphans belong to root. This trick
1825 // was earlier performed by getIPv4NetworkList().
1826 $netids = array_keys ($netlist);
1827 foreach ($netids as $cid)
1828 if (!in_array ($netlist[$cid]['parent_id'], $netids))
1829 $netlist[$cid]['parent_id'] = NULL;
fec0c8da
DO
1830 $tree = treeFromList ($netlist); // medium call
1831 sortTree ($tree, 'IPv4NetworkCmp');
fec0c8da 1832 // complement the tree before markup to make the spare networks have "symbol" set
c3315231
DO
1833 treeApplyFunc ($tree, 'iptree_fill');
1834 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
1835 // count addresses after the markup to skip computation for hidden tree nodes
1836 treeApplyFunc ($tree, 'countOwnIPv4Addresses', 'nodeIsCollapsed');
fec0c8da
DO
1837 return $tree;
1838}
1839
1840// Check all items of the tree recursively, until the requested target id is
1841// found. Mark all items leading to this item as "expanded", collapsing all
1842// the rest, which exceed the given threshold (if the threshold is given).
1843function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
1844{
1845 $self = __FUNCTION__;
1846 $ret = FALSE;
1847 foreach (array_keys ($tree) as $key)
1848 {
5388794d 1849 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
fec0c8da
DO
1850 $below = $self ($tree[$key]['kids'], $threshold, $target);
1851 if (!$tree[$key]['kidc']) // terminal node
1852 $tree[$key]['symbol'] = 'spacer';
1853 elseif ($tree[$key]['kidc'] < $threshold)
1854 $tree[$key]['symbol'] = 'node-expanded-static';
1855 elseif ($here or $below)
1856 $tree[$key]['symbol'] = 'node-expanded';
1857 else
1858 $tree[$key]['symbol'] = 'node-collapsed';
1859 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
1860 }
1861 return $ret;
1862}
1863
e1ae3fb4
AD
1864// Convert entity name to human-readable value
1865function formatEntityName ($name) {
1866 switch ($name)
1867 {
1868 case 'ipv4net':
1869 return 'IPv4 Network';
1870 case 'ipv4rspool':
1871 return 'IPv4 RS Pool';
1872 case 'ipv4vs':
1873 return 'IPv4 Virtual Service';
1874 case 'object':
1875 return 'Object';
1876 case 'rack':
1877 return 'Rack';
1878 case 'user':
1879 return 'User';
1880 }
1881 return 'invalid';
1882}
1883
1884// Take a MySQL or other generic timestamp and make it prettier
1885function formatTimestamp ($timestamp) {
1886 return date('n/j/y g:iA', strtotime($timestamp));
1887}
1888
8bc5d1e4
DO
1889// Display hrefs for all of a file's parents. If scissors are requested,
1890// prepend cutting button to each of them.
1891function serializeFileLinks ($links, $scissors = FALSE)
e1ae3fb4 1892{
e1ae3fb4
AD
1893 $comma = '';
1894 $ret = '';
1895 foreach ($links as $link_id => $li)
1896 {
1897 switch ($li['entity_type'])
1898 {
1899 case 'ipv4net':
1900 $params = "page=ipv4net&id=";
1901 break;
1902 case 'ipv4rspool':
1903 $params = "page=ipv4rspool&pool_id=";
1904 break;
1905 case 'ipv4vs':
1906 $params = "page=ipv4vs&vs_id=";
1907 break;
1908 case 'object':
1909 $params = "page=object&object_id=";
1910 break;
1911 case 'rack':
1912 $params = "page=rack&rack_id=";
1913 break;
1914 case 'user':
1915 $params = "page=user&user_id=";
1916 break;
1917 }
8bc5d1e4
DO
1918 $ret .= $comma;
1919 if ($scissors)
1920 {
1921 $ret .= "<a href='" . makeHrefProcess(array('op'=>'unlinkFile', 'link_id'=>$link_id)) . "'";
1922 $ret .= getImageHREF ('cut') . '</a> ';
1923 }
790a60e8 1924 $ret .= sprintf("<a href='index.php?%s%s'>%s</a>", $params, $li['entity_id'], $li['name']);
8bc5d1e4 1925 $comma = '<br>';
e1ae3fb4 1926 }
e1ae3fb4
AD
1927 return $ret;
1928}
1929
1930// Convert filesize to appropriate unit and make it human-readable
1931function formatFileSize ($bytes) {
1932 // bytes
1933 if($bytes < 1024) // bytes
1934 return "${bytes} bytes";
1935
1936 // kilobytes
1937 if ($bytes < 1024000)
1938 return sprintf ("%.1fk", round (($bytes / 1024), 1));
1939
1940 // megabytes
1941 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
1942}
1943
1944// Reverse of formatFileSize, it converts human-readable value to bytes
1945function convertToBytes ($value) {
1946 $value = trim($value);
1947 $last = strtolower($value[strlen($value)-1]);
1948 switch ($last)
1949 {
1950 case 'g':
1951 $value *= 1024;
1952 case 'm':
1953 $value *= 1024;
1954 case 'k':
1955 $value *= 1024;
1956 }
1957
1958 return $value;
1959}
1960
4fbb5a00
DY
1961function ip_quad2long ($ip)
1962{
1963 return sprintf("%u", ip2long($ip));
1964}
1965
1966function ip_long2quad ($quad)
1967{
1968 return long2ip($quad);
1969}
1970
1971function makeHref($params = array())
1972{
790a60e8 1973 $ret = 'index.php?';
4fbb5a00 1974 $first = true;
4fbb5a00
DY
1975 foreach($params as $key=>$value)
1976 {
1977 if (!$first)
1978 $ret.='&';
1979 $ret .= urlencode($key).'='.urlencode($value);
1980 $first = false;
1981 }
1982 return $ret;
1983}
1984
1985function makeHrefProcess($params = array())
1986{
790a60e8
DO
1987 global $pageno, $tabno;
1988 $ret = 'process.php?';
4fbb5a00 1989 $first = true;
9f14a7ef
DY
1990 if (!isset($params['page']))
1991 $params['page'] = $pageno;
1992 if (!isset($params['tab']))
1993 $params['tab'] = $tabno;
4fbb5a00
DY
1994 foreach($params as $key=>$value)
1995 {
1996 if (!$first)
1997 $ret.='&';
1998 $ret .= urlencode($key).'='.urlencode($value);
1999 $first = false;
2000 }
2001 return $ret;
2002}
2003
39106006 2004function makeHrefForHelper ($helper_name, $params = array())
4fbb5a00 2005{
790a60e8 2006 $ret = 'popup.php?helper=' . $helper_name;
4fbb5a00 2007 foreach($params as $key=>$value)
39106006 2008 $ret .= '&'.urlencode($key).'='.urlencode($value);
4fbb5a00
DY
2009 return $ret;
2010}
2011
f3d274bf
DO
2012// Process the given list of records to build data suitable for printNiftySelect()
2013// (like it was formerly executed by printSelect()). Screen out vendors according
2014// to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
2015// selected OPTION is protected from being screened.
2016function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
2017{
2018 $ret = array();
2019 // Always keep "other" OPTGROUP at the SELECT bottom.
2020 $therest = array();
2021 foreach ($recordList as $dict_key => $dict_value)
2022 if (strpos ($dict_value, '%GSKIP%') !== FALSE)
2023 {
2024 $tmp = explode ('%GSKIP%', $dict_value, 2);
2025 $ret[$tmp[0]][$dict_key] = $tmp[1];
2026 }
2027 elseif (strpos ($dict_value, '%GPASS%') !== FALSE)
2028 {
2029 $tmp = explode ('%GPASS%', $dict_value, 2);
2030 $ret[$tmp[0]][$dict_key] = $tmp[1];
2031 }
2032 else
2033 $therest[$dict_key] = $dict_value;
2034 if ($object_type_id != 0)
2035 {
2036 $screenlist = array();
2037 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
eea3ca5e 2038 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
f3d274bf 2039 $screenlist[] = $regs[1];
eea3ca5e 2040 }
f3d274bf
DO
2041 foreach (array_keys ($ret) as $vendor)
2042 if (in_array ($vendor, $screenlist))
2043 {
2044 $ok_to_screen = TRUE;
2045 if ($existing_value)
2046 foreach (array_keys ($ret[$vendor]) as $recordkey)
2047 if ($recordkey == $existing_value)
2048 {
2049 $ok_to_screen = FALSE;
2050 break;
2051 }
2052 if ($ok_to_screen)
2053 unset ($ret[$vendor]);
2054 }
2055 }
2056 $ret['other'] = $therest;
2057 return $ret;
2058}
2059
f8874cdb
DO
2060function dos2unix ($text)
2061{
2062 return str_replace ("\r\n", "\n", $text);
2063}
2064
9dd73255
DO
2065function unix2dos ($text)
2066{
2067 return str_replace ("\n", "\r\n", $text);
2068}
2069
a0527aef 2070function buildPredicateTable ($parsetree)
2c21a10c 2071{
a0527aef
DO
2072 $ret = array();
2073 foreach ($parsetree as $sentence)
72e8baf6 2074 if ($sentence['type'] == 'SYNT_DEFINITION')
a0527aef 2075 $ret[$sentence['term']] = $sentence['definition'];
72e8baf6
DO
2076 // Now we have predicate table filled in with the latest definitions of each
2077 // particular predicate met. This isn't as chik, as on-the-fly predicate
2078 // overloading during allow/deny scan, but quite sufficient for this task.
a0527aef
DO
2079 return $ret;
2080}
2081
2082// Take a list of records and filter against given RackCode expression. Return
2083// the original list intact, if there was no filter requested, but return an
2084// empty list, if there was an error.
573214e0 2085function filterCellList ($list_in, $expression = array())
d08d766d
DO
2086{
2087 if ($expression === NULL)
2088 return array();
2089 if (!count ($expression))
2090 return $list_in;
d08d766d
DO
2091 $list_out = array();
2092 foreach ($list_in as $item_key => $item_value)
573214e0 2093 if (TRUE === judgeCell ($item_value, $expression))
d08d766d
DO
2094 $list_out[$item_key] = $item_value;
2095 return $list_out;
2096}
2097
212c9d8a 2098// Tell, if the given expression is true for the given entity. Take complete record on input.
573214e0 2099function judgeCell ($cell, $expression)
d08d766d
DO
2100{
2101 global $pTable;
2102 return eval_expression
2103 (
2104 $expression,
2105 array_merge
2106 (
573214e0
DO
2107 $cell['etags'],
2108 $cell['itags'],
2109 $cell['atags']
d08d766d
DO
2110 ),
2111 $pTable,
2112 TRUE
2113 );
2114}
2115
7ddbcf59 2116// Tell, if a constraint from config option permits given record.
212c9d8a 2117function considerConfiguredConstraint ($cell, $varname)
c6bc0ac5 2118{
7ddbcf59 2119 if (!strlen (getConfigVar ($varname)))
c6bc0ac5 2120 return TRUE; // no restriction
7ddbcf59
DO
2121 global $parseCache;
2122 if (!isset ($parseCache[$varname]))
2123 // getConfigVar() doesn't re-read the value from DB because of its
2124 // own cache, so there is no race condition here between two calls.
2125 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2126 if ($parseCache[$varname]['result'] != 'ACK')
2127 return FALSE; // constraint set, but cannot be used due to compilation error
212c9d8a 2128 return judgeCell ($cell, $parseCache[$varname]['load']);
c6bc0ac5
DO
2129}
2130
17112e81
DO
2131// Return list of records in the given realm, which conform to
2132// the given RackCode expression. If the realm is unknown or text
2133// doesn't validate as a RackCode expression, return NULL.
2134// Otherwise (successful scan) return a list of all matched
2135// records, even if the list is empty (array() !== NULL). If the
2136// text is an empty string, return all found records in the given
2137// realm.
2138function scanRealmByText ($realm = NULL, $ftext = '')
2139{
2140 switch ($realm)
2141 {
2142 case 'object':
2143 case 'user':
2144 case 'ipv4net':
2145 case 'file':
39776127 2146 case 'ipv4vs':
41780975 2147 case 'ipv4rspool':
17112e81
DO
2148 if (!strlen ($ftext = trim ($ftext)))
2149 $fexpr = array();
2150 else
2151 {
2152 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2153 if ($fparse['result'] != 'ACK')
2154 return NULL;
2155 $fexpr = $fparse['load'];
2156 }
2157 return filterCellList (listCells ($realm), $fexpr);
2158 default:
c5f84f48 2159 throw new InvalidArgException ('$realm', $realm);
17112e81
DO
2160 }
2161
2162}
39776127
DO
2163
2164function getIPv4VSOptions ()
2165{
2166 $ret = array();
2167 foreach (listCells ('ipv4vs') as $vsid => $vsinfo)
59a83bd8 2168 $ret[$vsid] = $vsinfo['dname'] . (!strlen ($vsinfo['name']) ? '' : " (${vsinfo['name']})");
39776127
DO
2169 return $ret;
2170}
2171
41780975
DO
2172function getIPv4RSPoolOptions ()
2173{
2174 $ret = array();
2175 foreach (listCells ('ipv4rspool') as $pool_id => $poolInfo)
2176 $ret[$pool_id] = $poolInfo['name'];
2177 return $ret;
2178}
2179
d16af52f
DO
2180// Derive a complete cell structure from the given username regardless
2181// if it is a local account or not.
2182function constructUserCell ($username)
2183{
2184 if (NULL !== ($userid = getUserIDByUsername ($username)))
2185 return spotEntity ('user', $userid);
2186 $ret = array
2187 (
2188 'realm' => 'user',
2189 'user_name' => $username,
2190 'user_realname' => '',
2191 'etags' => array(),
2192 'itags' => array(),
d16af52f 2193 );
5689cefd 2194 $ret['atags'] = generateEntityAutoTags ($ret);
d16af52f
DO
2195 return $ret;
2196}
2197
56a797ef
DO
2198// Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2199function dump ($var)
2200{
2201 echo '<div align=left><pre>';
2202 print_r ($var);
2203 echo '</pre></div>';
2204}
2205
9e51318b
DO
2206function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2207{
2208 global $taglist;
2209 // first build top-N chart...
2210 $toplist = array();
2211 foreach ($taglist as $taginfo)
2212 if (isset ($taginfo['refcnt'][$realm]))
2213 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2214 arsort ($toplist, SORT_NUMERIC);
2215 $ret = array();
2216 $done = 0;
2217 foreach (array_keys ($toplist) as $tag_id)
2218 {
2219 $ret[$tag_id] = $taglist[$tag_id];
2220 if (++$done == $limit)
2221 break;
2222 }
2223 // ...then make sure, that every item of the special list is shown
2224 // (using the same sort order)
2225 $extra = array();
2226 foreach ($special_tags as $taginfo)
2227 if (!array_key_exists ($taginfo['id'], $ret))
2228 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2229 arsort ($extra, SORT_NUMERIC);
2230 foreach (array_keys ($extra) as $tag_id)
2231 $ret[] = $taglist[$tag_id];
2232 return $ret;
2233}
2234
7fa7047a
DO
2235function decodeObjectType ($objtype_id, $style = 'r')
2236{
2237 static $types = array();
2238 if (!count ($types))
2239 $types = array
2240 (
2241 'r' => readChapter (CHAP_OBJTYPE),
2242 'a' => readChapter (CHAP_OBJTYPE, 'a'),
2243 'o' => readChapter (CHAP_OBJTYPE, 'o')
2244 );
2245 return $types[$style][$objtype_id];
2246}
2247
0df8c52b
DO
2248function isolatedPermission ($p, $t, $cell)
2249{
2250 // This function is called from both "file" page and a number of other pages,
2251 // which have already fixed security context and authorized the user for it.
2252 // OTOH, it is necessary here to authorize against the current file, which
2253 // means saving the current context and building a new one.
2254 global
2255 $expl_tags,
2256 $impl_tags,
2257 $target_given_tags,
2258 $auto_tags;
2259 // push current context
2260 $orig_expl_tags = $expl_tags;
2261 $orig_impl_tags = $impl_tags;
2262 $orig_target_given_tags = $target_given_tags;
2263 $orig_auto_tags = $auto_tags;
2264 // retarget
2265 fixContext ($cell);
2266 // remember decision
2267 $ret = permitted ($p, $t);
2268 // pop context
2269 $expl_tags = $orig_expl_tags;
2270 $impl_tags = $orig_impl_tags;
2271 $target_given_tags = $orig_target_given_tags;
2272 $auto_tags = $orig_auto_tags;
2273 return $ret;
2274}
2275
3153a326
DO
2276function getPortListPrefs()
2277{
2278 $ret = array();
2279 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
2280 $ret['iif_pick'] = 1;
2281 $ret['oif_picks'] = array();
2282 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
2283 {
2284 $tmp = explode ('=', trim ($tmp));
2285 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
2286 $ret['oif_picks'][$tmp[0]] = $tmp[1];
2287 }
2288 // enforce default value
2289 if (!array_key_exists (1, $ret['oif_picks']))
2290 $ret['oif_picks'][1] = 24;
2291 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
2292 return $ret;
2293}
2294
2dfa1b73
DO
2295// Return data for printNiftySelect() with port type options. All OIF options
2296// for the default IIF will be shown, but only the default OIFs will be present
2297// for each other IIFs. IIFs, for which there is no default OIF, will not
2298// be listed.
2299// This SELECT will be used for the "add new port" form.
2300function getNewPortTypeOptions()
2301{
2302 $ret = array();
2303 $prefs = getPortListPrefs();
2304 foreach (getPortInterfaceCompat() as $row)
2305 {
2306 if ($row['iif_id'] == $prefs['iif_pick'])
2307 $optgroup = $row['iif_name'];
2308 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
2309 $optgroup = 'other';
2310 else
2311 continue;
2312 if (!array_key_exists ($optgroup, $ret))
2313 $ret[$optgroup] = array();
2314 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
2315 }
2316 return $ret;
2317}
2318
8198f2c6
DO
2319// Return a serialized version of VLAN configuration for a port.
2320// If a native VLAN is defined, print it first. All other VLANs
d0dadd80
DO
2321// are tagged and are listed after a plus sign. When no configuration
2322// is set for a port, return "default" string.
4f860864 2323function serializeVLANPack ($vlanport)
8198f2c6 2324{
4f860864
DO
2325 if (!array_key_exists ('mode', $vlanport))
2326 return 'error';
2327 switch ($vlanport['mode'])
2328 {
2329 case 'none':
2330 return 'none';
2331 case 'access':
2332 $ret = 'A';
2333 break;
2334 case 'trunk':
2335 $ret = 'T';
2336 break;
36a70b71
DO
2337 case 'uplink':
2338 $ret = 'U';
2339 break;
2340 case 'downlink':
2341 $ret = 'D';
2342 break;
4f860864
DO
2343 default:
2344 return 'error';
2345 }
8198f2c6 2346 $tagged = array();
4f860864
DO
2347 foreach ($vlanport['allowed'] as $vlan_id)
2348 if ($vlan_id != $vlanport['native'])
8198f2c6
DO
2349 $tagged[] = $vlan_id;
2350 sort ($tagged);
4f860864 2351 $ret .= $vlanport['native'] ? $vlanport['native'] : '';
e9be55de
DO
2352 $tagged_bits = array();
2353 $id_from = $id_to = 0;
2354 foreach ($tagged as $next_id)
2355 {
2356 if ($id_to)
2357 {
2358 if ($next_id == $id_to + 1) // merge
2359 {
2360 $id_to = $next_id;
2361 continue;
2362 }
2363 // flush
2364 $tagged_bits[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}";
2365 }
2366 $id_from = $id_to = $next_id; // start next pair
2367 }
2368 // pull last pair
2369 if ($id_to)
2370 $tagged_bits[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}";
8198f2c6 2371 if (count ($tagged))
a9953bf1 2372 $ret .= '+' . implode (', ', $tagged_bits);
d0dadd80 2373 return strlen ($ret) ? $ret : 'default';
8198f2c6
DO
2374}
2375
8846b060
DO
2376// Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
2377// return the numbers as an array of two.
2378function decodeVLANCK ($string)
2379{
2380 $matches = array();
2381 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
2382 throw new InvalidArgException ('VLAN compound key', $string);
2383 return array ($matches[1], $matches[2]);
2384}
2385
ce85f5c8
DO
2386// Return VLAN name formatted for HTML output (note, that input
2387// argument comes from database unescaped).
a72aa89f
DO
2388function formatVLANName ($vlaninfo, $context = 'markup long')
2389{
2390 switch ($context)
2391 {
2392 case 'option':
2393 $ret = $vlaninfo['vlan_id'];
2394 if ($vlaninfo['vlan_descr'] != '')
2395 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
2396 return $ret;
2397 case 'label':
2398 $ret = $vlaninfo['vlan_id'];
2399 if ($vlaninfo['vlan_descr'] != '')
2400 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2401 return $ret;
0812b506
DO
2402 case 'plain long':
2403 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2404 if ($vlaninfo['vlan_descr'] != '')
2405 $ret .= ' (' . niftyString ($vlaninfo['vlan_descr']) . ')';
2406 return $ret;
a72aa89f
DO
2407 case 'markup long':
2408 default:
2409 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2410 $ret .= ' @' . niftyString ($vlaninfo['domain_descr']);
2411 if ($vlaninfo['vlan_descr'] != '')
2412 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2413 return $ret;
2414 }
ce85f5c8
DO
2415}
2416
e9d357e1
DO
2417// map interface name
2418function ios12ShortenIfName ($ifname)
2419{
ffd829af 2420 $ifname = preg_replace ('@^Ethernet(.+)$@', 'e\\1', $ifname);
e9d357e1
DO
2421 $ifname = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $ifname);
2422 $ifname = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $ifname);
2423 $ifname = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $ifname);
2424 $ifname = preg_replace ('@^Port-channel(.+)$@', 'po\\1', $ifname);
50636557 2425 $ifname = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $ifname);
e9d357e1
DO
2426 return $ifname;
2427}
2428
f10dd5cc
DO
2429function iosParseVLANString ($string)
2430{
2431 $ret = array();
2432 foreach (explode (',', $string) as $item)
2433 {
2434 $matches = array();
735e323f 2435 $item = trim ($item, ' ');
f10dd5cc
DO
2436 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches))
2437 $ret[] = $matches[1];
2438 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches))
2439 $ret = array_merge ($ret, range ($matches[1], $matches[2]));
2440 }
2441 return $ret;
2442}
2443
bb35bb93
DO
2444// Scan given array and return the key, which addresses the first item
2445// with requested column set to given value (or NULL if there is none such).
19350222
DO
2446// Note that 0 and NULL mean completely different things and thus
2447// require strict checking (=== and !===).
dbc00990
DO
2448function scanArrayForItem ($table, $scan_column, $scan_value)
2449{
2450 foreach ($table as $key => $row)
2451 if ($row[$scan_column] == $scan_value)
2452 return $key;
2453 return NULL;
2454}
2455
66658512
DO
2456// Return TRUE, if every value of A1 is present in A2 and vice versa,
2457// regardless of each array's sort order and indexing.
2458function array_values_same ($a1, $a2)
2459{
2460 return !count (array_diff ($a1, $a2)) and !count (array_diff ($a2, $a1));
2461}
2462
1ce258f7
DO
2463// Use the VLAN switch template to set VST role for each port of
2464// the provided list. Return resulting list.
bc254f49 2465function apply8021QOrder ($vst_id, $portlist)
25930440 2466{
bc254f49 2467 $vst = getVLANSwitchTemplate ($vst_id);
1ce258f7
DO
2468 foreach (array_keys ($portlist) as $port_name)
2469 {
2470 foreach ($vst['rules'] as $rule)
2471 if (preg_match ($rule['port_pcre'], $port_name))
25930440
DO
2472 {
2473 $portlist[$port_name]['vst_role'] = $rule['port_role'];
091768aa 2474 $portlist[$port_name]['wrt_vlans'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']);
1ce258f7 2475 continue 2;
25930440 2476 }
1ce258f7
DO
2477 $portlist[$port_name]['vst_role'] = 'none';
2478 }
25930440
DO
2479 return $portlist;
2480}
2481
091768aa
DO
2482// return a sequence of ranges for given string form and port role
2483function buildVLANFilter ($role, $string)
be28b696 2484{
091768aa 2485 // set base
1ce258f7 2486 switch ($role)
be28b696 2487 {
1ce258f7
DO
2488 case 'access': // 1-4094
2489 $min = VLAN_MIN_ID;
2490 $max = VLAN_MAX_ID;
2491 break;
2492 case 'trunk': // 2-4094
2493 case 'uplink':
65da0c15 2494 case 'downlink':
ec523868 2495 case 'anymode':
1ce258f7
DO
2496 $min = VLAN_MIN_ID + 1;
2497 $max = VLAN_MAX_ID;
2498 break;
65da0c15 2499 default: // none
1ce258f7 2500 return array();
be28b696 2501 }
091768aa
DO
2502 if ($string == '') // fast track
2503 return array (array ('from' => $min, 'to' => $max));
2504 // transform
2505 $vlanidlist = array();
1ce258f7
DO
2506 foreach (iosParseVLANString ($string) as $vlan_id)
2507 if ($min <= $vlan_id and $vlan_id <= $max)
091768aa 2508 $vlanidlist[] = $vlan_id;
cc6a6c4e
DO
2509 return listToRanges ($vlanidlist);
2510}
2511
2512// pack set of integers into list of integer ranges
2513// e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11))
ec523868
DO
2514// The second argument, when it is different from 0, limits amount of
2515// items in each generated range.
2516function listToRanges ($vlanidlist, $limit = 0)
cc6a6c4e 2517{
091768aa
DO
2518 sort ($vlanidlist);
2519 $ret = array();
2520 $from = $to = NULL;
2521 foreach ($vlanidlist as $vlan_id)
2522 if ($from == NULL)
ec523868
DO
2523 {
2524 if ($limit == 1)
2525 $ret[] = array ('from' => $vlan_id, 'to' => $vlan_id);
2526 else
2527 $from = $to = $vlan_id;
2528 }
091768aa 2529 elseif ($to + 1 == $vlan_id)
ec523868 2530 {
091768aa 2531 $to = $vlan_id;
ec523868
DO
2532 if ($to - $from + 1 == $limit)
2533 {
2534 // cut accumulated range and start over
2535 $ret[] = array ('from' => $from, 'to' => $to);
2536 $from = $to = NULL;
2537 }
2538 }
091768aa
DO
2539 else
2540 {
2541 $ret[] = array ('from' => $from, 'to' => $to);
2542 $from = $to = $vlan_id;
2543 }
2544 if ($from != NULL)
2545 $ret[] = array ('from' => $from, 'to' => $to);
be28b696
DO
2546 return $ret;
2547}
2548
091768aa
DO
2549// return TRUE, if given VLAN ID belongs to one of filter's ranges
2550function matchVLANFilter ($vlan_id, $vfilter)
2551{
2552 foreach ($vfilter as $range)
2553 if ($range['from'] <= $vlan_id and $vlan_id <= $range['to'])
2554 return TRUE;
2555 return FALSE;
2556}
2557
bcd14540
DO
2558function exportSwitch8021QConfig
2559(
2560 $vswitch,
2561 $device_vlanlist,
2562 $before,
2563 $changes
2564)
2565{
2566 // only ignore VLANs, which exist and are explicitly shown as "alien"
2567 $old_managed_vlans = array();
2568 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
2569 foreach ($device_vlanlist as $vlan_id)
2570 if
2571 (
2572 !array_key_exists ($vlan_id, $domain_vlanlist) or
2573 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien'
2574 )
2575 $old_managed_vlans[] = $vlan_id;
2576 $ports_to_do = array();
2577 $after = $before;
2578 foreach ($changes as $port_name => $port)
2579 {
2580 $ports_to_do[$port_name] = array
2581 (
2582 'old_mode' => $before[$port_name]['mode'],
2583 'old_allowed' => $before[$port_name]['allowed'],
2584 'old_native' => $before[$port_name]['native'],
2585 'new_mode' => $port['mode'],
2586 'new_allowed' => $port['allowed'],
2587 'new_native' => $port['native'],
2588 );
2589 $after[$port_name] = $port;
2590 }
2591 // New VLAN table is a union of:
2592 // 1. all compulsory VLANs
2593 // 2. all "current" non-alien allowed VLANs of those ports, which are left
2594 // intact (regardless if a VLAN exists in VLAN domain, but looking,
2595 // if it is present in device's own VLAN table)
2596 // 3. all "new" allowed VLANs of those ports, which we do "push" now
2597 // Like for old_managed_vlans, a VLANs is never listed, only if it
2598 // exists and belongs to "alien" type.
2599 $new_managed_vlans = array();
2600 // 1
2601 foreach ($domain_vlanlist as $vlan_id => $vlan)
2602 if ($vlan['vlan_type'] == 'compulsory')
2603 $new_managed_vlans[] = $vlan_id;
2604 // 2
2605 foreach ($before as $port_name => $port)
2606 if (!array_key_exists ($port_name, $changes))
2607 foreach ($port['allowed'] as $vlan_id)
2608 {
2609 if (in_array ($vlan_id, $new_managed_vlans))
2610 continue;
2611 if
2612 (
2613 array_key_exists ($vlan_id, $domain_vlanlist) and
2614 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien'
2615 )
2616 continue;
2617 if (in_array ($vlan_id, $device_vlanlist))
2618 $new_managed_vlans[] = $vlan_id;
2619 }
2620 // 3
2621 foreach ($changes as $port)
10b6b476 2622 foreach ($port['allowed'] as $vlan_id)
bcd14540
DO
2623 if
2624 (
2625 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' and
2626 !in_array ($vlan_id, $new_managed_vlans)
2627 )
2628 $new_managed_vlans[] = $vlan_id;
2629 $crq = array();
2630 // Before removing each old VLAN as such it is necessary to unassign
2631 // ports from it (to remove VLAN from each ports' list of "allowed"
2632 // VLANs). This change in turn requires, that a port's "native"
2633 // VLAN isn't set to the one being removed from its "allowed" list.
2634 foreach ($ports_to_do as $port_name => $port)
2635 switch ($port['old_mode'] . '->' . $port['new_mode'])
2636 {
2637 case 'trunk->trunk':
2638 // "old" native is set and differs from the "new" native
2639 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
2640 $crq[] = array
2641 (
2642 'opcode' => 'unset native',
2643 'arg1' => $port_name,
2644 'arg2' => $port['old_native'],
2645 );
cc6a6c4e 2646 if (count ($tmp = array_diff ($port['old_allowed'], $port['new_allowed'])))
bcd14540
DO
2647 $crq[] = array
2648 (
2649 'opcode' => 'rem allowed',
cc6a6c4e
DO
2650 'port' => $port_name,
2651 'vlans' => $tmp,
bcd14540
DO
2652 );
2653 break;
2654 case 'access->access':
2655 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
2656 $crq[] = array
2657 (
2658 'opcode' => 'unset access',
2659 'arg1' => $port_name,
2660 'arg2' => $port['old_native'],
2661 );
2662 break;
2663 case 'access->trunk':
2664 $crq[] = array
2665 (
2666 'opcode' => 'unset access',
2667 'arg1' => $port_name,
2668 'arg2' => $port['old_native'],
2669 );
2670 break;
2671 case 'trunk->access':
2672 $crq[] = array
2673 (
2674 'opcode' => 'unset native',
2675 'arg1' => $port_name,
2676 'arg2' => $port['old_native'],
2677 );
cc6a6c4e 2678 if (count ($port['old_allowed']))
bcd14540
DO
2679 $crq[] = array
2680 (
2681 'opcode' => 'rem allowed',
cc6a6c4e
DO
2682 'port' => $port_name,
2683 'vlans' => $port['old_allowed'],
bcd14540
DO
2684 );
2685 break;
2686 default:
164ba494 2687 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
bcd14540
DO
2688 }
2689 // Now it is safe to unconfigure VLANs, which still exist on device,
2690 // but are not present on the "new" list.
2691 // FIXME: put all IDs into one pseudo-command to make it easier
2692 // for translators to create/destroy VLANs in batches, where
2693 // target platform allows them to do.
2694 foreach (array_diff ($old_managed_vlans, $new_managed_vlans) as $vlan_id)
2695 $crq[] = array
2696 (
2697 'opcode' => 'destroy VLAN',
2698 'arg1' => $vlan_id,
2699 );
2700 // Configure VLANs, which must be present on the device, but are not yet.
2701 foreach (array_diff ($new_managed_vlans, $old_managed_vlans) as $vlan_id)
2702 $crq[] = array
2703 (
2704 'opcode' => 'create VLAN',
2705 'arg1' => $vlan_id,
2706 );
2707 // Now, when all new VLANs are created (queued), it is safe to assign (queue)
2708 // ports to the new VLANs.
2709 foreach ($ports_to_do as $port_name => $port)
2710 switch ($port['old_mode'] . '->' . $port['new_mode'])
2711 {
2712 case 'trunk->trunk':
2713 // For each allowed VLAN, which is present on the "new" list and missing from
2714 // the "old" one, queue a command to assign current port to that VLAN.
cc6a6c4e 2715 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed'])))
bcd14540
DO
2716 $crq[] = array
2717 (
2718 'opcode' => 'add allowed',
cc6a6c4e
DO
2719 'port' => $port_name,
2720 'vlans' => $tmp,
bcd14540
DO
2721 );
2722 // One of the "allowed" VLANs for this port may probably be "native".
2723 // "new native" is set and differs from "old native"
2724 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
2725 $crq[] = array
2726 (
2727 'opcode' => 'set native',
2728 'arg1' => $port_name,
2729 'arg2' => $port['new_native'],
2730 );
2731 break;
2732 case 'access->access':
2733 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
2734 $crq[] = array
2735 (
2736 'opcode' => 'set access',
2737 'arg1' => $port_name,
2738 'arg2' => $port['new_native'],
2739 );
2740 break;
2741 case 'access->trunk':
2742 $crq[] = array
2743 (
2744 'opcode' => 'set mode',
2745 'arg1' => $port_name,
2746 'arg2' => $port['new_mode'],
2747 );
cc6a6c4e 2748 if (count ($port['new_allowed']))
bcd14540
DO
2749 $crq[] = array
2750 (
2751 'opcode' => 'add allowed',
cc6a6c4e
DO
2752 'port' => $port_name,
2753 'vlans' => $port['new_allowed'],
bcd14540
DO
2754 );
2755 $crq[] = array
2756 (
2757 'opcode' => 'set native',
2758 'arg1' => $port_name,
2759 'arg2' => $port['new_native'],
2760 );
2761 break;
2762 case 'trunk->access':
2763 $crq[] = array
2764 (
2765 'opcode' => 'set mode',
2766 'arg1' => $port_name,
2767 'arg2' => $port['new_mode'],
2768 );
2769 $crq[] = array
2770 (
2771 'opcode' => 'set access',
2772 'arg1' => $port_name,
2773 'arg2' => $port['new_native'],
2774 );
2775 break;
2776 default:
164ba494 2777 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
bcd14540 2778 }
5017142e
DO
2779 if (count ($crq))
2780 {
2781 array_unshift ($crq, array ('opcode' => 'begin configuration'));
2782 $crq[] = array ('opcode' => 'end configuration');
2783 if (considerConfiguredConstraint (spotEntity ('object', $vswitch['object_id']), '8021Q_WRI_AFTER_CONFT_LISTSRC'))
2784 $crq[] = array ('opcode' => 'save configuration');
f82a94da 2785 setDevice8021QConfig ($vswitch['object_id'], $crq);
5017142e 2786 }
bcd14540
DO
2787 return count ($crq);
2788}
2789
af204724 2790// filter list of changed ports to cancel changes forbidden by VST and domain
af204724
DO
2791function filter8021QChangeRequests
2792(
2793 $domain_vlanlist,
2794 $before, // current saved configuration of all ports
2795 $changes // changed ports with VST markup
2796)
2797{
2798 $domain_immune_vlans = array();
2799 foreach ($domain_vlanlist as $vlan_id => $vlan)
2800 if ($vlan['vlan_type'] == 'alien')
2801 $domain_immune_vlans[] = $vlan_id;
2802 $ret = array();
2803 foreach ($changes as $port_name => $port)
2804 {
ec523868
DO
2805 // VST violation ?
2806 if (!goodModeForVSTRole ($port['mode'], $port['vst_role']))
2807 continue; // ignore change request
af204724 2808 // find and cancel any changes regarding immune VLANs
ec523868 2809 switch ($port['mode'])
034a61c9
DO
2810 {
2811 case 'access':
034a61c9 2812 foreach ($domain_immune_vlans as $immune)
af204724
DO
2813 // Reverting an attempt to set an access port from
2814 // "normal" VLAN to immune one (or vice versa) requires
2815 // special handling, becase the calling function has
2816 // discarded the old contents of 'allowed' for current port.
af204724
DO
2817 if
2818 (
2819 $before[$port_name]['native'] == $immune or
2820 $port['native'] == $immune
2821 )
2822 {
2823 $port['native'] = $before[$port_name]['native'];
2824 $port['allowed'] = array ($port['native']);
034a61c9
DO
2825 // Such reversal happens either once or never for an
2826 // access port.
2827 break;
af204724 2828 }
034a61c9
DO
2829 break;
2830 case 'trunk':
034a61c9 2831 foreach ($domain_immune_vlans as $immune)
af204724
DO
2832 if (in_array ($immune, $before[$port_name]['allowed'])) // was allowed before
2833 {
2834 if (!in_array ($immune, $port['allowed']))
2835 $port['allowed'][] = $immune; // restore
2836 if ($before[$port_name]['native'] == $immune) // and was native
2837 $port['native'] = $immune; // also restore
2838 }
2839 else // wasn't
2840 {
2841 if (in_array ($immune, $port['allowed']))
2842 unset ($port['allowed'][array_search ($immune, $port['allowed'])]); // cancel
2843 if ($port['native'] == $immune)
2844 $port['native'] = $before[$port_name]['native'];
2845 }
034a61c9
DO
2846 break;
2847 default:
ec523868 2848 throw new InvalidArgException ('mode', $port['mode']);
034a61c9
DO
2849 }
2850 // save work
af204724
DO
2851 $ret[$port_name] = $port;
2852 }
2853 return $ret;
2854}
2855
4741e9c3 2856// take port list with order applied and return uplink ports in the same format
ca7f0af4 2857function produceUplinkPorts ($domain_vlanlist, $portlist)
4741e9c3
DO
2858{
2859 $ret = array();
ca7f0af4
DO
2860 $employed = array();
2861 foreach ($domain_vlanlist as $vlan_id => $vlan)
2862 if ($vlan['vlan_type'] == 'compulsory')
2863 $employed[] = $vlan_id;
2864 foreach ($portlist as $port_name => $port)
2865 if ($port['vst_role'] != 'uplink')
2866 foreach ($port['allowed'] as $vlan_id)
2867 if (!in_array ($vlan_id, $employed))
2868 $employed[] = $vlan_id;
2869 foreach ($portlist as $port_name => $port)
2870 if ($port['vst_role'] == 'uplink')
2871 {
1ce258f7
DO
2872 $employed_here = array();
2873 foreach ($employed as $vlan_id)
091768aa 2874 if (matchVLANFilter ($vlan_id, $port['wrt_vlans']))
1ce258f7 2875 $employed_here[] = $vlan_id;
ca7f0af4
DO
2876 $ret[$port_name] = array
2877 (
034a61c9 2878 'vst_role' => 'uplink',
ca7f0af4
DO
2879 'mode' => 'trunk',
2880 'allowed' => $employed_here,
2881 'native' => 0,
2882 );
2883 }
4741e9c3
DO
2884 return $ret;
2885}
2886
9c45ea37
DO
2887function same8021QConfigs ($a, $b)
2888{
2889 return $a['mode'] == $b['mode'] &&
2890 array_values_same ($a['allowed'], $b['allowed']) &&
2891 $a['native'] == $b['native'];
2892}
2893
ec523868
DO
2894// Return TRUE, if the port can be edited by the user.
2895function editable8021QPort ($port)
2896{
2897 return in_array ($port['vst_role'], array ('trunk', 'access', 'anymode'));
2898}
2899
2900// Decide, whether the given 802.1Q port mode is permitted by
2901// VST port role.
2902function goodModeForVSTRole ($mode, $role)
2903{
2904 switch ($mode)
2905 {
2906 case 'access':
2907 return in_array ($role, array ('access', 'anymode'));
2908 case 'trunk':
2909 return in_array ($role, array ('trunk', 'uplink', 'downlink', 'anymode'));
2910 default:
2911 throw new InvalidArgException ('mode', $mode);
2912 }
2913}
2914
26dec09b
DO
2915/*
2916
2917Relation between desired (D), cached (C) and running (R)
2918copies of switch ports (P) list.
2919
2920 D C R
2921+---+ +---+ +---+
2922| P |-----| P |-? +--| P |
2923+---+ +---+ / +---+
2924| P |-----| P |--+ ?-| P |
2925+---+ +---+ +---+
2926| P |-----| P |-------| P |
2927+---+ +---+ +---+
2928| P |-----| P |--+ ?-| P |
2929+---+ +---+ \ +---+
2930| P |-----| P |--+ +--| P |
2931+---+ +---+ \ +---+
2932 +--| P |
2933 +---+
2934 ?-| P |
2935 +---+
2936
2937A modified local version of a port in "conflict" state ignores remote
2938changes until remote change maintains its difference. Once both edits
2939match, the local copy "locks" on the remote and starts tracking it.
2940
2941v
2942a "o" -- remOte version
2943l "l" -- Local version
2944u "b" -- Both versions
2945e
2946
2947^
2948| o b
2949| o
2950| l l l l l l b b
2951| o o b
2952| o b
2953|
2954| o
2955|
2956|
29570----------------------------------------------> time
2958
2959*/
be28b696
DO
2960function get8021QSyncOptions
2961(
2962 $vswitch,
2963 $D, // desired config
2964 $C, // cached config
2965 $R // running-config
2966)
2967{
2968 $default_port = array
2969 (
2970 'mode' => 'access',
2971 'allowed' => array (VLAN_DFL_ID),
2972 'native' => VLAN_DFL_ID,
2973 );
ab25b0d0 2974 $ret = array();
24832534 2975 $allports = array();
ab25b0d0 2976 foreach (array_unique (array_merge (array_keys ($C), array_keys ($R))) as $pn)
d3082829
DO
2977 $allports[$pn] = array();
2978 foreach (apply8021QOrder ($vswitch['template_id'], $allports) as $pn => $port)
be28b696 2979 {
7a375475
DO
2980 // catch anomalies early
2981 if ($port['vst_role'] == 'none')
2982 {
2983 if ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and !array_key_exists ($pn, $C))
2984 $ret[$pn] = array ('status' => 'none');
2985 else
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 }
2994 elseif ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and array_key_exists ($pn, $C))
2995 {
2996 $ret[$pn] = array
2997 (
2998 'status' => 'martian_conflict',
2999 'left' => array_key_exists ($pn, $C) ? $C[$pn] : array ('mode' => 'none'),
3000 'right' => array_key_exists ($pn, $R) ? $R[$pn] : array ('mode' => 'none'),
3001 );
3002 continue;
3003 }
26dec09b
DO
3004 // (DC_): port missing from device
3005 if (!array_key_exists ($pn, $R))
be28b696 3006 {
ef016293
DO
3007 $ret[$pn] = array ('left' => $D[$pn]);
3008 if (same8021QConfigs ($D[$pn], $default_port))
3009 $ret[$pn]['status'] = 'ok_to_delete';
3010 else
3011 {
3012 $ret[$pn]['status'] = 'delete_conflict';
3013 $ret[$pn]['lastseen'] = $C[$pn];
3014 }
be28b696
DO
3015 continue;
3016 }
26dec09b
DO
3017 // (__R): port missing from DB
3018 if (!array_key_exists ($pn, $C))
be28b696 3019 {
7a375475
DO
3020 // Allow importing any configuration, which passes basic
3021 // validation. If port mode doesn't match its VST role,
3022 // this will be handled later WRT each port.
ab25b0d0
DO
3023 $ret[$pn] = array
3024 (
7a375475 3025 'status' => acceptable8021QConfig ($R[$pn]) ? 'ok_to_add' : 'add_conflict',
ab25b0d0
DO
3026 'right' => $R[$pn],
3027 );
be28b696
DO
3028 continue;
3029 }
3030 $D_eq_C = same8021QConfigs ($D[$pn], $C[$pn]);
3031 $C_eq_R = same8021QConfigs ($C[$pn], $R[$pn]);
26dec09b 3032 // (DCR), D = C = R: data in sync
be28b696 3033 if ($D_eq_C and $C_eq_R) // implies D == R
ab25b0d0
DO
3034 {
3035 $ret[$pn] = array
3036 (
3037 'status' => 'in_sync',
3038 'both' => $R[$pn],
3039 );
be28b696 3040 continue;
ab25b0d0 3041 }
26dec09b 3042 // (DCR), D = C: no local edit in the way
be28b696 3043 if ($D_eq_C)
ab25b0d0
DO
3044 $ret[$pn] = array
3045 (
3046 'status' => 'ok_to_pull',
3047 'left' => $D[$pn],
3048 'right' => $R[$pn],
3049 );
26dec09b 3050 // (DCR), C = R: no remote edit in the way
be28b696 3051 elseif ($C_eq_R)
ab25b0d0
DO
3052 $ret[$pn] = array
3053 (
3054 'status' => 'ok_to_push',
3055 'left' => $D[$pn],
3056 'right' => $R[$pn],
3057 );
26dec09b 3058 // (DCR), D = R: end of version conflict, restore tracking
be28b696 3059 elseif (same8021QConfigs ($D[$pn], $R[$pn]))
ab25b0d0
DO
3060 $ret[$pn] = array
3061 (
d973196a 3062 'status' => 'ok_to_merge',
ab25b0d0
DO
3063 'both' => $R[$pn],
3064 );
26dec09b 3065 else // D != C, C != R, D != R: version conflict
ab25b0d0
DO
3066 $ret[$pn] = array
3067 (
ec523868 3068 'status' => editable8021QPort ($port) ?
d3082829
DO
3069 // In case the port is normally updated by user, let him
3070 // resolve the conflict. If the system manages this port,
3071 // arrange the data to let remote version go down.
3072 'merge_conflict' : 'ok_to_push_with_merge',
ab25b0d0
DO
3073 'left' => $D[$pn],
3074 'right' => $R[$pn],
3075 );
be28b696 3076 }
ab25b0d0 3077 return $ret;
be28b696
DO
3078}
3079
ca5d4cbc 3080// return number of records updated successfully of FALSE, if a conflict was in the way
d973196a 3081function exec8021QDeploy ($object_id, $do_push)
ca5d4cbc
DO
3082{
3083 global $dbxlink;
b3a27170 3084 $nsaved = $npushed = $nsaved_uplinks = 0;
ca5d4cbc
DO
3085 $dbxlink->beginTransaction();
3086 if (NULL === $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE'))
3087 throw new InvalidArgException ('object_id', $object_id, 'VLAN domain is not set for this object');
3088 $D = getStored8021QConfig ($vswitch['object_id'], 'desired');
3089 $C = getStored8021QConfig ($vswitch['object_id'], 'cached');
d973196a
DO
3090 try
3091 {
3092 $R = getRunning8021QConfig ($vswitch['object_id']);
3093 }
3a089a44 3094 catch (RTGatewayError $e)
d973196a 3095 {
c1aa3ada
DO
3096 usePreparedExecuteBlade
3097 (
3098 'UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?',
3099 array (E_8021Q_PULL_REMOTE_ERROR, $vswitch['object_id'])
3100 );
d973196a
DO
3101 $dbxlink->commit();
3102 return 0;
3103 }
ca5d4cbc 3104 $conflict = FALSE;
ca5d4cbc 3105 $ok_to_push = array();
d973196a 3106 foreach (get8021QSyncOptions ($vswitch, $D, $C, $R['portdata']) as $pn => $port)
ca5d4cbc
DO
3107 {
3108 // always update cache with new data from switch
3109 switch ($port['status'])
3110 {
d973196a 3111 case 'ok_to_merge':
f82a94da 3112 // FIXME: this can be logged
d973196a 3113 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['both']);
ca5d4cbc
DO
3114 break;
3115 case 'ok_to_delete':
d973196a 3116 $nsaved += del8021QPort ($vswitch['object_id'], $pn);
ca5d4cbc
DO
3117 break;
3118 case 'ok_to_add':
d973196a 3119 $nsaved += add8021QPort ($vswitch['object_id'], $pn, $port['right']);
ca5d4cbc
DO
3120 break;
3121 case 'delete_conflict':
3122 case 'merge_conflict':
7a375475
DO
3123 case 'add_conflict':
3124 case 'martian_conflict':
ca5d4cbc
DO
3125 $conflict = TRUE;
3126 break;
3127 case 'ok_to_pull':
f82a94da 3128 // FIXME: this can be logged
d973196a
DO
3129 upd8021QPort ('desired', $vswitch['object_id'], $pn, $port['right']);
3130 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3131 $nsaved++;
ca5d4cbc 3132 break;
d3082829
DO
3133 case 'ok_to_push_with_merge':
3134 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3135 // fall through
ca5d4cbc
DO
3136 case 'ok_to_push':
3137 $ok_to_push[$pn] = $port['left'];
3138 break;
3139 }
3140 }
32c7fad1
DO
3141 // redo uplinks unconditionally
3142 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3143 $Dnew = apply8021QOrder ($vswitch['template_id'], getStored8021QConfig ($vswitch['object_id'], 'desired'));
034a61c9
DO
3144 // Take new "desired" configuration and derive uplink port configuration
3145 // from it. Then cancel changes to immune VLANs and save resulting
3146 // changes (if any left).
3147 $new_uplinks = filter8021QChangeRequests ($domain_vlanlist, $Dnew, produceUplinkPorts ($domain_vlanlist, $Dnew));
b3a27170
DO
3148 $nsaved_uplinks += replace8021QPorts ('desired', $vswitch['object_id'], $Dnew, $new_uplinks);
3149 if ($nsaved + $nsaved_uplinks)
94080e1c 3150 {
32c7fad1
DO
3151 // saved configuration has changed (either "user" ports have changed,
3152 // or uplinks, or both), so bump revision number up)
c1aa3ada
DO
3153 usePreparedExecuteBlade
3154 (
3155 'UPDATE VLANSwitch SET mutex_rev=mutex_rev+1, last_change=NOW(), out_of_sync="yes" WHERE object_id=?',
3156 array ($vswitch['object_id'])
3157 );
ca5d4cbc 3158 }
d973196a 3159 if ($conflict)
c1aa3ada
DO
3160 usePreparedExecuteBlade
3161 (
3162 'UPDATE VLANSwitch SET out_of_sync="yes", last_errno=?, last_error_ts=NOW() WHERE object_id=?',
3163 array (E_8021Q_VERSION_CONFLICT, $vswitch['object_id'])
3164 );
52ce8bd7 3165 else
d973196a 3166 {
c1aa3ada
DO
3167 usePreparedExecuteBlade
3168 (
3169 'UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?',
3170 array (E_8021Q_NOERROR, $vswitch['object_id'])
3171 );
4799a8df
DO
3172 // Modified uplinks are very likely to differ from those in R-copy,
3173 // so don't mark device as clean, if this happened. This can cost
3174 // us an additional, empty round of sync, but at least out_of_sync
3175 // won't be mistakenly set to 'no'.
3176 // FIXME: A cleaner way of coupling pull and push operations would
3177 // be to split this function into two.
3178 if (!count ($ok_to_push) and !$nsaved_uplinks)
c1aa3ada
DO
3179 usePreparedExecuteBlade
3180 (
3181 'UPDATE VLANSwitch SET out_of_sync="no" WHERE object_id=?',
3182 array ($vswitch['object_id'])
3183 );
52ce8bd7 3184 elseif ($do_push)
d973196a 3185 {
c1aa3ada
DO
3186 usePreparedExecuteBlade
3187 (
3188 'UPDATE VLANSwitch SET last_push_started=NOW() WHERE object_id=?',
3189 array ($vswitch['object_id'])
3190 );
52ce8bd7
DO
3191 try
3192 {
3193 $npushed += exportSwitch8021QConfig ($vswitch, $R['vlanlist'], $R['portdata'], $ok_to_push);
3194 // update cache for ports deployed
3195 replace8021QPorts ('cached', $vswitch['object_id'], $R['portdata'], $ok_to_push);
c1aa3ada
DO
3196 usePreparedExecuteBlade
3197 (
3198 'UPDATE VLANSwitch SET last_push_finished=NOW(), out_of_sync="no", last_errno=? WHERE object_id=?',
3199 array (E_8021Q_NOERROR, $vswitch['object_id'])
3200 );
52ce8bd7 3201 }
3a089a44 3202 catch (RTGatewayError $r)
52ce8bd7 3203 {
c1aa3ada
DO
3204 usePreparedExecuteBlade
3205 (
3206 'UPDATE VLANSwitch SET out_of_sync="yes", last_error_ts=NOW(), last_errno=? WHERE object_id=?',
3207 array (E_8021Q_PUSH_REMOTE_ERROR, $vswitch['object_id'])
3208 );
52ce8bd7 3209 }
ca5d4cbc
DO
3210 }
3211 }
3212 $dbxlink->commit();
b3a27170
DO
3213 // start downlink work only after unlocking current object to make deadlocks less likely to happen
3214 // TODO: only process changed uplink ports
3215 if ($nsaved_uplinks)
3216 initiateUplinksReverb ($vswitch['object_id'], $new_uplinks);
3217 return $nsaved + $npushed + $nsaved_uplinks;
ca5d4cbc
DO
3218}
3219
be28b696
DO
3220// print part of HTML HEAD block
3221function printPageHeaders ()
3222{
3223 global $pageheaders;
3224 ksort ($pageheaders);
3225 foreach ($pageheaders as $s)
3226 echo $s . "\n";
3227 echo "<style type='text/css'>\n";
3228 foreach (array ('F', 'A', 'U', 'T', 'Th', 'Tw', 'Thw') as $statecode)
3229 {
3230 echo "td.state_${statecode} {\n";
3231 echo "\ttext-align: center;\n";
3232 echo "\tbackground-color: #" . (getConfigVar ('color_' . $statecode)) . ";\n";
3233 echo "\tfont: bold 10px Verdana, sans-serif;\n";
3234 echo "}\n\n";
3235 }
3236 echo '</style>';
3237}
3238
538d9cf8
DO
3239function strerror8021Q ($errno)
3240{
3241 switch ($errno)
3242 {
57acf3f5
DO
3243 case E_8021Q_VERSION_CONFLICT:
3244 return 'pull failed due to version conflict';
3245 case E_8021Q_PULL_REMOTE_ERROR:
3246 return 'pull failed due to remote error';
3247 case E_8021Q_PUSH_REMOTE_ERROR:
3248 return 'push failed due to remote error';
5701baec
DO
3249 case E_8021Q_SYNC_DISABLED:
3250 return 'sync disabled by operator';
57acf3f5
DO
3251 default:
3252 return "unknown error code ${errno}";
538d9cf8
DO
3253 }
3254}
3255
0c1674ae
DO
3256function saveDownlinksReverb ($object_id, $requested_changes)
3257{
3258 $nsaved = 0;
3259 global $dbxlink;
3260 $dbxlink->beginTransaction();
b3a27170
DO
3261 if (NULL === $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE')) // not configured, bail out
3262 {
3263 $dbxlink->rollBack();
3264 return;
3265 }
0c1674ae
DO
3266 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3267 // aplly VST to the smallest set necessary
3268 $requested_changes = apply8021QOrder ($vswitch['template_id'], $requested_changes);
3269 $before = getStored8021QConfig ($object_id, 'desired');
3270 $changes_to_save = array();
3271 // first filter by wrt_vlans constraint
3272 foreach ($requested_changes as $pn => $requested)
3273 if (array_key_exists ($pn, $before) and $requested['vst_role'] == 'downlink')
3274 {
3275 $negotiated = array
3276 (
3277 'vst_role' => 'downlink',
3278 'mode' => 'trunk',
3279 'allowed' => array(),
3280 'native' => 0,
3281 );
3282 // wrt_vlans filter
3283 foreach ($requested['allowed'] as $vlan_id)
091768aa 3284 if (matchVLANFilter ($vlan_id, $requested['wrt_vlans']))
0c1674ae
DO
3285 $negotiated['allowed'][] = $vlan_id;
3286 $changes_to_save[$pn] = $negotiated;
3287 }
3288 // immune VLANs filter
3289 foreach (filter8021QChangeRequests ($domain_vlanlist, $before, $changes_to_save) as $pn => $finalconfig)
3290 if (!same8021QConfigs ($finalconfig, $before[$pn]))
3291 $nsaved += upd8021QPort ('desired', $vswitch['object_id'], $pn, $finalconfig);
3292 if ($nsaved)
c1aa3ada
DO
3293 usePreparedExecuteBlade
3294 (
3295 'UPDATE VLANSwitch SET mutex_rev=mutex_rev+1, last_change=NOW(), out_of_sync="yes" WHERE object_id=?',
3296 array ($vswitch['object_id'])
3297 );
0c1674ae
DO
3298 $dbxlink->commit();
3299}
3300
b3a27170
DO
3301// Use records from Port and Link tables to run a series of tasks on remote
3302// objects. These device-specific tasks will adjust downlink ports according to
3303// the current configuration of given uplink ports.
3304function initiateUplinksReverb ($object_id, $uplink_ports)
3305{
3306 $object = spotEntity ('object', $object_id);
3307 amplifyCell ($object);
3308 // Filter and regroup all requests (regardless of how many will succeed)
3309 // to end up with no more, than one execution per remote object.
3310 $upstream_config = array();
3311 foreach ($object['ports'] as $portinfo)
3312 if
3313 (
3314 array_key_exists ($portinfo['name'], $uplink_ports) and
3315 $portinfo['remote_object_id'] != '' and
3316 $portinfo['remote_name'] != ''
3317 )
3318 $upstream_config[$portinfo['remote_object_id']][$portinfo['remote_name']] = $uplink_ports[$portinfo['name']];
3319 // Note that when current object has several Port records inder same name
3320 // (but with unique IIF-OIF pair), these ports can be Link'ed to different
3321 // remote objects (using different media types, perhaps). Such a case can
3322 // be considered as normal, and each remote object will show up on the
3323 // task list (with its actual remote port name, of course).
3324 foreach ($upstream_config as $remote_object_id => $remote_ports)
3325 saveDownlinksReverb ($remote_object_id, $remote_ports);
3326}
3327
76452d15
DO
3328function detectVLANSwitchQueue ($vswitch)
3329{
3330 if ($vswitch['out_of_sync'] == 'no')
3331 return 'done';
3332 switch ($vswitch['last_errno'])
3333 {
3334 case E_8021Q_NOERROR:
37cb9e18 3335 if ($vswitch['last_change_age_seconds'] > getConfigVar ('8021Q_DEPLOY_MAXAGE'))
611b5e46 3336 return 'sync_ready';
37cb9e18 3337 elseif ($vswitch['last_change_age_seconds'] < getConfigVar ('8021Q_DEPLOY_MINAGE'))
611b5e46 3338 return 'sync_aging';
37cb9e18 3339 else
611b5e46 3340 return 'sync_ready';
76452d15 3341 case E_8021Q_VERSION_CONFLICT:
37cb9e18 3342 if ($vswitch['last_error_age_seconds'] < getConfigVar ('8021Q_DEPLOY_RETRY'))
611b5e46 3343 return 'resync_aging';
37cb9e18 3344 else
611b5e46 3345 return 'resync_ready';
76452d15
DO
3346 case E_8021Q_PULL_REMOTE_ERROR:
3347 case E_8021Q_PUSH_REMOTE_ERROR:
3348 return 'failed';
3349 case E_8021Q_SYNC_DISABLED:
3350 return 'disabled';
3351 }
3352 return '';
3353}
3354
37cb9e18
DO
3355function get8021QDeployQueues()
3356{
611b5e46
DO
3357 global $dqtitle;
3358 $ret = array();
3359 foreach (array_keys ($dqtitle) as $qcode)
3360 $ret[$qcode] = array();
37cb9e18
DO
3361 foreach (getVLANSwitches() as $object_id)
3362 {
3363 $vswitch = getVLANSwitchInfo ($object_id);
3364 if ('' != $qcode = detectVLANSwitchQueue ($vswitch))
3365 $ret[$qcode][] = $vswitch;
3366 }
3367 return $ret;
3368}
3369
f29d93e1
DO
3370function acceptable8021QConfig ($port)
3371{
3372 switch ($port['mode'])
3373 {
3374 case 'trunk':
3375 return TRUE;
3376 case 'access':
3377 if
3378 (
3379 count ($port['allowed']) == 1 and
3380 in_array ($port['native'], $port['allowed'])
3381 )
3382 return TRUE;
3383 // fall through
3384 default:
3385 return FALSE;
3386 }
3387}
3388
bb2024b9
DO
3389function authorize8021QChangeRequests ($before, $changes)
3390{
3391 $ret = array();
3392 foreach ($changes as $pn => $change)
3393 {
bb2024b9 3394 foreach (array_diff ($before[$pn]['allowed'], $change['allowed']) as $removed_id)
647635ad
DO
3395 if (!permitted (NULL, NULL, NULL, array (array ('tag' => '$fromvlan_' . $removed_id))))
3396 continue 2; // next port
3397 foreach (array_diff ($change['allowed'], $before[$pn]['allowed']) as $added_id)
3398 if (!permitted (NULL, NULL, NULL, array (array ('tag' => '$tovlan_' . $added_id))))
3399 continue 2; // next port
3400 $ret[$pn] = $change;
bb2024b9
DO
3401 }
3402 return $ret;
3403}
3404
e9d357e1
DO
3405function formatPortIIFOIF ($port)
3406{
3407 $ret = '';
3408 if ($port['iif_id'] != 1)
3409 $ret .= $port['iif_name'] . '/';
3410 $ret .= $port['oif_name'];
3411 return $ret;
3412}
3413
7d0438a5
DO
3414function compareDecomposedPortNames ($porta, $portb)
3415{
3416 if (0 != $cmp = strcmp ($porta['prefix'], $portb['prefix']))
3417 return $cmp;
3418 if ($porta['numidx'] != $portb['numidx'])
3419 return $porta['numidx'] - $portb['numidx'];
3420 // Below assumes both arrays be indexed from 0 onwards.
3421 for ($i = 0; $i < $porta['numidx']; $i++)
3422 if ($porta['index'][$i] != $portb['index'][$i])
3423 return $porta['index'][$i] - $portb['index'][$i];
3424 return 0;
3425}
3426
3427// Sort provided port list in a way based on natural. For example,
3428// switches can have ports:
3429// * fa0/1~48, gi0/1~4 (in this case 'gi' should come after 'fa'
3430// * fa1, gi0/1~48, te1/49~50 (type matters, then index)
3431// * gi5/1~3, te5/4~5 (here index matters more, than type)
3432// This implementation makes port type (prefix) matter for all
3433// interfaces, which have less, than 2 indices, but for other ports
3434// their indices matter more, than type (unless there is a clash
3435// of indices).
3436function sortPortList ($plist)
3437{
3438 $ret = array();
3439 $seen = array();
3440 $intersects = FALSE;
3441 $prefix_re = '/^([^0-9]+)[0-9].*$/';
3442 foreach (array_keys ($plist) as $pn)
3443 {
3444 $numbers = preg_split ('/[^0-9]+/', $pn, -1, PREG_SPLIT_NO_EMPTY);
3445 $ret[$pn] = array
3446 (
3447 'prefix' => '',
3448 'numidx' => count ($numbers),
3449 'index' => $numbers,
3450 );
3451 if ($ret[$pn]['numidx'] <= 1)
3452 $ret[$pn]['prefix'] = preg_replace ($prefix_re, '\\1', $pn);
3453 elseif (!$intersects)
3454 {
3455 $coord = implode ('-', $numbers);
3456 if (array_key_exists ($coord, $seen))
3457 $intersects = TRUE;
3458 $seen[$coord] = TRUE;
3459 }
3460 }
3461 unset ($seen);
3462 if ($intersects)
3463 foreach (array_keys ($ret) as $pn)
3464 if ($ret[$pn]['numidx'] > 1)
3465 $ret[$pn]['prefix'] = preg_replace ($prefix_re, '\\1', $pn);
3466 uasort ($ret, 'compareDecomposedPortNames');
3467 foreach (array_keys ($ret) as $pn)
3468 $ret[$pn] = $plist[$pn];
3469 return $ret;
3470}
3471
79075d5d
DO
3472// This is a dual-purpose formating function:
3473// 1. Replace empty strings with nbsp.
3474// 2. Cut strings, which are too long, append "cut here" indicator and provide a mouse hint.
3475function niftyString ($string, $maxlen = 30)
3476{
3477 $cutind = '&hellip;'; // length is 1
3478 if (!mb_strlen ($string))
3479 return '&nbsp;';
3480 // a tab counts for a space
3481 $string = preg_replace ("/\t/", ' ', $string);
3482 if (!$maxlen or mb_strlen ($string) <= $maxlen)
3483 return htmlspecialchars ($string, ENT_QUOTES, 'UTF-8');
3484 return "<span title='" . htmlspecialchars ($string, ENT_QUOTES, 'UTF-8') . "'>" .
3485 str_replace (' ', '&nbsp;', htmlspecialchars (mb_substr ($string, 0, $maxlen - 1), ENT_QUOTES, 'UTF-8')) . $cutind . '</span>';
3486}
3487
ec523868
DO
3488// return a "?, ?, ?, ... ?, ?" string consisting of N question marks
3489function questionMarks ($count = 0)
3490{
3491 return implode (', ', array_fill (0, $count, '?'));
3492}
3493
e673ee24 3494?>