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