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