checkPortRole(): don't put "else" after "return"
[racktables] / wwwroot / inc / functions.php
1 <?php
2
3 # This file is a part of RackTables, a datacenter and server room management
4 # framework. See accompanying file "COPYING" for the full copyright and
5 # licensing information.
6
7 /*
8 *
9 * This file is a library of computational functions for RackTables.
10 *
11 */
12
13 defineIfNotDefined ('TAGNAME_REGEXP', '/^[\p{L}0-9-]( ?([._~+%-] ?)?[\p{L}0-9:])*(%|\+)?$/u');
14 defineIfNotDefined ('AUTOTAGNAME_REGEXP', '/^\$[\p{L}0-9-]( ?([._~+%-] ?)?[\p{L}0-9:])*(%|\+)?$/u');
15
16 $loclist[0] = 'front';
17 $loclist[1] = 'interior';
18 $loclist[2] = 'rear';
19 $loclist['front'] = 0;
20 $loclist['interior'] = 1;
21 $loclist['rear'] = 2;
22 $template[0] = array (TRUE, TRUE, TRUE);
23 $template[1] = array (TRUE, TRUE, FALSE);
24 $template[2] = array (FALSE, TRUE, TRUE);
25 $template[3] = array (TRUE, FALSE, FALSE);
26 $template[4] = array (FALSE, TRUE, FALSE);
27 $template[5] = array (FALSE, FALSE, TRUE);
28 $templateWidth[0] = 3;
29 $templateWidth[1] = 2;
30 $templateWidth[2] = 2;
31 $templateWidth[3] = 1;
32 $templateWidth[4] = 1;
33 $templateWidth[5] = 1;
34
35 $message_buffering = FALSE;
36
37 define ('CHAP_OBJTYPE', 1);
38 // The latter matches both SunOS and Linux-styled formats.
39 define ('RE_L2_IFCFG', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){5}$/i');
40 define ('RE_L2_CISCO', '/^[0-9a-f]{4}(\.[0-9a-f]{4}){2}$/i');
41 define ('RE_L2_HUAWEI', '/^[0-9a-f]{4}(-[0-9a-f]{4}){2}$/i');
42 define ('RE_L2_SOLID', '/^[0-9a-f]{12}$/i');
43 define ('RE_L2_IPCFG', '/^[0-9a-f]{2}(-[0-9a-f]{2}){5}$/i');
44 define ('RE_L2_WWN_COLON', '/^[0-9a-f]{1,2}(:[0-9a-f]{1,2}){7}$/i');
45 define ('RE_L2_WWN_HYPHEN', '/^[0-9a-f]{2}(-[0-9a-f]{2}){7}$/i');
46 define ('RE_L2_WWN_SOLID', '/^[0-9a-f]{16}$/i');
47 define ('RE_IP4_ADDR', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}$#');
48 define ('RE_IP4_NET', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$#');
49 define ('E_8021Q_NOERROR', 0);
50 define ('E_8021Q_VERSION_CONFLICT', 101);
51 define ('E_8021Q_PULL_REMOTE_ERROR', 102);
52 define ('E_8021Q_PUSH_REMOTE_ERROR', 103);
53 define ('E_8021Q_SYNC_DISABLED', 104);
54 define ('VLAN_MIN_ID', 1);
55 define ('VLAN_MAX_ID', 4094);
56 define ('VLAN_DFL_ID', 1);
57 define ('TAB_REMEMBER_TIMEOUT', 300);
58
59 // Entity type by page number mapping is 1:1 atm, but may change later.
60 $etype_by_pageno = array
61 (
62 'ipv4net' => 'ipv4net',
63 'ipv6net' => 'ipv6net',
64 'ipv4rspool' => 'ipv4rspool',
65 'ipv4vs' => 'ipv4vs',
66 'ipvs' => 'ipvs',
67 'object' => 'object',
68 'rack' => 'rack',
69 'row' => 'row',
70 'location' => 'location',
71 'user' => 'user',
72 'file' => 'file',
73 'vst' => 'vst',
74 );
75 $pageno_by_etype = array_flip ($etype_by_pageno);
76
77 // Rack thumbnail image width summands: "front", "interior" and "rear" elements w/o surrounding border.
78 $rtwidth = array
79 (
80 0 => 9,
81 1 => 21,
82 2 => 9
83 );
84
85 $location_obj_types = array
86 (
87 1560,
88 1561,
89 1562
90 );
91
92 // 802.1Q deploy queue titles
93 $dqtitle = array
94 (
95 'sync_aging' => 'Normal, aging',
96 'resync_aging' => 'Failed, aging',
97 'sync_ready' => 'Normal, ready for sync',
98 'resync_ready' => 'Failed, ready for retry',
99 'disabled' => 'Sync disabled',
100 'done' => 'Up to date',
101 );
102
103 $wdm_packs = array
104 (
105 '1000cwdm80' => array
106 (
107 'title' => '1000Base-CWDM80 (8 channels)',
108 'iif_ids' => array (3, 4),
109 'oif_ids' => array (1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216),
110 ),
111 '1000dwdm80' => array // ITU channels 20~61
112 (
113 'title' => '1000Base-DWDM80 (42 channels)',
114 'iif_ids' => array (3, 4),
115 'oif_ids' => array
116 (
117 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226,
118 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236,
119 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246,
120 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256,
121 1257, 1258
122 ),
123 ),
124 '10000dwdm80' => array // same channels for 10GE
125 (
126 'title' => '10GBase-ZR-DWDM80 (42 channels)',
127 'iif_ids' => array (9, 6, 5, 8, 7),
128 'oif_ids' => array
129 (
130 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268,
131 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278,
132 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288,
133 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298,
134 1299, 1300
135 ),
136 ),
137 '10000dwdm40' => array
138 (
139 'title' => '10GBase-ER-DWDM40 (42 channels)',
140 'iif_ids' => array (9, 6, 5, 8, 7),
141 'oif_ids' => array
142 (
143 1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434,
144 1435, 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444,
145 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454,
146 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, 1463, 1464,
147 1465, 1466
148 ),
149 ),
150 );
151
152 // Default input for renderExpirations(), can be overridden in local plugins.
153 $expirations = array();
154 $expirations[21] = array
155 (
156 array ('from' => -365, 'to' => 0, 'class' => 'has_problems_', 'title' => 'has expired within last year'),
157 array ('from' => 0, 'to' => 30, 'class' => 'row_', 'title' => 'expires within 30 days'),
158 array ('from' => 30, 'to' => 60, 'class' => 'row_', 'title' => 'expires within 60 days'),
159 array ('from' => 60, 'to' => 90, 'class' => 'row_', 'title' => 'expires within 90 days'),
160 );
161 $expirations[22] = $expirations[21];
162 $expirations[24] = $expirations[21];
163
164 $natv4_proto = array ('TCP' => 'TCP', 'UDP' => 'UDP', 'ALL' => 'ALL');
165
166 $log_messages = array(); // messages waiting for displaying
167
168 function defineIfNotDefined ($constant, $value, $case_insensitive = FALSE)
169 {
170 if (defined ($constant) === FALSE)
171 define ($constant, $value, $case_insensitive);
172 }
173
174 // This function assures that specified argument was passed
175 // and is a number greater than zero.
176 function assertUIntArg ($argname, $allow_zero = FALSE)
177 {
178 if (!isset ($_REQUEST[$argname]))
179 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
180 if (!is_numeric ($_REQUEST[$argname]))
181 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a number');
182 if ($_REQUEST[$argname] < 0)
183 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is less than zero');
184 if (! $allow_zero && $_REQUEST[$argname] == 0)
185 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is zero');
186 return $_REQUEST[$argname];
187 }
188
189 function isInteger ($arg, $allow_zero = FALSE)
190 {
191 return is_numeric ($arg) && ($allow_zero || $arg != 0);
192 }
193
194 # Make sure the arg is a parsable date, return its UNIX timestamp equivalent
195 # (or empty string for empty input, when allowed).
196 #
197 # FIXME: This function should be removed for the reasons below:
198 # 1. Its naming is wrong as it would accept any argument value that is valid
199 # per DATETIME_FORMAT configuration variable, which by default is set to
200 # date only but can include time if necessary.
201 # 2. It converts the target value from a string to a UNIX timestamp, which is
202 # not the semantics of similar functions.
203 # 3. It is not used anywhere in the code anymore.
204 function assertDateArg ($argname, $ok_if_empty = FALSE)
205 {
206 if ('' == $arg = assertStringArg ($argname, $ok_if_empty))
207 return '';
208 try
209 {
210 return timestampFromDatetimestr ($arg);
211 }
212 catch (InvalidArgException $e)
213 {
214 throw $e->newIRAE ($argname);
215 }
216 }
217
218 // This function assures that specified argument was passed
219 // and is a non-empty string.
220 function assertStringArg ($argname, $ok_if_empty = FALSE)
221 {
222 global $sic;
223 if (!isset ($_REQUEST[$argname]))
224 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
225 if (!is_string ($_REQUEST[$argname]))
226 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
227 if (! $ok_if_empty && $_REQUEST[$argname] == '')
228 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
229 return $sic[$argname];
230 }
231
232 function assertBoolArg ($argname, $ok_if_empty = FALSE)
233 {
234 if (!isset ($_REQUEST[$argname]))
235 throw new InvalidRequestArgException($argname, '', 'parameter is missing');
236 if (! is_string ($_REQUEST[$argname]) || $_REQUEST[$argname] != 'on')
237 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
238 if (! $ok_if_empty && $_REQUEST[$argname] == '')
239 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
240 return $_REQUEST[$argname] == TRUE;
241 }
242
243 // function returns binary IP address, or throws an exception
244 function assertIPArg ($argname)
245 {
246 try
247 {
248 return ip_parse (assertStringArg ($argname));
249 }
250 catch (InvalidArgException $e)
251 {
252 throw $e->newIRAE ($argname);
253 }
254 }
255
256 // function returns binary IPv4 address, or throws an exception
257 function assertIPv4Arg ($argname)
258 {
259 try
260 {
261 return ip4_parse (assertStringArg ($argname));
262 }
263 catch (InvalidArgException $e)
264 {
265 throw $e->newIRAE ($argname);
266 }
267 }
268
269 // function returns binary IPv6 address, or throws an exception
270 function assertIPv6Arg ($argname)
271 {
272 try
273 {
274 return ip6_parse (assertStringArg ($argname));
275 }
276 catch (InvalidArgException $e)
277 {
278 throw $e->newIRAE ($argname);
279 }
280 }
281
282 function assertPCREArg ($argname)
283 {
284 $arg = assertStringArg ($argname, TRUE); // empty pattern is Ok
285 if (FALSE === @preg_match ($arg, 'test'))
286 throw new InvalidRequestArgException($argname, $arg, 'PCRE validation failed');
287 return $arg;
288 }
289
290 function isPCRE ($arg)
291 {
292 return isset ($arg) && FALSE !== @preg_match ($arg, 'test');
293 }
294
295 function genericAssertion ($argname, $argtype)
296 {
297 global $sic;
298 switch ($argtype)
299 {
300 case 'string':
301 return assertStringArg ($argname);
302 case 'string0':
303 return assertStringArg ($argname, TRUE);
304 case 'uint':
305 return assertUIntArg ($argname);
306 case 'uint0':
307 return assertUIntArg ($argname, TRUE);
308 case 'decimal0':
309 if ('' == assertStringArg ($argname, TRUE))
310 return '';
311 // fall through
312 case 'decimal':
313 if (! preg_match ('/^\d+(\.\d+)?$/', assertStringArg ($argname)))
314 throw new InvalidRequestArgException ($argname, $sic[$argname], 'format error');
315 return $sic[$argname];
316 case 'inet':
317 return assertIPArg ($argname);
318 case 'inet4':
319 return assertIPv4Arg ($argname);
320 case 'inet6':
321 return assertIPv6Arg ($argname);
322 case 'l2address':
323 return assertStringArg ($argname);
324 case 'l2address0':
325 assertStringArg ($argname, TRUE);
326 try
327 {
328 l2addressForDatabase ($sic[$argname]);
329 }
330 catch (InvalidArgException $e)
331 {
332 throw new InvalidRequestArgException ($argname, $sic[$argname], 'malformed MAC/WWN address');
333 }
334 return $sic[$argname];
335 case 'tag':
336 if (!validTagName (assertStringArg ($argname)))
337 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Invalid tag name');
338 return $sic[$argname];
339 case 'pcre':
340 return assertPCREArg ($argname);
341 case 'json':
342 if (NULL === ($ret = json_decode (assertStringArg ($argname), TRUE)))
343 throw new InvalidRequestArgException ($argname, '(omitted)', 'Invalid JSON code received from client');
344 return $ret;
345 case 'array':
346 if (! array_key_exists ($argname, $_REQUEST))
347 throw new InvalidRequestArgException ($argname, '(missing argument)');
348 if (! is_array ($_REQUEST[$argname]))
349 throw new InvalidRequestArgException ($argname, '(omitted)', 'argument is not an array');
350 return $_REQUEST[$argname];
351 case 'array0':
352 if (! array_key_exists ($argname, $_REQUEST))
353 return array();
354 if (! is_array ($_REQUEST[$argname]))
355 throw new InvalidRequestArgException ($argname, '(omitted)', 'argument is not an array');
356 return $_REQUEST[$argname];
357 case 'datetime0':
358 if ('' == assertStringArg ($argname, TRUE))
359 return '';
360 // fall through
361 case 'datetime':
362 $argvalue = assertStringArg ($argname);
363 try
364 {
365 timestampFromDatetimestr ($argvalue); // discard the result on success
366 }
367 catch (InvalidArgException $iae)
368 {
369 throw $iae->newIRAE ($argname);
370 }
371 return $argvalue;
372 case 'enum/attr_type':
373 assertStringArg ($argname);
374 if (!in_array ($sic[$argname], array ('uint', 'float', 'string', 'dict','date')))
375 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
376 return $sic[$argname];
377 case 'enum/vlan_type':
378 assertStringArg ($argname);
379 // "Alien" type is not valid until the logic is fixed to implement it in full.
380 if (!in_array ($sic[$argname], array ('ondemand', 'compulsory')))
381 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
382 return $sic[$argname];
383 case 'enum/wdmstd':
384 assertStringArg ($argname);
385 global $wdm_packs;
386 if (! array_key_exists ($sic[$argname], $wdm_packs))
387 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
388 return $sic[$argname];
389 case 'enum/ipproto':
390 assertStringArg ($argname);
391 global $vs_proto;
392 if (!array_key_exists ($sic[$argname], $vs_proto))
393 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
394 return $sic[$argname];
395 case 'enum/natv4proto':
396 assertStringArg ($argname);
397 global $natv4_proto;
398 if (! array_key_exists ($sic[$argname], $natv4_proto))
399 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
400 return $sic[$argname];
401 case 'enum/alloc_type':
402 assertStringArg ($argname);
403 if (!in_array ($sic[$argname], array ('regular', 'shared', 'virtual', 'router', 'point2point')))
404 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
405 return $sic[$argname];
406 case 'enum/dqcode':
407 assertStringArg ($argname);
408 global $dqtitle;
409 if (! array_key_exists ($sic[$argname], $dqtitle))
410 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
411 return $sic[$argname];
412 case 'enum/yesno':
413 if (! in_array ($sic[$argname], array ('yes', 'no')))
414 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
415 return $sic[$argname];
416 case 'iif':
417 assertUIntArg ($argname);
418 if (!array_key_exists ($sic[$argname], getPortIIFOptions()))
419 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value');
420 return $sic[$argname];
421 // 'vlan' -- any valid VLAN ID except the default
422 // 'vlan1' -- any valid VLAN ID including the default
423 case 'vlan':
424 case 'vlan1':
425 assertUIntArg ($argname);
426 if ($argtype == 'vlan' && $sic[$argname] == VLAN_DFL_ID)
427 throw new InvalidRequestArgException ($argname, $sic[$argname], 'default VLAN not allowed');
428 if ($sic[$argname] > VLAN_MAX_ID || $sic[$argname] < VLAN_MIN_ID)
429 throw new InvalidRequestArgException ($argname, $sic[$argname], 'not a valid VLAN ID');
430 return $sic[$argname];
431 case 'uint-vlan':
432 case 'uint-vlan1':
433 if (! preg_match ('/^([1-9][0-9]*)-([1-9][0-9]*)$/', assertStringArg ($argname), $m))
434 throw new InvalidRequestArgException ($argname, $sic[$argname], 'format error');
435 if ($argtype == 'uint-vlan' && $m[2] == VLAN_DFL_ID)
436 throw new InvalidRequestArgException ($argname, $sic[$argname], 'default VLAN not allowed');
437 if ($m[2] > VLAN_MAX_ID || $m[2] < VLAN_MIN_ID)
438 throw new InvalidRequestArgException ($argname, $sic[$argname], 'not a valid VLAN ID');
439 return $sic[$argname];
440 case 'rackcode/expr':
441 if ('' == assertStringArg ($argname, TRUE))
442 return array();
443 if (! $expr = compileExpression ($sic[$argname]))
444 throw new InvalidRequestArgException ($argname, $sic[$argname], 'not a valid RackCode expression');
445 return $expr;
446 default:
447 throw new InvalidArgException ('argtype', $argtype); // comes not from user's input
448 }
449 }
450
451 // return HTML form checkbox value (TRUE of FALSE) by name of its input control
452 function isCheckSet ($input_name, $mode = 'bool')
453 {
454 $value = isset ($_REQUEST[$input_name]) && $_REQUEST[$input_name] == 'on';
455 switch ($mode)
456 {
457 case 'bool' : return $value;
458 case 'yesno': return $value ? 'yes' : 'no';
459 default: throw new InvalidArgException ('mode', $mode);
460 }
461 }
462
463 // Validate and return "bypass" value for the current context, if one is
464 // defined for it, or NULL otherwise.
465 // There is at least one bit of code that depends on the NULL return value
466 // (although it does not explicitly check for it), it is the "interface" case
467 // in index.php, which makes an unconditional call to here. Changing this
468 // function to throw an exception instead will require changing at least
469 // that code too.
470 function getBypassValue()
471 {
472 global $page, $pageno;
473 if (!array_key_exists ('bypass', $page[$pageno]))
474 return NULL;
475 if (!array_key_exists ('bypass_type', $page[$pageno]))
476 throw new RackTablesError ("Internal structure error at node '${pageno}' (bypass_type is not set)", RackTablesError::INTERNAL);
477 return genericAssertion ($page[$pageno]['bypass'], $page[$pageno]['bypass_type']);
478 }
479
480 // fills $args array with the bypass values of specified $pageno that are provided in $_REQUEST
481 function fillBypassValues ($pageno, &$args)
482 {
483 global $page, $sic;
484 if (isset ($page[$pageno]['bypass']))
485 {
486 $param_name = $page[$pageno]['bypass'];
487 if (! array_key_exists ($param_name, $args) && isset ($sic[$param_name]))
488 $args[$param_name] = $sic[$param_name];
489 }
490 if (isset ($page[$pageno]['bypass_tabs']))
491 foreach ($page[$pageno]['bypass_tabs'] as $param_name)
492 if (! array_key_exists ($param_name, $args) && isset ($sic[$param_name]))
493 $args[$param_name] = $sic[$param_name];
494 }
495
496 // Objects of some types should be explicitly shown as
497 // anonymous (labelless). This function is a single place where the
498 // decision about displayed name is made.
499 function formatObjectDisplayedName ($name, $objtype_id)
500 {
501 return ($name != '') ? $name : sprintf ('[%s]', decodeObjectType ($objtype_id));
502 }
503
504 // Set the dname attribute within a cell
505 function setDisplayedName (&$cell)
506 {
507 if ($cell['realm'] == 'object')
508 {
509 $cell['dname'] = formatObjectDisplayedName ($cell['name'], $cell['objtype_id']);
510 // If the object has a container, set its dname as well
511 if ($cell['container_id'])
512 $cell['container_dname'] = formatObjectDisplayedName ($cell['container_name'], $cell['container_objtype_id']);
513 }
514 elseif ($cell['realm'] == 'ipv4vs')
515 if ($cell['proto'] == 'MARK')
516 $cell['dname'] = 'fwmark: ' . implode ('', unpack ('N', substr ($cell['vip_bin'], 0, 4)));
517 else
518 $cell['dname'] = $cell['vip'] . ':' . $cell['vport'] . '/' . $cell['proto'];
519 }
520
521 // This function finds height of solid rectangle of atoms that are all
522 // assigned to the same object. Rectangle base is defined by specified
523 // template.
524 function rectHeight ($rackData, $startRow, $template_idx)
525 {
526 $height = 0;
527 // The first met object_id is used to match all the folowing IDs.
528 $object_id = 0;
529 global $template;
530 do
531 {
532 for ($locidx = 0; $locidx < 3; $locidx++)
533 {
534 // At least one value in template is TRUE, but the following block
535 // can meet 'skipped' atoms. Let's ensure we have something after processing
536 // the first row.
537 if ($template[$template_idx][$locidx])
538 {
539 if
540 (
541 isset ($rackData[$startRow - $height][$locidx]['skipped']) ||
542 isset ($rackData[$startRow - $height][$locidx]['rowspan']) ||
543 isset ($rackData[$startRow - $height][$locidx]['colspan']) ||
544 $rackData[$startRow - $height][$locidx]['state'] != 'T'
545 )
546 break 2;
547 if ($object_id == 0)
548 $object_id = $rackData[$startRow - $height][$locidx]['object_id'];
549 if ($object_id != $rackData[$startRow - $height][$locidx]['object_id'])
550 break 2;
551 }
552 }
553 // If the first row can't offer anything, bail out.
554 if ($height == 0 && $object_id == 0)
555 break;
556 $height++;
557 }
558 while ($startRow - $height > 0);
559 return $height;
560 }
561
562 // This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan
563 // attributes.
564 function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
565 {
566 global $template, $templateWidth;
567 $colspan = 0;
568 for ($height = 0; $height < $maxheight; $height++)
569 for ($locidx = 0; $locidx < 3; $locidx++)
570 if ($template[$template_idx][$locidx])
571 {
572 // Add colspan/rowspan to the first row met and mark the following ones to skip.
573 // Explicitly show even single-cell spanned atoms, because rectHeight()
574 // is expeciting this data for correct calculation.
575 if ($colspan != 0)
576 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
577 else
578 {
579 $colspan = $templateWidth[$template_idx];
580 if ($colspan >= 1)
581 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
582 if ($maxheight >= 1)
583 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
584 }
585 }
586 }
587
588 // This function sets rowspan/solspan/skipped atom attributes for renderRack()
589 // What we actually have to do is to find _all_ possible rectangles for each unit
590 // and then select the widest of those with the maximal square.
591 function markAllSpans (&$rackData)
592 {
593 for ($i = $rackData['height']; $i > 0; $i--)
594 while (markBestSpan ($rackData, $i));
595 }
596
597 // Calculate height of 6 possible span templates (array is presorted by width
598 // descending) and mark the best (if any).
599 function markBestSpan (&$rackData, $i)
600 {
601 global $template, $templateWidth;
602 for ($j = 0; $j < 6; $j++)
603 {
604 $height[$j] = rectHeight ($rackData, $i, $j);
605 $square[$j] = $height[$j] * $templateWidth[$j];
606 }
607 // find the widest rectangle of those with maximal height
608 $maxsquare = max ($square);
609 if (!$maxsquare)
610 return FALSE;
611 $best_template_index = 0;
612 for ($j = 0; $j < 6; $j++)
613 if ($square[$j] == $maxsquare)
614 {
615 $best_template_index = $j;
616 $bestheight = $height[$j];
617 break;
618 }
619 // distribute span marks
620 markSpan ($rackData, $i, $bestheight, $best_template_index);
621 return TRUE;
622 }
623
624 // We can mount 'F' atoms and unmount our own 'T' atoms.
625 function applyObjectMountMask (&$rackData, $object_id)
626 {
627 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
628 for ($locidx = 0; $locidx < 3; $locidx++)
629 switch ($rackData[$unit_no][$locidx]['state'])
630 {
631 case 'F':
632 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
633 break;
634 case 'T':
635 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id);
636 break;
637 default:
638 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
639 }
640 }
641
642 // check permissions for rack modification
643 function rackModificationPermitted ($rackData, $op, $with_context=TRUE)
644 {
645 $op_annex = array (array ('tag' => '$op_'.$op), array ('tag' => '$any_op'));
646 $rack_op_annex = array_merge ($rackData['etags'], $rackData['itags'], $rackData['atags'], $op_annex);
647 $context = !$with_context || permitted (NULL, NULL, NULL, $op_annex);
648 return $context && permitted (NULL, NULL, NULL, $rack_op_annex);
649 }
650
651 // Design change means transition between 'F' and 'A' and back.
652 function applyRackDesignMask (&$rackData)
653 {
654 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
655 for ($locidx = 0; $locidx < 3; $locidx++)
656 switch ($rackData[$unit_no][$locidx]['state'])
657 {
658 case 'F':
659 case 'A':
660 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
661 break;
662 default:
663 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
664 }
665 }
666
667 // The same for 'F' and 'U'.
668 function applyRackProblemMask (&$rackData)
669 {
670 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
671 for ($locidx = 0; $locidx < 3; $locidx++)
672 switch ($rackData[$unit_no][$locidx]['state'])
673 {
674 case 'F':
675 case 'U':
676 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
677 break;
678 default:
679 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
680 }
681 }
682
683 // This function highlights specified object by amending the
684 // 'hl' suffix of the class name.
685 function highlightObject (&$rackData, $object_id)
686 {
687 // Also highlight parent objects
688 $object = spotEntity ('object', $object_id);
689 $parents = reindexById (getParents ($object, 'object'));
690
691 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
692 for ($locidx = 0; $locidx < 3; $locidx++)
693 {
694 $atom = &$rackData[$unit_no][$locidx];
695 if
696 (
697 $atom['state'] == 'T' &&
698 ($atom['object_id'] == $object_id || isset ($parents[$atom['object_id']]))
699 )
700 $atom['hl'] = 'h' . $atom['hl'];
701 }
702 }
703
704 // This function marks atoms to selected or not depending on their current state.
705 function markupAtomGrid (&$data, $checked_state)
706 {
707 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
708 for ($locidx = 0; $locidx < 3; $locidx++)
709 {
710 if (!($data[$unit_no][$locidx]['enabled'] === TRUE))
711 continue;
712 if ($data[$unit_no][$locidx]['state'] == $checked_state)
713 $data[$unit_no][$locidx]['checked'] = ' checked';
714 else
715 $data[$unit_no][$locidx]['checked'] = '';
716 }
717 }
718
719 // This function is almost a clone of processGridForm(), but doesn't save anything to database
720 // Return value is the changed rack data.
721 // Here we assume that correct filter has already been applied, so we just
722 // set or unset checkbox inputs w/o changing atom state.
723 function mergeGridFormToRack (&$rackData)
724 {
725 $rack_id = $rackData['id'];
726 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
727 for ($locidx = 0; $locidx < 3; $locidx++)
728 if ($rackData[$unit_no][$locidx]['enabled'])
729 $rackData[$unit_no][$locidx]['checked'] =
730 isCheckSet ("atom_${rack_id}_${unit_no}_${locidx}") ? ' checked' : '';
731 }
732
733 // wrapper around ip4_mask and ip6_mask
734 // netmask conversion from length to binary string
735 // v4/v6 mode is toggled by $is_ipv6 parameter
736 // Throws exception if $prefix_len is invalid
737 function ip_mask ($prefix_len, $is_ipv6)
738 {
739 return $is_ipv6 ? ip6_mask ($prefix_len) : ip4_mask ($prefix_len);
740 }
741
742 // netmask conversion from length to binary string
743 // Throws exception if $prefix_len is invalid
744 function ip4_mask ($prefix_len)
745 {
746 static $mask = array
747 (
748 "\x00\x00\x00\x00", // 0
749 "\x80\x00\x00\x00", // 1
750 "\xC0\x00\x00\x00", // 2
751 "\xE0\x00\x00\x00", // 3
752 "\xF0\x00\x00\x00", // 4
753 "\xF8\x00\x00\x00", // 5
754 "\xFC\x00\x00\x00", // 6
755 "\xFE\x00\x00\x00", // 7
756 "\xFF\x00\x00\x00", // 8
757 "\xFF\x80\x00\x00", // 9
758 "\xFF\xC0\x00\x00", // 10
759 "\xFF\xE0\x00\x00", // 11
760 "\xFF\xF0\x00\x00", // 12
761 "\xFF\xF8\x00\x00", // 13
762 "\xFF\xFC\x00\x00", // 14
763 "\xFF\xFE\x00\x00", // 15
764 "\xFF\xFF\x00\x00", // 16
765 "\xFF\xFF\x80\x00", // 17
766 "\xFF\xFF\xC0\x00", // 18
767 "\xFF\xFF\xE0\x00", // 19
768 "\xFF\xFF\xF0\x00", // 20
769 "\xFF\xFF\xF8\x00", // 21
770 "\xFF\xFF\xFC\x00", // 22
771 "\xFF\xFF\xFE\x00", // 23
772 "\xFF\xFF\xFF\x00", // 24
773 "\xFF\xFF\xFF\x80", // 25
774 "\xFF\xFF\xFF\xC0", // 26
775 "\xFF\xFF\xFF\xE0", // 27
776 "\xFF\xFF\xFF\xF0", // 28
777 "\xFF\xFF\xFF\xF8", // 29
778 "\xFF\xFF\xFF\xFC", // 30
779 "\xFF\xFF\xFF\xFE", // 31
780 "\xFF\xFF\xFF\xFF", // 32
781 );
782
783 if ($prefix_len >= 0 && $prefix_len <= 32)
784 return $mask[$prefix_len];
785 throw new InvalidArgException ('prefix_len', $prefix_len);
786 }
787
788 // netmask conversion from length to binary string
789 // Throws exception if $prefix_len is invalid
790 function ip6_mask ($prefix_len)
791 {
792 static $mask = array
793 (
794 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 0
795 "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 1
796 "\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 2
797 "\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 3
798 "\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 4
799 "\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 5
800 "\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 6
801 "\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 7
802 "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 8
803 "\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 9
804 "\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 10
805 "\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 11
806 "\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 12
807 "\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 13
808 "\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 14
809 "\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 15
810 "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 16
811 "\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 17
812 "\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 18
813 "\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 19
814 "\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 20
815 "\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 21
816 "\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 22
817 "\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 23
818 "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 24
819 "\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 25
820 "\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 26
821 "\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 27
822 "\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 28
823 "\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 29
824 "\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 30
825 "\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 31
826 "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 32
827 "\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 33
828 "\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 34
829 "\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 35
830 "\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 36
831 "\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 37
832 "\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 38
833 "\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 39
834 "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 40
835 "\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 41
836 "\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 42
837 "\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 43
838 "\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 44
839 "\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 45
840 "\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 46
841 "\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 47
842 "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 48
843 "\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 49
844 "\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 50
845 "\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 51
846 "\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 52
847 "\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 53
848 "\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 54
849 "\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 55
850 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 56
851 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00", // 57
852 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00", // 58
853 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00", // 59
854 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00", // 60
855 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00", // 61
856 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00", // 62
857 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00", // 63
858 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00", // 64
859 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00", // 65
860 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00", // 66
861 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00", // 67
862 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00", // 68
863 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00", // 69
864 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00", // 70
865 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00", // 71
866 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00", // 72
867 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00", // 73
868 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00", // 74
869 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00", // 75
870 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00", // 76
871 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00", // 77
872 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00", // 78
873 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00", // 79
874 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00", // 80
875 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00", // 81
876 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00", // 82
877 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00", // 83
878 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00", // 84
879 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00", // 85
880 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00", // 86
881 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00", // 87
882 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00", // 88
883 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00", // 89
884 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00", // 90
885 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00", // 91
886 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00", // 92
887 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00", // 93
888 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00", // 94
889 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00", // 95
890 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00", // 96
891 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00", // 97
892 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00", // 98
893 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00", // 99
894 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00", // 100
895 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00", // 101
896 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00", // 102
897 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00", // 103
898 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00", // 104
899 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00", // 105
900 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00", // 106
901 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00", // 107
902 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00", // 108
903 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00", // 109
904 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00", // 110
905 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00", // 111
906 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", // 112
907 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00", // 113
908 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00", // 114
909 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00", // 115
910 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00", // 116
911 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00", // 117
912 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00", // 118
913 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00", // 119
914 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", // 120
915 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80", // 121
916 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0", // 122
917 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0", // 123
918 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0", // 124
919 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8", // 125
920 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC", // 126
921 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE", // 127
922 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", // 128
923 );
924
925 if ($prefix_len >= 0 && $prefix_len <= 128)
926 return $mask[$prefix_len];
927 throw new InvalidArgException ('prefix_len', $prefix_len);
928 }
929
930 // Return a uniformly (010203040506 or 0102030405060708) formatted address, if it is present
931 // in the provided string, an empty string for an empty string or raise an exception.
932 function l2addressForDatabase ($string)
933 {
934 $string = strtoupper (trim ($string));
935 $ret = '';
936 switch (TRUE)
937 {
938 case ($string == '' || preg_match (RE_L2_SOLID, $string) || preg_match (RE_L2_WWN_SOLID, $string)):
939 $ret = $string;
940 break;
941 case (preg_match (RE_L2_IFCFG, $string) || preg_match (RE_L2_WWN_COLON, $string)):
942 // reformat output of SunOS ifconfig
943 $ret = '';
944 foreach (explode (':', $string) as $byte)
945 $ret .= (strlen ($byte) == 1 ? '0' : '') . $byte;
946 $ret = $ret;
947 break;
948 case (preg_match (RE_L2_CISCO, $string)):
949 $ret = str_replace ('.', '', $string);
950 break;
951 case (preg_match (RE_L2_HUAWEI, $string)):
952 $ret = str_replace ('-', '', $string);
953 break;
954 case (preg_match (RE_L2_IPCFG, $string) || preg_match (RE_L2_WWN_HYPHEN, $string)):
955 $ret = str_replace ('-', '', $string);
956 break;
957 default:
958 throw new InvalidArgException ('string', $string, 'malformed MAC/WWN address');
959 }
960 // some switches provide this fake address through SNMP. Store it as NULL to allow multiple copies
961 if ($ret === '000000000000')
962 $ret = '';
963 return $ret;
964 }
965
966 function l2addressFromDatabase ($string)
967 {
968 switch (strlen ($string))
969 {
970 case 12: // Ethernet
971 case 16: // FireWire/Fibre Channel
972 return implode (':', str_split ($string, 2));
973 default:
974 return $string;
975 }
976 }
977
978 // DEPRECATED, remove in 0.21.0
979 function getPrevIDforRack ($row_id, $rack_id)
980 {
981 $n = getRackNeighbors ($row_id, $rack_id);
982 return $n['prev'];
983 }
984
985 // DEPRECATED, remove in 0.21.0
986 function getNextIDforRack ($row_id, $rack_id)
987 {
988 $n = getRackNeighbors ($row_id, $rack_id);
989 return $n['next'];
990 }
991
992 function getRackNeighbors ($row_id, $rack_id)
993 {
994 $ret = array ('prev' => NULL, 'next' => NULL);
995 $ids = selectRackOrder ($row_id);
996 $index = array_search ($rack_id, $ids);
997 if ($index !== FALSE && $index > 0)
998 $ret['prev'] = $ids[$index - 1];
999 if ($index !== FALSE && $index + 1 < count ($ids))
1000 $ret['next'] = $ids[$index + 1];
1001 return $ret;
1002 }
1003
1004 // Return a list of rack IDs that are P or less positions
1005 // far from the given rack in its row.
1006 function getProximateRacks ($rack_id, $proximity = 0)
1007 {
1008 $ret = array ($rack_id);
1009 if ($proximity > 0)
1010 {
1011 $rack = spotEntity ('rack', $rack_id);
1012 $rackList = selectRackOrder ($rack['row_id']);
1013 $cur_item = array_search ($rack_id, $rackList);
1014 if (FALSE !== $cur_item)
1015 {
1016 if ($todo = min ($cur_item, $proximity))
1017 $ret = array_merge ($ret, array_slice ($rackList, $cur_item - $todo, $todo));
1018 if ($todo = min (count ($rackList) - 1 - $cur_item, $proximity))
1019 $ret = array_merge ($ret, array_slice ($rackList, $cur_item + 1, $todo));
1020 }
1021 }
1022 return $ret;
1023 }
1024
1025 function sortTokenize ($a, $b)
1026 {
1027 $aold='';
1028 while ($a != $aold)
1029 {
1030 $aold=$a;
1031 $a = preg_replace('/[^a-zA-Z0-9]/',' ',$a);
1032 $a = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$a);
1033 $a = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$a);
1034 }
1035
1036 $bold='';
1037 while ($b != $bold)
1038 {
1039 $bold=$b;
1040 $b = preg_replace('/[^a-zA-Z0-9]/',' ',$b);
1041 $b = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$b);
1042 $b = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$b);
1043 }
1044
1045 $ar = explode(' ', $a);
1046 $br = explode(' ', $b);
1047 for ($i=0; $i<count($ar) && $i<count($br); $i++)
1048 {
1049 $ret = 0;
1050 if (is_numeric($ar[$i]) && is_numeric($br[$i]))
1051 $ret = ($ar[$i]==$br[$i])?0:($ar[$i]<$br[$i]?-1:1);
1052 else
1053 $ret = strcasecmp($ar[$i], $br[$i]);
1054 if ($ret != 0)
1055 return $ret;
1056 }
1057 if ($i<count($ar))
1058 return 1;
1059 if ($i<count($br))
1060 return -1;
1061 return 0;
1062 }
1063
1064 // This function returns an array of single element of object's FQDN attribute,
1065 // if FQDN is set. The next choice is object's common name, if it looks like a
1066 // hostname. Otherwise an array of all 'regular' IP addresses of the
1067 // object is returned (which may appear 0 and more elements long).
1068 function findAllEndpoints ($object_id, $fallback = '')
1069 {
1070 foreach (getAttrValues ($object_id) as $record)
1071 if ($record['id'] == 3 && $record['value'] != '') // FQDN
1072 return array ($record['value']);
1073 $regular = array();
1074 foreach (getObjectIPv4AllocationList ($object_id) as $ip_bin => $alloc)
1075 if ($alloc['type'] == 'regular')
1076 $regular[] = ip4_format ($ip_bin);
1077 // FIXME: add IPv6 allocations to this list
1078 if (!count ($regular) && $fallback != '')
1079 return array ($fallback);
1080 return $regular;
1081 }
1082
1083 // Split object's FQDN (or the common name if FQDN is not set) into the
1084 // hostname and domain name in Munin convention (using the first period as the
1085 // separator), and return the pair. Throw an exception on error.
1086 function getMuninNameAndDomain ($object_id)
1087 {
1088 $o = spotEntity ('object', $object_id);
1089 $hd = $o['name'];
1090 // FQDN overrides the common name for Munin purposes.
1091 $attrs = getAttrValues ($object_id);
1092 if (array_key_exists (3, $attrs) && $attrs[3]['value'] != '')
1093 $hd = $attrs[3]['value'];
1094 if (2 != count ($ret = preg_split ('/\./', $hd, 2)))
1095 throw new InvalidArgException ('object_id', $object_id, 'the name is not in the host.do.ma.in format');
1096 return $ret;
1097 }
1098
1099 // Some records in the dictionary may be written as plain text or as Wiki
1100 // link in the following syntax:
1101 // 1. word
1102 // 2. [[word URL]] // FIXME: this isn't working
1103 // 3. [[word word word | URL]]
1104 // This function parses the line in $record['value'] and modifies $record:
1105 // $record['o_value'] is set to be the first part of link (word word word)
1106 // $record['a_value'] is the same, but with %GPASS and %GSKIP macros applied
1107 // $record['href'] is set to URL if it is specified in the input value
1108 function parseWikiLink (&$record)
1109 {
1110 if (! preg_match ('/^\[\[(.+)\]\]$/', $record['value'], $matches))
1111 $record['o_value'] = $record['value'];
1112 else
1113 {
1114 $s = explode ('|', $matches[1]);
1115 if (isset ($s[1]))
1116 $record['href'] = trim ($s[1]);
1117 $record['o_value'] = trim ($s[0]);
1118 }
1119 $record['a_value'] = execGMarker ($record['o_value']);
1120 }
1121
1122 // FIXME: should this be saved as "P-data"?
1123 function execGMarker ($line)
1124 {
1125 return preg_replace ('/^.+%GSKIP%/', '',
1126 preg_replace ('/^(.+)%GPASS%/', '\\1 ',
1127 preg_replace ('/%L\d+,\d+(H|V|)%/', '', $line)));
1128 }
1129
1130 // extract the layout information from the %L...% marker in the dictionary info
1131 // This is somewhat similar to the %GPASS %GSKIP
1132 function extractLayout (&$record)
1133 {
1134 if (preg_match ('/%L(\d+),(\d+)(H|V|)%/', $record['value'], $matches))
1135 {
1136 $record['rows'] = $matches[1];
1137 $record['cols'] = $matches[2];
1138 $record['layout'] = $matches[3];
1139 if ($record['layout'] == '')
1140 $record['layout'] = ($record['cols'] >= 4) ? 'V' : 'H';
1141 }
1142 }
1143
1144 // rackspace usage for a single rack
1145 // (T + W + U) / (height * 3 - A)
1146 function getRSUforRack ($data)
1147 {
1148 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
1149 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1150 for ($locidx = 0; $locidx < 3; $locidx++)
1151 $counter[$data[$unit_no][$locidx]['state']]++;
1152 return ($counter['T'] + $counter['W'] + $counter['U']) /
1153 ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
1154 }
1155
1156 // Same for row.
1157 function getRSUforRow ($rowData)
1158 {
1159 if (!count ($rowData))
1160 return 0;
1161 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
1162 $total_height = 0;
1163 foreach (array_keys ($rowData) as $rack_id)
1164 {
1165 $data = spotEntity ('rack', $rack_id);
1166 amplifyCell ($data);
1167 $total_height += $data['height'];
1168 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1169 for ($locidx = 0; $locidx < 3; $locidx++)
1170 $counter[$data[$unit_no][$locidx]['state']]++;
1171 }
1172 return ($counter['T'] + $counter['W'] + $counter['U']) /
1173 ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
1174 }
1175
1176 function string_insert_hrefs_callback ($m)
1177 {
1178 $t_url_href = 'href="' . rtrim($m[1], '.') . '"';
1179 $s_url_replace = "<a ${t_url_href}>$m[1]</a> [<a ${t_url_href} target=\"_blank\">^</a>]";
1180 return $s_url_replace;
1181 }
1182
1183 # Detect URLs and email addresses in the string and replace them with href anchors
1184 # (adopted from MantisBT, core/string_api.php:string_insert_hrefs).
1185 function string_insert_hrefs ($p_string)
1186 {
1187 static $s_url_regex = null;
1188 static $s_url_replace = null;
1189 static $s_email_regex = null;
1190 static $s_anchor_regex = '/(<a[^>]*>.*?<\/a>)/is';
1191
1192 if (getConfigVar ('DETECT_URLS') != 'yes')
1193 return $p_string;
1194
1195 # Initialize static variables
1196 if (is_null ($s_url_regex))
1197 {
1198 # URL regex
1199 $t_url_protocol = '(?:[[:alpha:]][-+.[:alnum:]]*):\/\/';
1200
1201 # %2A notation in url's
1202 $t_url_hex = '%[[:digit:]A-Fa-f]{2}';
1203
1204 # valid set of characters that may occur in url scheme. Note: - should be first (A-F != -AF).
1205 $t_url_valid_chars = '-_.,!~*\';\/?%^\\\\:@&={\|}+$#[:alnum:]\pL';
1206 $t_url_chars = "(?:${t_url_hex}|[${t_url_valid_chars}\(\)\[\]])";
1207 $t_url_chars2 = "(?:${t_url_hex}|[${t_url_valid_chars}])";
1208 $t_url_chars_in_brackets = "(?:${t_url_hex}|[${t_url_valid_chars}\(\)])";
1209 $t_url_chars_in_parens = "(?:${t_url_hex}|[${t_url_valid_chars}\[\]])";
1210
1211 $t_url_part1 = "${t_url_chars}";
1212 $t_url_part2 = "(?:\(${t_url_chars_in_parens}*\)|\[${t_url_chars_in_brackets}*\]|${t_url_chars2})";
1213
1214 $s_url_regex = "/(${t_url_protocol}(${t_url_part1}*?${t_url_part2}+))/su";
1215
1216 # URL replacement
1217 $t_url_href = "href=\"'.rtrim('\\1','.').'\"";
1218 $s_url_replace = "'<a ${t_url_href}>\\1</a> [<a ${t_url_href} target=\"_blank\">^</a>]'";
1219
1220 # e-mail regex
1221 $s_email_regex = substr_replace (email_regex_simple(), '(?:mailto:)?', 1, 0);
1222 }
1223
1224 # Find any URL in a string and replace it by a clickable link
1225 $p_string = preg_replace_callback
1226 (
1227 $s_url_regex,
1228 'string_insert_hrefs_callback',
1229 $p_string
1230 );
1231
1232 # Find any email addresses in the string and replace them with a clickable
1233 # mailto: link, making sure that we skip processing of any existing anchor
1234 # tags, to avoid parts of URLs such as https://user@example.com/ or
1235 # http://user:password@example.com/ to be not treated as an email.
1236 $t_pieces = preg_split ($s_anchor_regex, $p_string, null, PREG_SPLIT_DELIM_CAPTURE);
1237 $p_string = '';
1238 foreach ($t_pieces as $piece)
1239 if (preg_match ($s_anchor_regex, $piece))
1240 $p_string .= $piece;
1241 else
1242 $p_string .= preg_replace ($s_email_regex, '<a href="mailto:\0">\0</a>', $piece);
1243
1244 return $p_string;
1245 }
1246
1247 # Adopted from MantisBT, core/email_api.php:email_regex_simple.
1248 function email_regex_simple()
1249 {
1250 static $s_email_regex = null;
1251
1252 if (is_null ($s_email_regex))
1253 {
1254 $t_recipient = "([a-z0-9!#*+\/=?^_{|}~-]+(?:\.[a-z0-9!#*+\/=?^_{|}~-]+)*)";
1255
1256 # a domain is one or more subdomains
1257 $t_subdomain = "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)";
1258 $t_domain = "(${t_subdomain}(?:\.${t_subdomain})*)";
1259
1260 $s_email_regex = "/${t_recipient}\@${t_domain}/i";
1261 }
1262 return $s_email_regex;
1263 }
1264
1265 // Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
1266 // for the requested object.
1267 function getAutoPorts ($object)
1268 {
1269 return parseAutoPortsConfig (getAutoPortsConfig ($object));
1270 }
1271
1272 // Extract automatic ports schema from the AUTOPORTS_CONFIG
1273 // based on the given object's type. Can be overriden
1274 function getAutoPortsConfig ($object)
1275 {
1276 $override = callHook ('getAutoPortsConfig_hook', $object);
1277 if (isset ($override))
1278 return $override;
1279
1280 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
1281 foreach ($typemap as $equation)
1282 {
1283 $tmp = explode ('=', $equation);
1284 if (count ($tmp) != 2)
1285 continue;
1286 $objtype_id = $tmp[0];
1287 if ($objtype_id == $object['objtype_id'])
1288 return $tmp[1];
1289 }
1290 }
1291
1292 function parseAutoPortsConfig ($schema)
1293 {
1294 $ret = array();
1295
1296 foreach (explode ('+', $schema) as $product)
1297 {
1298 $tmp = explode ('*', $product);
1299 if (count ($tmp) > 4 || count ($tmp) < 3)
1300 continue;
1301 # format: <number of ports>*<port_type_id>[*<sprintf_name>*<startnumber>]
1302 $nports = $tmp[0];
1303 $port_type = $tmp[1];
1304 $format = $tmp[2];
1305 $startnum = isset ($tmp[3]) ? $tmp[3] : 0;
1306 for ($i = 0; $i < $nports; $i++)
1307 $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i + $startnum));
1308 }
1309 return $ret;
1310 }
1311
1312 // Use pre-served trace to traverse the tree, then place given node where it belongs.
1313 function pokeNode (&$tree, $trace, $key, $value, $threshold = 0)
1314 {
1315 $self = __FUNCTION__;
1316 // This function needs the trace to be followed FIFO-way. The fastest
1317 // way to do so is to use array_push() for putting values into the
1318 // list and array_shift() for getting them out. This exposed up to 11%
1319 // performance gain compared to other patterns of array_push/array_unshift/
1320 // array_reverse/array_pop/array_shift conjunction.
1321 $myid = array_shift ($trace);
1322 if (count ($trace)) // not yet reached the target
1323 $self ($tree[$myid]['kids'], $trace, $key, $value, $threshold);
1324 else // just did
1325 {
1326 if (! $threshold || ($threshold && $tree[$myid]['kidc'] + 1 < $threshold))
1327 $tree[$myid]['kids'][$key] = $value;
1328 // Reset accumulated records once, when the limit is reached, not each time
1329 // after that.
1330 if (++$tree[$myid]['kidc'] == $threshold)
1331 $tree[$myid]['kids'] = array();
1332 }
1333 }
1334
1335 // Likewise traverse the tree with the trace and return the final node.
1336 // This function is not currently used in RackTables main code but it works well.
1337 function peekNode ($tree, $trace, $target_id)
1338 {
1339 $self = __FUNCTION__;
1340 if (NULL === ($next = array_shift ($trace))) // warm
1341 {
1342 foreach ($tree as $node)
1343 if (array_key_exists ('id', $node) && $node['id'] == $target_id) // hot
1344 return $node;
1345 }
1346 else // cold
1347 {
1348 foreach ($tree as $node)
1349 if (array_key_exists ('id', $node) && $node['id'] == $next) // warmer
1350 return $self ($node['kids'], $trace, $target_id);
1351 }
1352 throw new RackTablesError ('inconsistent tree data', RackTablesError::INTERNAL);
1353 }
1354
1355 // The structure used in RackTables to represent tags is called a forest of rooted
1356 // trees, which is a set of directed graphs each having a node appointed as root
1357 // and exactly one path possible from the root node to every other node of the
1358 // graph.
1359 //
1360 // The TagTree database table contains a generic list of graph nodes with each node
1361 // having an optional incoming directed edge from any existing node. This table
1362 // generally can encode any set of any directed graphs that allow at most one
1363 // incoming edge per node. This includes but is not limited to the forest of rooted
1364 // trees. However, a number of RackTables functions specifically relies upon
1365 // consistent relations between the tags (presented either as a complete forest
1366 // structure or just as each node's path from the root), hence an early validation
1367 // step is required in the PHP code to implement the constraints in full.
1368 //
1369 // The function below implements this step. For every node on the input list that
1370 // belongs to a forest of rooted trees it sets the 'trace' key to the sequence of
1371 // node IDs that leads from tree root up to (but not including) the node. As an
1372 // edge case, for each root node it sets this sequence to an empty list. For any
1373 // nodes not in the forest (i.e., those that form any graph cycle or descend from
1374 // such a cycle) it leaves 'trace' unset.
1375 function addTraceToNodes ($nodelist)
1376 {
1377 foreach ($nodelist as $nodeid => $node)
1378 {
1379 $trace = array();
1380 $parentid = $node['parent_id'];
1381 while ($parentid != NULL)
1382 {
1383 if (! isset ($nodelist[$parentid]))
1384 {
1385 // bad parent_id
1386 $trace = NULL;
1387 break;
1388 }
1389
1390 // check for cycles every 10 steps
1391 if (0 == (count ($trace) % 10) && in_array ($parentid, $trace))
1392 {
1393 // cycle detected
1394 $trace = NULL;
1395 break;
1396 }
1397 array_unshift ($trace, $parentid);
1398 $parentid = $nodelist[$parentid]['parent_id'];
1399 }
1400 if (isset ($trace))
1401 $nodelist[$nodeid]['trace'] = $trace;
1402 }
1403 return $nodelist;
1404 }
1405
1406 function treeItemCmp ($a, $b)
1407 {
1408 return $a['__tree_index'] - $b['__tree_index'];
1409 }
1410
1411 function getTagTree()
1412 {
1413 return treeFromList (getTagUsage());
1414 }
1415
1416 // Build a tree from the item list and return it. Input and output data is
1417 // indexed by item id (nested items in output are recursively stored in 'kids'
1418 // key, which is in turn indexed by id. Functions that are ready to handle
1419 // tree collapsion/expansion themselves may request non-zero threshold value
1420 // for smaller resulting tree.
1421 // FIXME: The 2nd argument to this function seems not to be used any more.
1422 // FIXME: The structure this function returns is a forest of rooted trees
1423 // despite the terminology it used to use.
1424 function treeFromList ($nodelist, $threshold = 0)
1425 {
1426 $tree = array();
1427
1428 // Preserve original ordering in __tree_index.
1429 $ti = 0;
1430 foreach (array_keys ($nodelist) as $key)
1431 {
1432 $nodelist[$key]['__tree_index'] = $ti++;
1433 $nodelist[$key]['kidc'] = 0;
1434 $nodelist[$key]['kids'] = array();
1435 }
1436
1437 $done_ids = array();
1438 do
1439 {
1440 $nextpass = FALSE;
1441 foreach (array_keys ($nodelist) as $nodeid)
1442 {
1443 $node = $nodelist[$nodeid];
1444 // Skip any irrelevant nodes early as they will fail the checks below anyway.
1445 if (! array_key_exists ('trace', $node))
1446 continue;
1447 $parentid = $node['parent_id'];
1448 // Moving a node from the input list to the output tree potentially enables more
1449 // nodes to make it from the list to the same tree as well, hence in this case make
1450 // another full round after the current one.
1451
1452 if ($parentid == NULL) // A root node?
1453 {
1454 $tree[$nodeid] = $node;
1455 unset ($nodelist[$nodeid]);
1456 $done_ids[] = $nodeid;
1457 $nextpass = TRUE;
1458 }
1459 elseif (in_array ($parentid, $done_ids)) // Has a direct parent node already on the tree?
1460 {
1461 // Being here implies the current node's trace is at least one element long.
1462 pokeNode ($tree, $node['trace'], $nodeid, $node, $threshold);
1463 unset ($nodelist[$nodeid]);
1464 $done_ids[] = $nodeid;
1465 $nextpass = TRUE;
1466 }
1467 }
1468 }
1469 while ($nextpass);
1470 sortTree ($tree, 'treeItemCmp'); // sort the resulting tree by the order in original list
1471 return $tree;
1472 }
1473
1474 // Return those tags that belong to the full list of tags but don't belong
1475 // to the forest of rooted trees as found by addTraceToNodes().
1476 function getInvalidNodes ($nodelist)
1477 {
1478 $ret = array();
1479 foreach ($nodelist as $node_id => $node)
1480 if (! array_key_exists ('trace', $node))
1481 $ret[$node_id] = $node;
1482 return $ret;
1483 }
1484
1485 // Throw an exception unless it is OK to assign the given parent ID
1486 // to the node with the given ID.
1487 function assertValidParentId ($nodelist, $node_id, $parent_id)
1488 {
1489 if ($parent_id == 0)
1490 return;
1491 if ($parent_id == $node_id)
1492 throw new InvalidArgException ('parent_id', $parent_id, 'must be different from the tag ID');
1493 if (! array_key_exists ($parent_id, $nodelist))
1494 throw new InvalidArgException ('parent_id', $parent_id, 'must refer to an existing tag');
1495 if (! array_key_exists ('trace', $nodelist[$parent_id]))
1496 throw new InvalidArgException ('parent_id', $parent_id, 'would add to an existing graph cycle');
1497 if (in_array ($node_id, $nodelist[$parent_id]['trace']))
1498 throw new InvalidArgException ('parent_id', $parent_id, 'would create a new graph cycle');
1499 }
1500
1501 // Given an existing node ID filter a list of traced nodes and silently skip
1502 // the nodes that are not valid parent node options. Filtering criteria are
1503 // effectively the same as in the function above but use a simpler expression.
1504 function getParentNodeOptionsExisting ($nodelist, $textfield, $node_id)
1505 {
1506 $ret = array (0 => '-- NONE --');
1507 foreach ($nodelist as $key => $each)
1508 if
1509 (
1510 $key != $node_id &&
1511 array_key_exists ('trace', $each) &&
1512 ! in_array ($node_id, $each['trace'])
1513 )
1514 $ret[$key] = $each[$textfield];
1515 return $ret;
1516 }
1517
1518 // Idem, but for a new node, which doesn't yet exist, or a node that is based
1519 // on a circular reference. The condition is even simpler in this case.
1520 function getParentNodeOptionsNew ($nodelist, $textfield)
1521 {
1522 $ret = array (0 => '-- NONE --');
1523 foreach ($nodelist as $key => $each)
1524 if (array_key_exists ('trace', $each))
1525 $ret[$key] = $each[$textfield];
1526 return $ret;
1527 }
1528
1529 // removes implicit tags from ['etags'] array and fills ['itags'] array
1530 // Replaces call sequence "getExplicitTagsOnly, getImplicitTags"
1531 function sortEntityTags (&$cell)
1532 {
1533 global $taglist;
1534 if (! is_array ($cell['etags']))
1535 throw new InvalidArgException ('cell[etags]', $cell['etags']);
1536 $cell['itags'] = array();
1537 foreach ($cell['etags'] as $tag_id => $taginfo)
1538 foreach ($taglist[$tag_id]['trace'] as $parent_id)
1539 {
1540 $cell['itags'][$parent_id] = $taglist[$parent_id];
1541 unset ($cell['etags'][$parent_id]);
1542 }
1543 }
1544
1545 // Return the list of missing implicit tags.
1546 function getImplicitTags ($oldtags)
1547 {
1548 global $taglist;
1549 $tmp = array();
1550 foreach ($oldtags as $taginfo)
1551 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']);
1552 // don't call array_unique here, it is in the function we will call now
1553 return buildTagChainFromIds ($tmp);
1554 }
1555
1556 // Minimize the chain: exclude all implicit tags and return the result.
1557 // This function makes use of an external cache with a miss/hit ratio
1558 // about 3/7 (ticket:255).
1559 function getExplicitTagsOnly ($chain)
1560 {
1561 global $taglist, $tagRelCache;
1562 $ret = array();
1563 foreach (array_keys ($chain) as $keyA) // check each A
1564 {
1565 $tagidA = $chain[$keyA]['id'];
1566 // do not include A in result, if A is seen on the trace of any B!=A
1567 foreach (array_keys ($chain) as $keyB)
1568 {
1569 $tagidB = $chain[$keyB]['id'];
1570 if ($tagidA == $tagidB)
1571 continue;
1572 if (!isset ($tagRelCache[$tagidA][$tagidB]))
1573 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']);
1574 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B
1575 continue 2; // skip this A
1576 }
1577 $ret[] = $chain[$keyA];
1578 }
1579 return $ret;
1580 }
1581
1582 // Check, if the given tag is present on the chain (will only work
1583 // for regular tags with tag ID set.
1584 function tagOnChain ($taginfo, $tagchain)
1585 {
1586 if (!isset ($taginfo['id']))
1587 return FALSE;
1588 return NULL !== scanArrayForItem ($tagchain, 'id', $taginfo['id']);
1589 }
1590
1591 function tagNameOnChain ($tagname, $tagchain)
1592 {
1593 return NULL !== scanArrayForItem ($tagchain, 'tag', $tagname);
1594 }
1595
1596 // Return TRUE, if two tags chains differ (order of tags doesn't matter).
1597 // Assume, that neither of the lists contains duplicates.
1598 // FIXME: a faster, than O(x^2) method is possible for this calculation.
1599 function tagChainCmp ($chain1, $chain2)
1600 {
1601 if (count ($chain1) != count ($chain2))
1602 return TRUE;
1603 foreach ($chain1 as $taginfo1)
1604 if (!tagOnChain ($taginfo1, $chain2))
1605 return TRUE;
1606 return FALSE;
1607 }
1608
1609 // returns an array of tag ids that have $tagid as its parent (all levels)
1610 function getTagDescendents ($tagid)
1611 {
1612 global $taglist;
1613 $ret = array();
1614 foreach ($taglist as $id => $taginfo)
1615 if (array_key_exists ('trace', $taginfo) && in_array ($tagid, $taginfo['trace']))
1616 $ret[] = $id;
1617 return $ret;
1618 }
1619
1620 function redirectIfNecessary ()
1621 {
1622 global
1623 $trigger,
1624 $pageno,
1625 $tabno;
1626 startSession();
1627 if
1628 (
1629 ! isset ($_REQUEST['tab']) &&
1630 isset ($_SESSION['RTLT'][$pageno]) &&
1631 getConfigVar ('SHOW_LAST_TAB') == 'yes' &&
1632 permitted ($pageno, $_SESSION['RTLT'][$pageno]['tabname']) &&
1633 time() - $_SESSION['RTLT'][$pageno]['time'] <= TAB_REMEMBER_TIMEOUT
1634 )
1635 redirectUser (buildRedirectURL ($pageno, $_SESSION['RTLT'][$pageno]['tabname']));
1636
1637 // Fall back to default when a trigger was OK about a tab when generating the previous page
1638 // but isn't OK anymore when generating the current page and the tab is the requested tab.
1639 if
1640 (
1641 isset ($trigger[$pageno][$tabno]) &&
1642 '' == call_user_func ($trigger[$pageno][$tabno])
1643 )
1644 {
1645 $_SESSION['RTLT'][$pageno]['dont_remember'] = 1;
1646 redirectUser (buildRedirectURL ($pageno, 'default'));
1647 }
1648 if (is_array (@$_SESSION['RTLT'][$pageno]) && isset ($_SESSION['RTLT'][$pageno]['dont_remember']))
1649 unset ($_SESSION['RTLT'][$pageno]['dont_remember']);
1650 // store the last visited tab name
1651 if (isset ($_REQUEST['tab']))
1652 $_SESSION['RTLT'][$pageno] = array ('tabname' => $tabno, 'time' => time());
1653 session_commit(); // if we are continuing to run, unlock session data
1654 }
1655
1656 function prepareNavigation()
1657 {
1658 global $pageno, $tabno;
1659 $pageno = array_fetch ($_REQUEST, 'page', 'index');
1660 $tabno = array_fetch ($_REQUEST, 'tab', 'default');
1661 }
1662
1663 function fixContext ($target = NULL)
1664 {
1665 global
1666 $pageno,
1667 $auto_tags,
1668 $expl_tags,
1669 $impl_tags,
1670 $target_given_tags,
1671 $user_given_tags,
1672 $etype_by_pageno;
1673
1674 if ($target !== NULL)
1675 {
1676 $target_given_tags = $target['etags'];
1677 // Don't reset autochain, because auth procedures could push stuff there in.
1678 // Another important point is to ignore 'user' realm, so we don't infuse effective
1679 // context with autotags of the displayed account.
1680 if ($target['realm'] != 'user')
1681 $auto_tags = array_merge ($auto_tags, $target['atags']);
1682 }
1683 elseif (array_key_exists ($pageno, $etype_by_pageno))
1684 {
1685 // Each page listed in the map above requires one uint argument.
1686 $target = spotEntity ($etype_by_pageno[$pageno], getBypassValue());
1687 $target_given_tags = $target['etags'];
1688 if ($target['realm'] != 'user')
1689 $auto_tags = array_merge ($auto_tags, $target['atags']);
1690 }
1691 elseif ($pageno == 'ipaddress' && $net = spotNetworkByIP (getBypassValue()))
1692 {
1693 // IP addresses inherit context tags from their parent networks
1694 $target_given_tags = $net['etags'];
1695 $auto_tags = array_merge ($auto_tags, $net['atags']);
1696 }
1697 // Explicit and implicit chains should be normally empty at this point, so
1698 // overwrite the contents anyway.
1699 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1700 $impl_tags = getImplicitTags ($expl_tags);
1701 }
1702
1703 # Merge e/i/a-tags of the given cell structures into current context, when
1704 # these aren't there yet.
1705 function spreadContext ($extracell)
1706 {
1707 global
1708 $auto_tags,
1709 $expl_tags,
1710 $impl_tags,
1711 $target_given_tags,
1712 $user_given_tags;
1713 foreach ($extracell['atags'] as $taginfo)
1714 if (! tagNameOnChain ($taginfo['tag'], $auto_tags))
1715 $auto_tags[] = $taginfo;
1716 $target_given_tags = mergeTagChains ($target_given_tags, $extracell['etags']);
1717 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1718 $impl_tags = getImplicitTags ($expl_tags);
1719 }
1720
1721 # return a structure suitable for feeding into restoreContext()
1722 function getContext()
1723 {
1724 global
1725 $auto_tags,
1726 $expl_tags,
1727 $impl_tags,
1728 $target_given_tags;
1729 return array
1730 (
1731 'auto_tags' => $auto_tags,
1732 'expl_tags' => $expl_tags,
1733 'impl_tags' => $impl_tags,
1734 'target_given_tags' => $target_given_tags,
1735 );
1736 }
1737
1738 function restoreContext ($ctx)
1739 {
1740 global
1741 $auto_tags,
1742 $expl_tags,
1743 $impl_tags,
1744 $target_given_tags;
1745 $auto_tags = $ctx['auto_tags'];
1746 $expl_tags = $ctx['expl_tags'];
1747 $impl_tags = $ctx['impl_tags'];
1748 $target_given_tags = $ctx['target_given_tags'];
1749 }
1750
1751 // Take a list of user-supplied tag IDs to build a list of valid taginfo
1752 // records indexed by tag IDs (tag chain).
1753 function buildTagChainFromIds ($tagidlist)
1754 {
1755 global $taglist;
1756 $ret = array();
1757 foreach (array_unique ($tagidlist) as $tag_id)
1758 if (isset ($taglist[$tag_id]))
1759 $ret[] = $taglist[$tag_id];
1760 return $ret;
1761 }
1762
1763 function buildTagIdsFromChain ($tagchain)
1764 {
1765 $ret = array();
1766 foreach ($tagchain as $taginfo)
1767 $ret[] = $taginfo['id'];
1768 return array_unique ($ret);
1769 }
1770
1771 // Process a given tag tree and return only meaningful branches. The resulting
1772 // (sub)tree will have refcnt leaves on every last branch.
1773 function getObjectiveTagTree ($tree, $realm, $preselect)
1774 {
1775 $self = __FUNCTION__;
1776 $ret = array();
1777 foreach ($tree as $taginfo)
1778 {
1779 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1780 // If the current node addresses something, add it to the result
1781 // regardless of how many sub-nodes it features.
1782 if
1783 (
1784 isset ($taginfo['refcnt'][$realm]) ||
1785 count ($subsearch) > 1 ||
1786 in_array ($taginfo['id'], $preselect)
1787 )
1788 $ret[] = array
1789 (
1790 'id' => $taginfo['id'],
1791 'tag' => $taginfo['tag'],
1792 'parent_id' => $taginfo['parent_id'],
1793 'refcnt' => $taginfo['refcnt'],
1794 'kids' => $subsearch
1795 );
1796 else
1797 $ret = array_merge ($ret, $subsearch);
1798 }
1799 return $ret;
1800 }
1801
1802 // Preprocess tag tree to get only tags that can effectively reduce given filter result,
1803 // then pass shrinked tag tree to getObjectiveTagTree and return its result.
1804 // This makes sense only if andor mode is 'and', otherwise function does not modify tree.
1805 // 'Given filter' is a pair of $entity_list(filter result) and $preselect(filter data).
1806 // 'Effectively' means reduce to non-empty result.
1807 function getShrinkedTagTree ($entity_list, $realm, $preselect)
1808 {
1809 $tagtree = getTagTree();
1810 if ($preselect['andor'] != 'and' || empty($entity_list) && $preselect['is_empty'])
1811 return getObjectiveTagTree($tagtree, $realm, $preselect['tagidlist']);
1812
1813 $used_tags = array(); //associative, keys - tag ids, values - taginfos
1814 foreach ($entity_list as $entity)
1815 {
1816 foreach ($entity['etags'] as $etag)
1817 if (! array_key_exists($etag['id'], $used_tags))
1818 $used_tags[$etag['id']] = 1;
1819 else
1820 $used_tags[$etag['id']]++;
1821
1822 foreach ($entity['itags'] as $itag)
1823 if (! array_key_exists($itag['id'], $used_tags))
1824 $used_tags[$itag['id']] = 0;
1825 }
1826
1827 $shrinked_tree = shrinkSubtree($tagtree, $used_tags, $preselect, $realm);
1828 return getObjectiveTagTree($shrinked_tree, $realm, $preselect['tagidlist']);
1829 }
1830
1831 // deletes item from tag subtree unless it exists in $used_tags and not preselected
1832 function shrinkSubtree ($tree, $used_tags, $preselect, $realm)
1833 {
1834 $self = __FUNCTION__;
1835
1836 foreach ($tree as $i => &$item)
1837 {
1838 $item['kids'] = $self($item['kids'], $used_tags, $preselect, $realm);
1839 $item['kidc'] = count($item['kids']);
1840 if
1841 (
1842 ! array_key_exists($item['id'], $used_tags) &&
1843 ! in_array($item['id'], $preselect['tagidlist']) &&
1844 ! $item['kidc']
1845 )
1846 unset($tree[$i]);
1847 else
1848 {
1849 if (isset ($used_tags[$item['id']]) && $used_tags[$item['id']])
1850 $item['refcnt'][$realm] = $used_tags[$item['id']];
1851 else
1852 unset($item['refcnt'][$realm]);
1853 }
1854 }
1855 return $tree;
1856 }
1857
1858 // Get taginfo record by tag name, return NULL, if record doesn't exist.
1859 function getTagByName ($tag_name)
1860 {
1861 global $taglist;
1862 static $cache = NULL;
1863 if (! isset ($cache))
1864 {
1865 $cache = array();
1866 foreach ($taglist as $key => $taginfo)
1867 $cache[$taginfo['tag']] = $taginfo;
1868 }
1869 return array_fetch($cache, $tag_name, NULL);
1870 }
1871
1872 // Merge two chains, filtering dupes out. Return the resulting superset.
1873 function mergeTagChains ($chainA, $chainB)
1874 {
1875 // $ret = $chainA;
1876 // Reindex by tag id in any case.
1877 $ret = array();
1878 foreach ($chainA as $tag)
1879 $ret[$tag['id']] = $tag;
1880 foreach ($chainB as $tag)
1881 if (!isset ($ret[$tag['id']]))
1882 $ret[$tag['id']] = $tag;
1883 return $ret;
1884 }
1885
1886 # Return a list consisting of tag ID of the given tree node and IDs of all
1887 # nodes it contains.
1888 # This is an earlier and more generic variety of getTagDescendents().
1889 function getTagIDListForNode ($treenode)
1890 {
1891 $self = __FUNCTION__;
1892 $ret = array ($treenode['id']);
1893 foreach ($treenode['kids'] as $item)
1894 $ret = array_merge ($ret, $self ($item));
1895 return $ret;
1896 }
1897
1898 function applyCellFilter ($realm, $cellfilter, $parent_id = NULL)
1899 {
1900 if ($res = callHook ('applyCellFilter_hook', $realm, $cellfilter, $parent_id))
1901 return $res;
1902 return filterCellList (listCells ($realm, $parent_id), $cellfilter['expression']);
1903 }
1904
1905 function getCellFilter ()
1906 {
1907 global $sic;
1908 global $pageno;
1909 $andor_used = FALSE;
1910 startSession();
1911 // 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.
1912 if (isset($_REQUEST['andor']))
1913 $andor_used = TRUE;
1914 if ($andor_used || array_key_exists ('clear-cf', $_REQUEST))
1915 unset($_SESSION[$pageno]); // delete saved filter
1916
1917 // otherwise inject saved filter to the $_REQUEST and $sic vars
1918 elseif (isset ($_SESSION[$pageno]['filter']) && is_array ($_SESSION[$pageno]['filter']) && getConfigVar ('STATIC_FILTER') == 'yes')
1919 foreach (array('andor', 'cfe', 'cft[]', 'cfp[]', 'nft[]', 'nfp[]') as $param)
1920 {
1921 $param = str_replace ('[]', '', $param, $is_array);
1922 if (! isset ($_REQUEST[$param]) && isset ($_SESSION[$pageno]['filter'][$param]) && (! $is_array || is_array ($_SESSION[$pageno]['filter'][$param])))
1923 {
1924 $_REQUEST[$param] = $_SESSION[$pageno]['filter'][$param];
1925 if (! $is_array)
1926 $sic[$param] = $_REQUEST[$param];
1927 }
1928 }
1929
1930 $ret = array
1931 (
1932 'tagidlist' => array(),
1933 'tnamelist' => array(),
1934 'pnamelist' => array(),
1935 'negatedlist' => array(),
1936 'andor' => '',
1937 'text' => '',
1938 'extratext' => '',
1939 'expression' => array(),
1940 'urlextra' => '', // Just put text here and let makeHref call urlencode().
1941 'is_empty' => TRUE,
1942 );
1943 switch (TRUE)
1944 {
1945 case (!isset ($_REQUEST['andor'])):
1946 $andor = getConfigVar ('FILTER_DEFAULT_ANDOR');
1947 break;
1948 case ($_REQUEST['andor'] == 'and'):
1949 case ($_REQUEST['andor'] == 'or'):
1950 $_SESSION[$pageno]['filter']['andor'] = $_REQUEST['andor'];
1951 $ret['andor'] = $andor = $_REQUEST['andor'];
1952 break;
1953 default:
1954 showWarning ('Invalid and/or switch value in submitted form');
1955 session_commit();
1956 return NULL;
1957 }
1958 // Both tags and predicates that don't exist, should be
1959 // handled somehow. Discard them silently for now.
1960 global $taglist, $pTable;
1961 foreach (array ('cft', 'cfp', 'nft', 'nfp') as $param)
1962 if (isset ($_REQUEST[$param]) && is_array ($_REQUEST[$param]))
1963 {
1964 $_SESSION[$pageno]['filter'][$param] = $_REQUEST[$param];
1965 foreach ($_REQUEST[$param] as $req_key)
1966 {
1967 if (strpos ($param, 'ft') !== FALSE)
1968 {
1969 // param is a taglist
1970 if (! isset ($taglist[$req_key]))
1971 continue;
1972 $ret['tagidlist'][] = $req_key;
1973 $ret['tnamelist'][] = $taglist[$req_key]['tag'];
1974 $text = '{' . $taglist[$req_key]['tag'] . '}';
1975 }
1976 else
1977 {
1978 // param is a predicate list
1979 if (! isset ($pTable[$req_key]))
1980 continue;
1981 $ret['pnamelist'][] = $req_key;
1982 $text = '[' . $req_key . ']';
1983 }
1984 if (strpos ($param, 'nf') === 0)
1985 {
1986 $text = "not $text";
1987 $ret['negatedlist'][] = $req_key;
1988 }
1989 if (! empty ($ret['text']))
1990 {
1991 $andor_used = TRUE;
1992 $ret['text'] .= " $andor ";
1993 }
1994 $ret['text'] .= $text;
1995 $ret['urlextra'] .= '&' . $param . '[]=' . $req_key;
1996 }
1997 }
1998 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1999 if (isset ($sic['cfe']))
2000 {
2001 $_SESSION[$pageno]['filter']['cfe'] = $sic['cfe'];
2002 $ret['extratext'] = trim ($sic['cfe']);
2003 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
2004 }
2005 $finaltext = array();
2006 if ($ret['text'] != '')
2007 $finaltext[] = '(' . $ret['text'] . ')';
2008 if ($ret['extratext'] != '')
2009 $finaltext[] = '(' . $ret['extratext'] . ')';
2010 $andor_used = $andor_used || (count($finaltext) > 1);
2011 $finaltext = implode (' ' . $andor . ' ', $finaltext);
2012 if ($finaltext != '')
2013 {
2014 $ret['is_empty'] = FALSE;
2015 $ret['expression'] = compileExpression ($finaltext);
2016 // It's not quite fair enough to put the blame of the whole text onto
2017 // non-empty "extra" portion of it, but it's the only user-generated portion
2018 // of it, thus the most probable cause of parse error.
2019 if ($ret['extratext'] != '')
2020 $ret['extraclass'] = $ret['expression'] ? 'validation-success' : 'validation-error';
2021 }
2022 if (! $andor_used)
2023 $ret['andor'] = getConfigVar ('FILTER_DEFAULT_ANDOR');
2024 else
2025 $ret['urlextra'] .= '&andor=' . $ret['andor'];
2026 session_commit();
2027 return $ret;
2028 }
2029
2030 function buildRedirectURL ($nextpage = NULL, $nexttab = NULL, $moreArgs = array())
2031 {
2032 $params = array();
2033 if ($nextpage !== NULL)
2034 $params['page'] = $nextpage;
2035 if ($nexttab !== NULL)
2036 $params['tab'] = $nexttab;
2037 $params = makePageParams ($params + $moreArgs);
2038 unset ($params['module']); // 'interface' module is the default
2039 return makeHref ($params);
2040 }
2041
2042 // store the accumulated message list into he $SESSION array to display them later
2043 function backupLogMessages()
2044 {
2045 global $log_messages;
2046 if (! empty ($log_messages))
2047 {
2048 startSession();
2049 $_SESSION['log'] = $log_messages;
2050 session_commit();
2051 }
2052 }
2053
2054 function redirectUser ($url)
2055 {
2056 backupLogMessages();
2057 header ("Location: " . $url);
2058 die;
2059 }
2060
2061 function getRackImageWidth ()
2062 {
2063 global $rtwidth;
2064 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
2065 }
2066
2067 function getRackImageHeight ($units)
2068 {
2069 return 3 + 3 + $units * 2;
2070 }
2071
2072 // Indicate occupation state of each IP address: none, ordinary or problematic.
2073 // Returns number of marked up (busy) addresses
2074 function markupIPAddrList (&$addrlist)
2075 {
2076 $used = 0;
2077 foreach (array_keys ($addrlist) as $ip_bin)
2078 {
2079 $refc = array
2080 (
2081 'shared' => 0, // virtual
2082 'virtual' => 0, // loopback
2083 'regular' => 0, // connected host
2084 'router' => 0, // connected gateway
2085 'point2point' => 0,
2086 );
2087 $nallocs = 0;
2088 foreach ($addrlist[$ip_bin]['allocs'] as $a)
2089 {
2090 $refc[$a['type']]++;
2091 $nallocs++;
2092 }
2093 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
2094 if ($nallocs > 1 && $nallocs != $refc['shared'] || $nallocs && $nreserved)
2095 {
2096 $addrlist[$ip_bin]['class'] = 'trerror';
2097 ++$used;
2098 }
2099 elseif (! isIPAddressEmpty ($addrlist[$ip_bin], array ('name', 'comment', 'inpf', 'outpf'))) // these fields don't trigger the 'busy' status
2100 {
2101 $addrlist[$ip_bin]['class'] = 'trbusy';
2102 ++$used;
2103 }
2104 else
2105 $addrlist[$ip_bin]['class'] = '';
2106 }
2107 return $used;
2108 }
2109
2110 function findNetRouters ($net)
2111 {
2112 if (isset ($net['own_addrlist']))
2113 $own_addrlist = $net['own_addrlist'];
2114 else
2115 {
2116 // do not call loadIPAddrList, it is expensive.
2117 // instead, do our own DB scan only for router allocations
2118 $rtrlist = scanIPNet ($net, IPSCAN_DO_ALLOCS | IPSCAN_RTR_ONLY);
2119 $own_addrlist = filterOwnAddrList ($net, $rtrlist);
2120 }
2121 return findRouters ($own_addrlist);
2122 }
2123
2124 // Scan the given address list (returned by scanIPv4Space/scanIPv6Space) and return a list of all routers found.
2125 function findRouters ($addrlist)
2126 {
2127 $ret = array();
2128 foreach ($addrlist as $addr)
2129 foreach ($addr['allocs'] as $alloc)
2130 if ($alloc['type'] == 'router')
2131 $ret[] = array
2132 (
2133 'id' => $alloc['object_id'],
2134 'iface' => $alloc['name'],
2135 'dname' => $alloc['object_name'],
2136 'ip_bin' => $addr['ip_bin'],
2137 );
2138 return $ret;
2139 }
2140
2141 function numSign ($x)
2142 {
2143 if ($x < 0)
2144 return -1;
2145 if ($x > 0)
2146 return 1;
2147 return 0;
2148 }
2149
2150 function numCompare ($a, $b)
2151 {
2152 return numSign ($a - $b);
2153 }
2154
2155 // compare binary IPs (IPv4 are less than IPv6)
2156 // valid return values are: 1, 0, -1
2157 function IPCmp ($ip_binA, $ip_binB)
2158 {
2159 if (strlen ($ip_binA) !== strlen ($ip_binB))
2160 return numCompare (strlen ($ip_binA), strlen ($ip_binB));
2161 return numSign (strcmp ($ip_binA, $ip_binB));
2162 }
2163
2164 // Binary compare the first addresses of each pair
2165 // If the first addresses of the compared pairs are equal, compare last addresses in reverse order
2166 // valid return values are: 1, 0, -1
2167 function IPSpaceCmp ($pairA, $pairB)
2168 {
2169 $first = IPCmp ($pairA['first'], $pairB['first']);
2170 return $first ? $first : IPCmp ($pairB['last'], $pairA['last']);
2171 }
2172
2173 // filter netsted ip pairs
2174 function reduceIPPairList ($pairlist)
2175 {
2176 $ret = array();
2177 $left = $pairlist;
2178 usort ($left, 'IPSpaceCmp');
2179 while ($left)
2180 {
2181 $agg = array_shift ($left);
2182 $ret[] = $agg;
2183 foreach ($left as $id => $pair)
2184 if ($pair['first'] >= $agg['first'] && $pair['last'] <= $agg['last'])
2185 unset ($left[$id]);
2186 }
2187 return $ret;
2188 }
2189
2190 // Compare networks. When sorting a tree, the records on the list will have
2191 // distinct base IP addresses.
2192 // valid return values are: 2, 1, 0, -1, -2
2193 // -2, 2 have special meaning: $netA includes $netB or vice versa, respecively
2194 // "The comparison function must return an integer less than, equal to, or greater
2195 // than zero if the first argument is considered to be respectively less than,
2196 // equal to, or greater than the second." (c) PHP manual
2197 function IPNetworkCmp ($netA, $netB)
2198 {
2199 $ret = IPCmp ($netA['ip_bin'], $netB['ip_bin']);
2200 if ($ret == 0)
2201 $ret = $netA['mask'] < $netB['mask'] ? -1 : ($netA['mask'] > $netB['mask'] ? 1 : 0);
2202 if ($ret == -1 && $netA['ip_bin'] === ($netB['ip_bin'] & $netA['mask_bin']))
2203 $ret = -2;
2204 if ($ret == 1 && $netB['ip_bin'] === ($netA['ip_bin'] & $netB['mask_bin']))
2205 $ret = 2;
2206 return $ret;
2207 }
2208
2209 function IPNetContainsOrEqual ($netA, $netB)
2210 {
2211 $res = IPNetworkCmp ($netA, $netB);
2212 return ($res == -2 || $res == 0);
2213 }
2214
2215 function IPNetContains ($netA, $netB)
2216 {
2217 return (-2 == IPNetworkCmp ($netA, $netB));
2218 }
2219
2220 function IPNetsIntersect ($netA, $netB)
2221 {
2222 return ($netA['ip_bin'] & $netB['mask_bin']) === $netB['ip_bin'] ||
2223 ($netB['ip_bin'] & $netA['mask_bin']) === $netA['ip_bin'];
2224 }
2225
2226 function ip_in_range ($ip_bin, $range)
2227 {
2228 return ($ip_bin & $range['mask_bin']) === $range['ip_bin'];
2229 }
2230
2231 // Sort each level of the tree independently using the given compare function.
2232 function sortTree (&$tree, $cmpfunc = '')
2233 {
2234 $self = __FUNCTION__;
2235 if (! is_callable ($cmpfunc))
2236 throw new InvalidArgException ('cmpfunc', $cmpfunc, 'is not a callable function');
2237 usort ($tree, $cmpfunc);
2238 foreach (array_keys ($tree) as $tagid)
2239 $self ($tree[$tagid]['kids'], $cmpfunc);
2240 }
2241
2242 function iptree_fill (&$netdata)
2243 {
2244 if (! isset ($netdata['kids']) || ! count ($netdata['kids']))
2245 return;
2246
2247 foreach ($netdata['spare_ranges'] as $mask => $list)
2248 {
2249 $spare_mask = $mask;
2250 // align spare IPv6 nets by nibble boundary
2251 if (strlen ($netdata['ip_bin']) == 16 && $mask % 4)
2252 $spare_mask = $mask + 4 - ($mask % 4);
2253 foreach ($list as $ip_bin)
2254 foreach (splitNetworkByMask (constructIPRange ($ip_bin, $mask), $spare_mask) as $spare)
2255 $netdata['kids'][] = $spare + array('kids' => array(), 'kidc' => 0, 'name' => '');
2256 }
2257
2258 if (count ($netdata['kids']) != $netdata['kidc'])
2259 {
2260 $netdata['kidc'] = count ($netdata['kids']);
2261 usort ($netdata['kids'], 'IPNetworkCmp');
2262 }
2263 }
2264
2265 // returns TRUE if inet_ntop and inet_pton functions exist
2266 function is_inet_avail()
2267 {
2268 static $ret = NULL;
2269 if (! isset ($ret))
2270 $ret = is_callable ('inet_pton') && is_callable ('inet_ntop');
2271 return $ret;
2272 }
2273
2274 // returns TRUE if inet_ntop and inet_pton functions exist and support IPv6
2275 function is_inet6_avail()
2276 {
2277 static $ret = NULL;
2278 if (! isset ($ret))
2279 $ret = is_inet_avail() && defined ('AF_INET6');
2280 return $ret;
2281 }
2282
2283 function ip_format ($ip_bin)
2284 {
2285 switch (strlen ($ip_bin))
2286 {
2287 case 4: return ip4_format ($ip_bin);
2288 case 16: return ip6_format ($ip_bin);
2289 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2290 }
2291 }
2292
2293 function ip4_format ($ip_bin)
2294 {
2295 if (4 == strlen ($ip_bin))
2296 {
2297 if (is_inet_avail())
2298 {
2299 $ret = @inet_ntop ($ip_bin);
2300 if ($ret !== FALSE)
2301 return $ret;
2302 }
2303 else
2304 return implode ('.', unpack ('C*', $ip_bin));
2305 }
2306 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2307 }
2308
2309 function ip6_format ($ip_bin)
2310 {
2311 do {
2312 if (16 != strlen ($ip_bin))
2313 break;
2314
2315 if (is_inet6_avail())
2316 {
2317 $ret = @inet_ntop ($ip_bin);
2318 if ($ret !== FALSE)
2319 return $ret;
2320 break;
2321 }
2322
2323 // maybe this is IPv6-to-IPv4 address?
2324 if (substr ($ip_bin, 0, 12) == "\0\0\0\0\0\0\0\0\0\0\xff\xff")
2325 return '::ffff:' . implode ('.', unpack ('C*', substr ($ip_bin, 12, 4)));
2326
2327 $result = array();
2328 $hole_index = NULL;
2329 $max_hole_index = NULL;
2330 $hole_length = 0;
2331 $max_hole_length = 0;
2332
2333 for ($i = 0; $i < 8; $i++)
2334 {
2335 $unpacked = unpack ('n', substr ($ip_bin, $i * 2, 2));
2336 $value = array_shift ($unpacked);
2337 $result[] = dechex ($value & 0xffff);
2338 if ($value != 0)
2339 {
2340 unset ($hole_index);
2341 $hole_length = 0;
2342 }
2343 else
2344 {
2345 if (! isset ($hole_index))
2346 $hole_index = $i;
2347 if (++$hole_length >= $max_hole_length)
2348 {
2349 $max_hole_index = $hole_index;
2350 $max_hole_length = $hole_length;
2351 }
2352 }
2353 }
2354 if (isset ($max_hole_index))
2355 {
2356 array_splice ($result, $max_hole_index, $max_hole_length, array (''));
2357 if ($max_hole_index == 0 && $max_hole_length == 8)
2358 return '::';
2359 elseif ($max_hole_index == 0)
2360 return ':' . implode (':', $result);
2361 elseif ($max_hole_index + $max_hole_length == 8)
2362 return implode (':', $result) . ':';
2363 }
2364 return implode (':', $result);
2365 } while (FALSE);
2366
2367 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2368 }
2369
2370 function ip_parse ($ip)
2371 {
2372 try
2373 {
2374 if (FALSE !== strpos ($ip, ':'))
2375 return ip6_parse ($ip);
2376 else
2377 return ip4_parse ($ip);
2378 }
2379 catch (InvalidArgException $e)
2380 {
2381 // re-throw with general error message, without specifying an IP family
2382 throw new InvalidArgException ('ip', $ip, "Invalid IP address");
2383 }
2384 }
2385
2386 function ip4_parse ($ip)
2387 {
2388 if (is_inet_avail())
2389 {
2390 if (FALSE !== ($ret = @inet_pton ($ip)) && strlen ($ret) == 4)
2391 return $ret;
2392 }
2393 elseif (FALSE !== ($int = ip2long ($ip)))
2394 return pack ('N', $int);
2395
2396 throw new InvalidArgException ('ip', $ip, "Invalid IPv4 address");
2397 }
2398
2399 // returns 16-byte string ip_bin
2400 // throws exception if unable to parse
2401 function ip6_parse ($ip)
2402 {
2403 do {
2404 if (is_inet6_avail())
2405 {
2406 if (FALSE !== ($ret = @inet_pton ($ip)) && strlen ($ret) == 16)
2407 return $ret;
2408 break;
2409 }
2410
2411 if (empty ($ip))
2412 break;
2413
2414 $result = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // 16 bytes
2415 // remove one of double beginning/tailing colons
2416 if (substr ($ip, 0, 2) == '::')
2417 $ip = substr ($ip, 1);
2418 elseif (substr ($ip, -2, 2) == '::')
2419 $ip = substr ($ip, 0, strlen ($ip) - 1);
2420
2421 $tokens = explode (':', $ip);
2422 $last_token = $tokens[count ($tokens) - 1];
2423 $split = explode ('.', $last_token);
2424 if (count ($split) == 4)
2425 {
2426 $hex_tokens = array();
2427 $hex_tokens[] = dechex ($split[0] * 256 + $split[1]);
2428 $hex_tokens[] = dechex ($split[2] * 256 + $split[3]);
2429 array_splice ($tokens, -1, 1, $hex_tokens);
2430 }
2431 if (count ($tokens) > 8)
2432 break;
2433 for ($i = 0; $i < count ($tokens); $i++)
2434 {
2435 if ($tokens[$i] != '')
2436 {
2437 if (! set_word_value ($result, $i, $tokens[$i]))
2438 break;
2439 }
2440 else
2441 {
2442 $k = 8; //index in result string (last word)
2443 for ($j = count ($tokens) - 1; $j > $i; $j--) // $j is an index in $tokens for reverse walk
2444 if ($tokens[$j] == '')
2445 break;
2446 elseif (! set_word_value ($result, --$k, $tokens[$j]))
2447 break;
2448 if ($i != $j)
2449 break; //error, more than 1 '::' range
2450 break;
2451 }
2452 }
2453 if (! isset ($k) && count ($tokens) != 8)
2454 break;
2455 return $result;
2456 } while (FALSE);
2457
2458 throw new InvalidArgException ('ip', $ip, "Invalid IPv6 address");
2459 }
2460
2461 function ip_get_arpa ($ip_bin)
2462 {
2463 switch (strlen ($ip_bin))
2464 {
2465 case 4: return ip4_get_arpa ($ip_bin);
2466 case 16: return ip6_get_arpa ($ip_bin);
2467 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2468 }
2469 }
2470
2471 function ip4_get_arpa ($ip_bin)
2472 {
2473 $ret = '';
2474 for ($i = 3; $i >= 0; $i--)
2475 $ret .= ord($ip_bin[$i]) . '.';
2476 return $ret . 'in-addr.arpa';
2477 }
2478
2479 function ip6_get_arpa ($ip_bin)
2480 {
2481 $ret = '';
2482 $hex = implode ('', unpack('H32', $ip_bin));
2483 for ($i = 31; $i >= 0; $i--)
2484 $ret .= $hex[$i] . '.';
2485 return $ret . 'ip6.arpa';
2486 }
2487
2488 function set_word_value (&$haystack, $nword, $hexvalue)
2489 {
2490 // check that $hexvalue is like /^[0-9a-fA-F]*$/
2491 for ($i = 0; $i < strlen ($hexvalue); $i++)
2492 {
2493 $char = ord ($hexvalue[$i]);
2494 if (! ($char >= 0x30 && $char <= 0x39 || $char >= 0x41 && $char <= 0x46 || $char >=0x61 && $char <= 0x66))
2495 return FALSE;
2496 }
2497 $haystack = substr_replace ($haystack, pack ('n', hexdec ($hexvalue)), $nword * 2, 2);
2498 return TRUE;
2499 }
2500
2501 // returns binary IP or FALSE
2502 function ip_checkparse ($ip)
2503 {
2504 try
2505 {
2506 return ip_parse ($ip);
2507 }
2508 catch (InvalidArgException $e)
2509 {
2510 return FALSE;
2511 }
2512 }
2513
2514 // returns binary IP or FALSE
2515 function ip4_checkparse ($ip)
2516 {
2517 try
2518 {
2519 return ip4_parse ($ip);
2520 }
2521 catch (InvalidArgException $e)
2522 {
2523 return FALSE;
2524 }
2525 }
2526
2527 // returns binary IP or FALSE
2528 function ip6_checkparse ($ip)
2529 {
2530 try
2531 {
2532 return ip6_parse ($ip);
2533 }
2534 catch (InvalidArgException $e)
2535 {
2536 return FALSE;
2537 }
2538 }
2539
2540 function ip4_int2bin ($ip_int)
2541 {
2542 return pack ('N', $ip_int + 0);
2543 }
2544
2545 function ip4_bin2int ($ip_bin)
2546 {
2547 if (4 != strlen ($ip_bin))
2548 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2549 $ret = array_first (unpack ('N', $ip_bin));
2550 if (PHP_INT_SIZE > 4 && $ret < 0)
2551 $ret = $ret & 0xffffffff;
2552 return $ret;
2553 }
2554
2555 // Use this function only when you need to export binary ip out of PHP running context (e.g., DB)
2556 // !DO NOT perform arithmetic and bitwise operations with the result of this function!
2557 function ip4_bin2db ($ip_bin)
2558 {
2559 $ip_int = ip4_bin2int ($ip_bin);
2560 return $ip_int >= 0 ? $ip_int : sprintf ('%u', 0x00000000 + $ip_int);
2561 }
2562
2563 function ip_last ($net)
2564 {
2565 return $net['ip_bin'] | ~$net['mask_bin'];
2566 }
2567
2568 function ip_next ($ip_bin)
2569 {
2570 $ret = $ip_bin;
2571 $p = 1;
2572 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2573 {
2574 $oct = $p + ord ($ret[$i]);
2575 $ret[$i] = chr ($oct & 0xff);
2576 if ($oct <= 255 && $oct >= 0)
2577 break;
2578 }
2579 return $ret;
2580 }
2581
2582 function ip_prev ($ip_bin)
2583 {
2584 $ret = $ip_bin;
2585 $p = -1;
2586 for ($i = strlen ($ret) - 1; $i >= 0; $i--)
2587 {
2588 $oct = $p + ord ($ret[$i]);
2589 $ret[$i] = chr ($oct & 0xff);
2590 if ($oct <= 255 && $oct >= 0)
2591 break;
2592 }
2593 return $ret;
2594 }
2595
2596 function ip4_range_size ($range)
2597 {
2598 return ip4_mask_size ($range['mask']);
2599 }
2600
2601 function ip4_mask_size ($mask)
2602 {
2603 switch (TRUE)
2604 {
2605 case ($mask > 1 && $mask <= 32):
2606 return (0x7fffffff >> ($mask - 1)) + 1;
2607 // constants below are not representable in 32-bit PHP's int type,
2608 // so they are literally hardcoded and returned as strings on 32-bit architecture.
2609 case ($mask == 1):
2610 return 2147483648;
2611 case ($mask == 0):
2612 return 4294967296;
2613 default:
2614 throw new InvalidArgException ('mask', $mask, 'Invalid IPv4 prefix length');
2615 }
2616 }
2617
2618 // returns array with keys 'ip', 'ip_bin', 'mask', 'mask_bin'
2619 function constructIPRange ($ip_bin, $mask = NULL)
2620 {
2621 $node = array();
2622 switch (strlen ($ip_bin))
2623 {
2624 case 4: // IPv4
2625 if ($mask === NULL)
2626 $mask = 32;
2627 elseif (! is_numeric($mask) || $mask < 0 || $mask > 32)
2628 throw new InvalidArgException ('mask', $mask, "Invalid v4 prefix length");
2629 $node['mask_bin'] = ip4_mask ($mask);
2630 $node['mask'] = $mask;
2631 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2632 $node['ip'] = ip4_format ($node['ip_bin']);
2633 break;
2634 case 16: // IPv6
2635 if ($mask === NULL)
2636 $mask = 128;
2637 elseif (! is_numeric($mask) || $mask < 0 || $mask > 128)
2638 throw new InvalidArgException ('mask', $mask, "Invalid v6 prefix length");
2639 $node['mask_bin'] = ip6_mask ($mask);
2640 $node['mask'] = $mask;
2641 $node['ip_bin'] = $ip_bin & $node['mask_bin'];
2642 $node['ip'] = ip6_format ($node['ip_bin']);
2643 break;
2644 default:
2645 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2646 }
2647 return $node;
2648 }
2649
2650 // Return minimal IP address structure
2651 function constructIPAddress ($ip_bin)
2652 {
2653 // common v4/v6 part
2654 $ret = array
2655 (
2656 'ip' => ip_format ($ip_bin),
2657 'ip_bin' => $ip_bin,
2658 'name' => '',
2659 'comment' => '',
2660 'reserved' => 'no',
2661 'allocs' => array(),
2662 'vslist' => array(),
2663 'vsglist' => array(),
2664 'rsplist' => array(),
2665 );
2666
2667 // specific v4 part
2668 if (strlen ($ip_bin) == 4)
2669 $ret = array_merge
2670 (
2671 $ret,
2672 array
2673 (
2674 'outpf' => array(),
2675 'inpf' => array(),
2676 )
2677 );
2678 return $ret;
2679 }
2680
2681 function treeApplyFunc1 (&$tree, $func)
2682 {
2683 $self = __FUNCTION__;
2684 foreach (array_keys ($tree) as $key)
2685 {
2686 $func ($tree[$key]);
2687 $self ($tree[$key]['kids'], $func);
2688 }
2689 }
2690
2691 function treeApplyFunc2 (&$tree, $func, $stopfunc)
2692 {
2693 $self = __FUNCTION__;
2694 foreach (array_keys ($tree) as $key)
2695 {
2696 $func ($tree[$key]);
2697 if (! $stopfunc ($tree[$key]))
2698 $self ($tree[$key]['kids'], $func);
2699 }
2700 }
2701
2702 // Note that the stop function is called after processing a tree item, not before.
2703 // In other words, for a given tree node either all its sub-nodes are processed or
2704 // none at all.
2705 // XXX: Perhaps instead of a separate stop function it would be better to convey the
2706 // feedback through the applied function's return value. This would also leave it up
2707 // to the applied function whether to stop before or after modifying the current node.
2708 function treeApplyFunc (&$tree, $func, $stopfunc = NULL)
2709 {
2710 if (! is_callable ($func))
2711 throw new InvalidArgException ('func', $func, 'is not callable');
2712 if ($stopfunc === NULL)
2713 treeApplyFunc1 ($tree, $func);
2714 else
2715 {
2716 if (! is_callable ($stopfunc))
2717 throw new InvalidArgException ('stopfunc', $stopfunc, 'is not callable');
2718 treeApplyFunc2 ($tree, $func, $stopfunc);
2719 }
2720 }
2721
2722 function nodeIsCollapsed ($node)
2723 {
2724 return $node['symbol'] == 'node-collapsed';
2725 }
2726
2727 // returns those addresses from $addrlist that do not belong to $net's subsequent networks
2728 function filterOwnAddrList ($net, $addrlist)
2729 {
2730 if ($net['kidc'] == 0)
2731 return $addrlist;
2732
2733 // net has children
2734 $ret = array();
2735 foreach ($net['spare_ranges'] as $mask => $spare_list)
2736 foreach ($spare_list as $spare_ip)
2737 {
2738 $spare_mask = ip_mask ($mask, strlen ($net['ip_bin']) == 16);
2739 foreach ($addrlist as $bin_ip => $addr)
2740 if (($bin_ip & $spare_mask) == $spare_ip)
2741 $ret[$bin_ip] = $addr;
2742 }
2743 return $ret;
2744 }
2745
2746 function getIPAddrList ($net, $flags = IPSCAN_ANY)
2747 {
2748 $addrlist = scanIPNet ($net, $flags);
2749 return filterOwnAddrList ($net, $addrlist);
2750 }
2751
2752 // sets 'addrlist', 'own_addrlist', 'addrc', 'own_addrc' keys of $node
2753 // 'addrc' and 'own_addrc' are sizes of 'addrlist' and 'own_addrlist', respectively
2754 function loadIPAddrList (&$node)
2755 {
2756 $node['addrlist'] = scanIPNet ($node);
2757
2758 if (! isset ($node['id']))
2759 $node['own_addrlist'] = $node['addrlist'];
2760 else
2761 $node['own_addrlist'] = filterOwnAddrList ($node, $node['addrlist']);
2762
2763 $node['addrc'] = count ($node['addrlist']);
2764 $node['own_addrc'] = count ($node['own_addrlist']);
2765 }
2766
2767 // returns list of PtP-typed allocs from $addrlist indexed by ip_bin
2768 function getPtPNeighbors ($ip_bin, $addrlist)
2769 {
2770 $ret = array();
2771 if (isset ($addrlist[$ip_bin]))
2772 {
2773 $found_ptp_alloc = FALSE;
2774 foreach ($addrlist[$ip_bin]['allocs'] as $alloc)
2775 if ($alloc['type'] == 'point2point')
2776 {
2777 $found_ptp_alloc = TRUE;
2778 break;
2779 }
2780 if ($found_ptp_alloc)
2781 foreach ($addrlist as $i_ip_bin => $i_address)
2782 if ($ip_bin !== $i_ip_bin)
2783 foreach ($i_address['allocs'] as $alloc)
2784 if ($alloc['type'] == 'point2point')
2785 $ret[$i_ip_bin][] = $alloc;
2786 }
2787 return $ret;
2788 }
2789
2790 // returns the array of structure described by constructIPAddress
2791 function getIPAddress ($ip_bin)
2792 {
2793 $scanres = scanIPSpace (array (array ('first' => $ip_bin, 'last' => $ip_bin)));
2794 if (empty ($scanres))
2795 $scanres[$ip_bin] = constructIPAddress ($ip_bin);
2796 markupIPAddrList ($scanres);
2797 return $scanres[$ip_bin];
2798 }
2799
2800 function getIPv4Address ($ip_bin)
2801 {
2802 if (strlen ($ip_bin) != 4)
2803 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2804 return getIPAddress ($ip_bin);
2805 }
2806
2807 function getIPv6Address ($ip_bin)
2808 {
2809 if (strlen ($ip_bin) != 16)
2810 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP");
2811 return getIPAddress ($ip_bin);
2812 }
2813
2814 function makeIPTree ($netlist)
2815 {
2816 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
2817 // so perform necessary pre-processing to calculate parent_id of each item of $netlist.
2818 $stack = array();
2819 foreach ($netlist as $net_id => &$net)
2820 {
2821 while (count ($stack))
2822 {
2823 $top_id = $stack[count ($stack) - 1];
2824 if (! IPNetContains ($netlist[$top_id], $net)) // unless $net is a child of stack top
2825 array_pop ($stack);
2826 else
2827 {
2828 $net['parent_id'] = $top_id;
2829 break;
2830 }
2831 }
2832 if (! count ($stack))
2833 $net['parent_id'] = NULL;
2834 array_push ($stack, $net_id);
2835 }
2836 unset ($stack);
2837
2838 return treeFromList (addTraceToNodes ($netlist));
2839 }
2840
2841 function prepareIPTree ($netlist, $expanded_id = 0)
2842 {
2843 $tree = makeIPTree ($netlist);
2844 // complement the tree before markup to make the spare networks have "symbol" set
2845 treeApplyFunc ($tree, 'iptree_fill');
2846 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
2847 return $tree;
2848 }
2849
2850 # Traverse IPv4/IPv6 tree and return a list of all networks that
2851 # exist in DB and don't have any sub-networks.
2852 function getTerminalNetworks ($tree)
2853 {
2854 $self = __FUNCTION__;
2855 $ret = array();
2856 foreach ($tree as $node)
2857 if ($node['kidc'] == 0 && isset ($node['realm']))
2858 $ret[] = $node;
2859 else
2860 $ret = array_merge ($ret, $self ($node['kids']));
2861 return $ret;
2862 }
2863
2864 // Check all items of the tree recursively, until the requested target id is
2865 // found. Mark all items leading to this item as "expanded", collapsing all
2866 // the rest that exceed the given threshold (if the threshold is given).
2867 function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
2868 {
2869 $self = __FUNCTION__;
2870 $ret = FALSE;
2871 foreach (array_keys ($tree) as $key)
2872 {
2873 $here = $target === 'ALL' || ($target > 0 && isset ($tree[$key]['id']) && $tree[$key]['id'] == $target);
2874 $below = $self ($tree[$key]['kids'], $threshold, $target);
2875 $expand_enabled = ($target !== 'NONE');
2876 if (!$tree[$key]['kidc']) // terminal node
2877 $tree[$key]['symbol'] = 'spacer';
2878 elseif ($expand_enabled && $tree[$key]['kidc'] < $threshold)
2879 $tree[$key]['symbol'] = 'node-expanded-static';
2880 elseif ($expand_enabled && ($here || $below))
2881 $tree[$key]['symbol'] = 'node-expanded';
2882 else
2883 $tree[$key]['symbol'] = 'node-collapsed';
2884 $ret = $ret || $here || $below;
2885 }
2886 return $ret;
2887 }
2888
2889 // Convert entity name to human-readable value
2890 function formatRealmName ($realm)
2891 {
2892 $realmstr = array
2893 (
2894 'ipv4net' => 'IPv4 Network',
2895 'ipv6net' => 'IPv6 Network',
2896 'ipv4rspool' => 'IPv4 RS Pool',
2897 'ipv4vs' => 'IPv4 Virtual Service',
2898 'ipvs' => 'IP Virtual Service',
2899 'object' => 'Object',
2900 'rack' => 'Rack',
2901 'row' => 'Row',
2902 'location' => 'Location',
2903 'user' => 'User',
2904 'file' => 'File',
2905 'vst' => 'VLAN switch template',
2906 );
2907 return array_fetch ($realmstr, $realm, 'invalid');
2908 }
2909
2910 // Convert filesize to appropriate unit and make it human-readable
2911 function formatFileSize ($bytes)
2912 {
2913 // bytes
2914 if($bytes < 1024) // bytes
2915 return "${bytes} bytes";
2916
2917 // kilobytes
2918 if ($bytes < 1024000)
2919 return sprintf ("%.1fk", round (($bytes / 1024), 1));
2920
2921 // megabytes
2922 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
2923 }
2924
2925 // Reverse of formatFileSize, it converts human-readable value to bytes
2926 function convertToBytes ($value)
2927 {
2928 $value = trim($value);
2929 $last = strtolower($value[strlen($value)-1]);
2930 switch ($last)
2931 {
2932 case 'g':
2933 $value *= 1024;
2934 case 'm':
2935 $value *= 1024;
2936 case 'k':
2937 $value *= 1024;
2938 }
2939
2940 return $value;
2941 }
2942
2943 // make "A" HTML element
2944 function mkA ($text, $nextpage, $bypass = NULL, $nexttab = NULL, $attrs = array())
2945 {
2946 global $page, $tab;
2947 if ($text == '')
2948 throw new InvalidArgException ('text', $text, 'must not be empty');
2949 if (! array_key_exists ($nextpage, $page))
2950 throw new InvalidArgException ('nextpage', $nextpage, 'not a valid page name');
2951 $args = array ('page' => $nextpage);
2952 if ($nexttab !== NULL)
2953 {
2954 if (! array_key_exists ($nexttab, $tab[$nextpage]))
2955 throw new InvalidArgException ('nexttab', $nexttab, 'not a valid tab name');
2956 $args['tab'] = $nexttab;
2957 }
2958 if (array_key_exists ('bypass', $page[$nextpage]))
2959 {
2960 if ($bypass === NULL)
2961 throw new InvalidArgException ('bypass', '(NULL)', 'must be specified for the given page name');
2962 $args[$page[$nextpage]['bypass']] = $bypass;
2963 }
2964 $attrs['href'] = makeHref ($args);
2965 $ret = '<a';
2966 foreach ($attrs as $attr_name => $attr_value)
2967 $ret .= " $attr_name=" . '"' . htmlspecialchars ($attr_value, ENT_QUOTES) . '"';
2968 return $ret . '>' . $text . '</a>';
2969 }
2970
2971 // make "HREF" HTML attribute
2972 function makeHref ($params = array())
2973 {
2974 $tmp = array();
2975 foreach ($params as $key => $value)
2976 {
2977 if (is_array ($value))
2978 $key .= "[]";
2979 else
2980 $value = array ($value);
2981 if (!count ($value))
2982 $tmp[] = urlencode ($key) . '=';
2983 else
2984 foreach ($value as $sub_value)
2985 $tmp[] = urlencode ($key) . '=' . urlencode ($sub_value);
2986 }
2987 return 'index.php?' . implode ('&', $tmp);
2988 }
2989
2990 function makePageParams ($params = array())
2991 {
2992 global $pageno, $tabno;
2993 $ret = array();
2994 // assure that page and tab keys go first
2995 $ret['page'] = isset ($params['page']) ? $params['page'] : $pageno;
2996 $ret['tab'] = isset ($params['tab']) ? $params['tab'] : $tabno;
2997 $ret += $params;
2998 if ($ret['page'] === $pageno)
2999 fillBypassValues ($pageno, $ret);
3000 return $ret;
3001 }
3002
3003 function makeHrefProcess ($params = array())
3004 {
3005 return makeHref (array ('module' => 'redirect') + makePageParams ($params));
3006 }
3007
3008 // Process the given list of records to build data suitable for printNiftySelect()
3009 // (like it was formerly executed by printSelect()). Screen out vendors according
3010 // to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
3011 // selected OPTION is protected from being screened.
3012 function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
3013 {
3014 $ret = array();
3015 $therest = array();
3016 foreach ($recordList as $dict_key => $dict_value)
3017 if (preg_match ('/^(.*)%(GPASS|GSKIP)%/', $dict_value, $m))
3018 $ret[$m[1]][$dict_key] = execGMarker ($dict_value);
3019 else
3020 $therest[$dict_key] = $dict_value;
3021
3022 // Always keep "other" OPTGROUP at the SELECT bottom.
3023 $ret['other'] = $therest;
3024
3025 if ($object_type_id != 0)
3026 {
3027 $screenlist = array();
3028 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
3029 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs))
3030 $screenlist[] = $regs[1];
3031
3032 foreach (array_keys ($ret) as $vendor)
3033 if (in_array ($vendor, $screenlist))
3034 {
3035 $ok_to_screen = TRUE;
3036 if ($existing_value)
3037 foreach (array_keys ($ret[$vendor]) as $recordkey)
3038 if ($recordkey == $existing_value)
3039 {
3040 $ok_to_screen = FALSE;
3041 break;
3042 }
3043 if ($ok_to_screen)
3044 unset ($ret[$vendor]);
3045 }
3046 }
3047 return $ret;
3048 }
3049
3050 function dos2unix ($text)
3051 {
3052 return str_replace ("\r\n", "\n", $text);
3053 }
3054
3055 function unix2dos ($text)
3056 {
3057 return str_replace ("\n", "\r\n", $text);
3058 }
3059
3060 function buildPredicateTable (&$rackCode)
3061 {
3062 $ret = array();
3063 $new_rackCode = array();
3064
3065 foreach ($rackCode as $sentence)
3066 if ($sentence['type'] == 'SYNT_DEFINITION')
3067 $ret[$sentence['term']] = $sentence['definition'];
3068 else
3069 $new_rackCode[] = $sentence;
3070
3071 // remove SYNT_DEFINITION statements from the original rackCode to
3072 // make permitted() calls faster.
3073 $rackCode = $new_rackCode;
3074
3075 // Now we have predicate table filled in with the latest definitions of each
3076 // particular predicate met. This isn't as chik, as on-the-fly predicate
3077 // overloading during allow/deny scan, but quite sufficient for this task.
3078 return $ret;
3079 }
3080
3081 // Take a list of records and filter against given RackCode expression. Return
3082 // the original list intact, if there was no filter requested, but return an
3083 // empty list, if there was an error.
3084 function filterCellList ($list_in, $expression = array())
3085 {
3086 if ($expression === NULL)
3087 return array();
3088 if (!count ($expression))
3089 return $list_in;
3090 $list_out = array();
3091 foreach ($list_in as $item_key => $item_value)
3092 if (TRUE === judgeCell ($item_value, $expression))
3093 $list_out[$item_key] = $item_value;
3094 return $list_out;
3095 }
3096
3097 function eval_expression ($expr, $tagchain, $silent = FALSE)
3098 {
3099 $self = __FUNCTION__;
3100 global $pTable;
3101
3102 switch ($expr['type'])
3103 {
3104 // Return true, if given tag is present on the tag chain.
3105 case 'LEX_TAG':
3106 return isset ($tagchain[$expr['load']]);
3107 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it.
3108 $pname = $expr['load'];
3109 if (!isset ($pTable[$pname]))
3110 {
3111 if (!$silent)
3112 showWarning ("Undefined predicate [${pname}]");
3113 return NULL;
3114 }
3115 return $self ($pTable[$pname], $tagchain, $silent);
3116 case 'LEX_BOOL':
3117 return $expr['load'];
3118 case 'SYNT_NOT_EXPR': // logical NOT
3119 $tmp = $self ($expr['load'], $tagchain, $silent);
3120 return is_bool($tmp) ? !$tmp : $tmp;
3121 case 'SYNT_AND_EXPR': // logical AND
3122 foreach ($expr['tag_args'] as $tag)
3123 if (! isset ($tagchain[$tag]))
3124 return FALSE; // early failure
3125 foreach ($expr['expr_args'] as $sub_expr)
3126 if (! $self ($sub_expr, $tagchain, $silent))
3127 return FALSE; // early failure
3128 return TRUE;
3129 case 'SYNT_EXPR': // logical OR
3130 foreach ($expr['tag_args'] as $tag)
3131 if (isset ($tagchain[$tag]))
3132 return TRUE; // early success
3133 foreach ($expr['expr_args'] as $sub_expr)
3134 if ($self ($sub_expr, $tagchain, $silent))
3135 return TRUE; // early success
3136 return FALSE;
3137 default:
3138 if (!$silent)
3139 showWarning ("Evaluation error, cannot process expression type '${expr['type']}'");
3140 return NULL;
3141 break;
3142 }
3143 }
3144
3145 // Tell, if the given expression is true for the given entity. Take complete record on input.
3146 function judgeCell ($cell, $expression)
3147 {
3148 $context = array_merge ($cell['etags'], $cell['itags'], $cell['atags']);
3149 $context = reindexById ($context, 'tag', TRUE);
3150 return eval_expression
3151 (
3152 $expression,
3153 $context,
3154 TRUE
3155 );
3156 }
3157
3158 function judgeContext ($expression)
3159 {
3160 global $expl_tags, $impl_tags, $auto_tags;
3161 $context = array_merge ($expl_tags, $impl_tags, $auto_tags);
3162 $context = reindexById ($context, 'tag', TRUE);
3163 return eval_expression
3164 (
3165 $expression,
3166 $context,
3167 TRUE
3168 );
3169 }
3170
3171 // Tell, if a constraint from config option permits given record.