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