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