r1893 + introduce and make use of getTagFilter()
[racktables] / inc / functions.php
1 <?php
2 /*
3 *
4 * This file is a library of computational functions for RackTables.
5 *
6 */
7
8 $loclist[0] = 'front';
9 $loclist[1] = 'interior';
10 $loclist[2] = 'rear';
11 $loclist['front'] = 0;
12 $loclist['interior'] = 1;
13 $loclist['rear'] = 2;
14 $template[0] = array (TRUE, TRUE, TRUE);
15 $template[1] = array (TRUE, TRUE, FALSE);
16 $template[2] = array (FALSE, TRUE, TRUE);
17 $template[3] = array (TRUE, FALSE, FALSE);
18 $template[4] = array (FALSE, TRUE, FALSE);
19 $template[5] = array (FALSE, FALSE, TRUE);
20 $templateWidth[0] = 3;
21 $templateWidth[1] = 2;
22 $templateWidth[2] = 2;
23 $templateWidth[3] = 1;
24 $templateWidth[4] = 1;
25 $templateWidth[5] = 1;
26
27 // Objects of some types should be explicitly shown as
28 // anonymous (labelless). This function is a single place where the
29 // decision about displayed name is made.
30 function displayedName ($objectData)
31 {
32 if ($objectData['name'] != '')
33 return $objectData['name'];
34 elseif (in_array ($objectData['objtype_id'], explode (',', getConfigVar ('NAMEFUL_OBJTYPES'))))
35 return "ANONYMOUS " . $objectData['objtype_name'];
36 else
37 return "[${objectData['objtype_name']}]";
38 }
39
40 // This function finds height of solid rectangle of atoms, which are all
41 // assigned to the same object. Rectangle base is defined by specified
42 // template.
43 function rectHeight ($rackData, $startRow, $template_idx)
44 {
45 $height = 0;
46 // The first met object_id is used to match all the folowing IDs.
47 $object_id = 0;
48 global $template;
49 do
50 {
51 for ($locidx = 0; $locidx < 3; $locidx++)
52 {
53 // At least one value in template is TRUE, but the following block
54 // can meet 'skipped' atoms. Let's ensure we have something after processing
55 // the first row.
56 if ($template[$template_idx][$locidx])
57 {
58 if (isset ($rackData[$startRow - $height][$locidx]['skipped']))
59 break 2;
60 if (isset ($rackData[$startRow - $height][$locidx]['rowspan']))
61 break 2;
62 if (isset ($rackData[$startRow - $height][$locidx]['colspan']))
63 break 2;
64 if ($rackData[$startRow - $height][$locidx]['state'] != 'T')
65 break 2;
66 if ($object_id == 0)
67 $object_id = $rackData[$startRow - $height][$locidx]['object_id'];
68 if ($object_id != $rackData[$startRow - $height][$locidx]['object_id'])
69 break 2;
70 }
71 }
72 // If the first row can't offer anything, bail out.
73 if ($height == 0 and $object_id == 0)
74 break;
75 $height++;
76 }
77 while ($startRow - $height > 0);
78 # echo "for startRow==${startRow} and template==(" . ($template[$template_idx][0] ? 'T' : 'F');
79 # echo ', ' . ($template[$template_idx][1] ? 'T' : 'F') . ', ' . ($template[$template_idx][2] ? 'T' : 'F');
80 # echo ") height==${height}<br>\n";
81 return $height;
82 }
83
84 // This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan
85 // attributes.
86 function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
87 {
88 global $template, $templateWidth;
89 $colspan = 0;
90 for ($height = 0; $height < $maxheight; $height++)
91 {
92 for ($locidx = 0; $locidx < 3; $locidx++)
93 {
94 if ($template[$template_idx][$locidx])
95 {
96 // Add colspan/rowspan to the first row met and mark the following ones to skip.
97 // Explicitly show even single-cell spanned atoms, because rectHeight()
98 // is expeciting this data for correct calculation.
99 if ($colspan != 0)
100 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
101 else
102 {
103 $colspan = $templateWidth[$template_idx];
104 if ($colspan >= 1)
105 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
106 if ($maxheight >= 1)
107 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
108 }
109 }
110 }
111 }
112 return;
113 }
114
115 // This function sets rowspan/solspan/skipped atom attributes for renderRack()
116 // What we actually have to do is to find _all_ possible rectangles for each unit
117 // and then select the widest of those with the maximal square.
118 function markAllSpans (&$rackData = NULL)
119 {
120 if ($rackData == NULL)
121 {
122 showError ('Invalid rackData', __FUNCTION__);
123 return;
124 }
125 for ($i = $rackData['height']; $i > 0; $i--)
126 while (markBestSpan ($rackData, $i));
127 }
128
129 // Calculate height of 6 possible span templates (array is presorted by width
130 // descending) and mark the best (if any).
131 function markBestSpan (&$rackData, $i)
132 {
133 global $template, $templateWidth;
134 for ($j = 0; $j < 6; $j++)
135 {
136 $height[$j] = rectHeight ($rackData, $i, $j);
137 $square[$j] = $height[$j] * $templateWidth[$j];
138 }
139 // find the widest rectangle of those with maximal height
140 $maxsquare = max ($square);
141 if (!$maxsquare)
142 return FALSE;
143 $best_template_index = 0;
144 for ($j = 0; $j < 6; $j++)
145 if ($square[$j] == $maxsquare)
146 {
147 $best_template_index = $j;
148 $bestheight = $height[$j];
149 break;
150 }
151 // distribute span marks
152 markSpan ($rackData, $i, $bestheight, $best_template_index);
153 return TRUE;
154 }
155
156 function delRow ($row_id = 0)
157 {
158 if ($row_id == 0)
159 {
160 showError ('Not all required args are present.', __FUNCTION__);
161 return;
162 }
163 if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
164 {
165 echo "Press <a href='?op=del_row&row_id=${row_id}&confirmed=true'>here</a> to confirm rack row deletion.";
166 return;
167 }
168 global $dbxlink;
169 echo 'Deleting rack row information: ';
170 $result = $dbxlink->query ("update RackRow set deleted = 'yes' where id=${row_id} limit 1");
171 if ($result->rowCount() != 1)
172 {
173 showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
174 return;
175 }
176 echo 'OK<br>';
177 recordHistory ('RackRow', "id = ${row_id}");
178 echo "Information was deleted. You may return to <a href='?op=list_rows&editmode=on'>rack row list</a>.";
179 }
180
181 function delRack ($rack_id = 0)
182 {
183 if ($rack_id == 0)
184 {
185 showError ('Not all required args are present.', __FUNCTION__);
186 return;
187 }
188 if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
189 {
190 echo "Press <a href='?op=del_rack&rack_id=${rack_id}&confirmed=true'>here</a> to confirm rack deletion.";
191 return;
192 }
193 global $dbxlink;
194 echo 'Deleting rack information: ';
195 $result = $dbxlink->query ("update Rack set deleted = 'yes' where id=${rack_id} limit 1");
196 if ($result->rowCount() != 1)
197 {
198 showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
199 return;
200 }
201 echo 'OK<br>';
202 recordHistory ('Rack', "id = ${rack_id}");
203 echo "Information was deleted. You may return to <a href='?op=list_racks&editmode=on'>rack list</a>.";
204 }
205
206 function delObject ($object_id = 0)
207 {
208 if ($object_id == 0)
209 {
210 showError ('Not all required args are present.', __FUNCTION__);
211 return;
212 }
213 if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
214 {
215 echo "Press <a href='?op=del_object&object_id=${object_id}&confirmed=true'>here</a> to confirm object deletion.";
216 return;
217 }
218 global $dbxlink;
219 echo 'Deleting object information: ';
220 $result = $dbxlink->query ("update RackObject set deleted = 'yes' where id=${object_id} limit 1");
221 if ($result->rowCount() != 1)
222 {
223 showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
224 return;
225 }
226 echo 'OK<br>';
227 recordHistory ('RackObject', "id = ${object_id}");
228 echo "Information was deleted. You may return to <a href='?op=list_objects&editmode=on'>object list</a>.";
229 }
230
231 // We can mount 'F' atoms and unmount our own 'T' atoms.
232 function applyObjectMountMask (&$rackData, $object_id)
233 {
234 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
235 for ($locidx = 0; $locidx < 3; $locidx++)
236 switch ($rackData[$unit_no][$locidx]['state'])
237 {
238 case 'F':
239 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
240 break;
241 case 'T':
242 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id);
243 break;
244 default:
245 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
246 }
247 }
248
249 // Design change means transition between 'F' and 'A' and back.
250 function applyRackDesignMask (&$rackData)
251 {
252 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
253 for ($locidx = 0; $locidx < 3; $locidx++)
254 switch ($rackData[$unit_no][$locidx]['state'])
255 {
256 case 'F':
257 case 'A':
258 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
259 break;
260 default:
261 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
262 }
263 }
264
265 // The same for 'F' and 'U'.
266 function applyRackProblemMask (&$rackData)
267 {
268 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
269 for ($locidx = 0; $locidx < 3; $locidx++)
270 switch ($rackData[$unit_no][$locidx]['state'])
271 {
272 case 'F':
273 case 'U':
274 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
275 break;
276 default:
277 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
278 }
279 }
280
281 // This mask should allow toggling 'T' and 'W' on object's rackspace.
282 function applyObjectProblemMask (&$rackData)
283 {
284 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
285 for ($locidx = 0; $locidx < 3; $locidx++)
286 switch ($rackData[$unit_no][$locidx]['state'])
287 {
288 case 'T':
289 case 'W':
290 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id);
291 break;
292 default:
293 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
294 }
295 }
296
297 // This function highlights specified object (and removes previous highlight).
298 function highlightObject (&$rackData, $object_id)
299 {
300 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
301 for ($locidx = 0; $locidx < 3; $locidx++)
302 if
303 (
304 $rackData[$unit_no][$locidx]['state'] == 'T' and
305 $rackData[$unit_no][$locidx]['object_id'] == $object_id
306 )
307 $rackData[$unit_no][$locidx]['hl'] = 'h';
308 else
309 unset ($rackData[$unit_no][$locidx]['hl']);
310 }
311
312 // This function marks atoms to selected or not depending on their current state.
313 function markupAtomGrid (&$data, $checked_state)
314 {
315 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
316 for ($locidx = 0; $locidx < 3; $locidx++)
317 {
318 if (!($data[$unit_no][$locidx]['enabled'] === TRUE))
319 continue;
320 if ($data[$unit_no][$locidx]['state'] == $checked_state)
321 $data[$unit_no][$locidx]['checked'] = ' checked';
322 else
323 $data[$unit_no][$locidx]['checked'] = '';
324 }
325 }
326
327 // This function is almost a clone of processGridForm(), but doesn't save anything to database
328 // Return value is the changed rack data.
329 // Here we assume that correct filter has already been applied, so we just
330 // set or unset checkbox inputs w/o changing atom state.
331 function mergeGridFormToRack (&$rackData)
332 {
333 $rack_id = $rackData['id'];
334 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
335 for ($locidx = 0; $locidx < 3; $locidx++)
336 {
337 if ($rackData[$unit_no][$locidx]['enabled'] != TRUE)
338 continue;
339 $inputname = "atom_${rack_id}_${unit_no}_${locidx}";
340 if (isset ($_REQUEST[$inputname]) and $_REQUEST[$inputname] == 'on')
341 $rackData[$unit_no][$locidx]['checked'] = ' checked';
342 else
343 $rackData[$unit_no][$locidx]['checked'] = '';
344 }
345 }
346
347 function binMaskFromDec ($maskL)
348 {
349 $binmask=0;
350 for ($i=0; $i<$maskL; $i++)
351 {
352 $binmask*=2;
353 $binmask+=1;
354 }
355 for ($i=$maskL; $i<32; $i++)
356 {
357 $binmask*=2;
358 }
359 return $binmask;
360 }
361
362 function binInvMaskFromDec ($maskL)
363 {
364 $binmask=0;
365 for ($i=0; $i<$maskL; $i++)
366 {
367 $binmask*=2;
368 }
369 for ($i=$maskL; $i<32; $i++)
370 {
371 $binmask*=2;
372 $binmask+=1;
373 }
374 return $binmask;
375 }
376
377 function addRange ($range='', $name='', $is_bcast = FALSE, $taglist = array())
378 {
379 // $range is in x.x.x.x/x format, split into ip/mask vars
380 $rangeArray = explode('/', $range);
381 if (count ($rangeArray) != 2)
382 return "Invalid IP subnet '${range}'";
383 $ip = $rangeArray[0];
384 $mask = $rangeArray[1];
385
386 if (empty ($ip) or empty ($mask))
387 return "Invalid IP subnet '${range}'";
388 $ipL = ip2long($ip);
389 $maskL = ip2long($mask);
390 if ($ipL == -1 || $ipL === FALSE)
391 return 'Bad ip address';
392 if ($mask < 32 && $mask > 0)
393 $maskL = $mask;
394 else
395 {
396 $maskB = decbin($maskL);
397 if (strlen($maskB)!=32)
398 return 'Bad mask';
399 $ones=0;
400 $zeroes=FALSE;
401 foreach( str_split ($maskB) as $digit)
402 {
403 if ($digit == '0')
404 $zeroes = TRUE;
405 if ($digit == '1')
406 {
407 $ones++;
408 if ($zeroes == TRUE)
409 return 'Bad mask';
410 }
411 }
412 $maskL = $ones;
413 }
414 $binmask = binMaskFromDec($maskL);
415 $ipL = $ipL & $binmask;
416
417 $query =
418 "select ".
419 "id, ip, mask, name ".
420 "from IPRanges ";
421
422
423 $result = useSelectBlade ($query);
424
425 while ($row = $result->fetch (PDO::FETCH_ASSOC))
426 {
427 $otherip = $row['ip'];
428 $othermask = binMaskFromDec($row['mask']);
429 // echo "checking $otherip & $othermask ".($otherip & $othermask)." == $ipL & $othermask ".($ipL & $othermask)." ".decbin($otherip)." ".decbin($othermask)." ".decbin($otherip & $othermask)." ".decbin($ipL)." ".decbin($othermask)." ".decbin($ipL & $othermask)."\n";
430 // echo "checking $otherip & $binmask ".($otherip & $binmask)." == $ipL & $binmask ".($ipL & $binmask)." ".decbin($otherip)." ".decbin($binmask)." ".decbin($otherip & $binmask)." ".decbin($ipL)." ".decbin($binmask)." ".decbin($ipL & $binmask)."\n";
431 // echo "\n";
432 // flush();
433 if (($otherip & $othermask) == ($ipL & $othermask))
434 return "This subnet intersects with ".long2ip($row['ip'])."/${row['mask']}";
435 if (($otherip & $binmask) == ($ipL & $binmask))
436 return "This subnet intersects with ".long2ip($row['ip'])."/${row['mask']}";
437 }
438 $result->closeCursor();
439 unset ($result);
440 $result = useInsertBlade
441 (
442 'IPRanges',
443 array
444 (
445 'ip' => sprintf ('%u', $ipL),
446 'mask' => "'${maskL}'",
447 'name' => "'${name}'"
448 )
449 );
450
451 if ($is_bcast and $maskL < 31)
452 {
453 $network_addr = long2ip ($ipL);
454 $broadcast_addr = long2ip ($ipL | binInvMaskFromDec ($maskL));
455 updateAddress ($network_addr, 'network', 'yes');
456 updateAddress ($broadcast_addr, 'broadcast', 'yes');
457 }
458 if (!count ($taglist))
459 return '';
460 if (($result = useSelectBlade ('select last_insert_id()')) == NULL)
461 return 'Query #3 failed in ' . __FUNCTION__;
462 $row = $result->fetch (PDO::FETCH_NUM);
463 $netid = $row[0];
464 $result->closeCursor();
465 unset ($result);
466 $errcount = 0;
467 foreach ($taglist as $tag_id)
468 if (useInsertBlade
469 (
470 'TagStorage',
471 array
472 (
473 'target_realm' => "'ipv4net'",
474 'target_id' => $netid,
475 'tag_id' => $tag_id
476 )
477 ) == FALSE)
478 $errcount++;
479 if (!$errcount)
480 return '';
481 else
482 return "Experienced ${errcount} errors adding tags for the network";
483 }
484
485 function getIPRange ($id = 0)
486 {
487 global $dbxlink;
488 $query =
489 "select ".
490 "id as IPRanges_id, ".
491 "INET_NTOA(ip) as IPRanges_ip, ".
492 "mask as IPRanges_mask, ".
493 "name as IPRanges_name ".
494 "from IPRanges ".
495 "where id = '$id'";
496 $result = useSelectBlade ($query);
497 $ret = array();
498 $row = $result->fetch (PDO::FETCH_ASSOC);
499 if ($row == NULL)
500 return $ret;
501 $ret['id'] = $row['IPRanges_id'];
502 $ret['ip'] = $row['IPRanges_ip'];
503 $ret['ip_bin'] = ip2long ($row['IPRanges_ip']);
504 $ret['mask_bin'] = binMaskFromDec($row['IPRanges_mask']);
505 $ret['mask_bin_inv'] = binInvMaskFromDec($row['IPRanges_mask']);
506 $ret['name'] = $row['IPRanges_name'];
507 $ret['mask'] = $row['IPRanges_mask'];
508 $ret['addrlist'] = array();
509 $result->closeCursor();
510 unset ($result);
511 // We risk losing some significant bits in an unsigned 32bit integer,
512 // unless it is converted to a string.
513 $db_first = "'" . sprintf ('%u', 0x00000000 + $ret['ip_bin'] & $ret['mask_bin']) . "'";
514 $db_last = "'" . sprintf ('%u', 0x00000000 + $ret['ip_bin'] | ($ret['mask_bin_inv'])) . "'";
515
516 // Don't try to build up the whole structure in a single pass. Request
517 // the list of user comments and reservations and merge allocations in
518 // at a latter point.
519 $query =
520 "select INET_NTOA(ip) as ip, name, reserved from IPAddress " .
521 "where ip between ${db_first} and ${db_last} " .
522 "and (reserved = 'yes' or name != '')";
523 $result = $dbxlink->query ($query);
524 while ($row = $result->fetch (PDO::FETCH_ASSOC))
525 {
526 $ip_bin = ip2long ($row['ip']);
527 $ret['addrlist'][$ip_bin] = $row;
528 $tmp = array();
529 foreach (array ('ip', 'name', 'reserved') as $cname)
530 $tmp[$cname] = $row[$cname];
531 $tmp['references'] = array();
532 $tmp['lbrefs'] = array();
533 $tmp['rsrefs'] = array();
534 $ret['addrlist'][$ip_bin] = $tmp;
535 }
536 $result->closeCursor();
537 unset ($result);
538
539 $query =
540 "select INET_NTOA(ipb.ip) as ip, ro.id as object_id, " .
541 "ro.name as object_name, ipb.name, ipb.type, objtype_id, " .
542 "dict_value as objtype_name from " .
543 "IPBonds as ipb inner join RackObject as ro on ipb.object_id = ro.id " .
544 "left join Dictionary on objtype_id=dict_key natural join Chapter " .
545 "where ip between ${db_first} and ${db_last} " .
546 "and chapter_name = 'RackObjectType'" .
547 "order by ipb.type, object_name";
548 $result = useSelectBlade ($query);
549 while ($row = $result->fetch (PDO::FETCH_ASSOC))
550 {
551 $ip_bin = ip2long ($row['ip']);
552 if (!isset ($ret['addrlist'][$ip_bin]))
553 {
554 $ret['addrlist'][$ip_bin] = array();
555 $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
556 $ret['addrlist'][$ip_bin]['name'] = '';
557 $ret['addrlist'][$ip_bin]['reserved'] = 'no';
558 $ret['addrlist'][$ip_bin]['references'] = array();
559 $ret['addrlist'][$ip_bin]['lbrefs'] = array();
560 $ret['addrlist'][$ip_bin]['rsrefs'] = array();
561 }
562 $tmp = array();
563 foreach (array ('object_id', 'type', 'name') as $cname)
564 $tmp[$cname] = $row[$cname];
565 $quasiobject['name'] = $row['object_name'];
566 $quasiobject['objtype_id'] = $row['objtype_id'];
567 $quasiobject['objtype_name'] = $row['objtype_name'];
568 $tmp['object_name'] = displayedName ($quasiobject);
569 $ret['addrlist'][$ip_bin]['references'][] = $tmp;
570 }
571 $result->closeCursor();
572 unset ($result);
573
574 $query = "select vs_id, inet_ntoa(vip) as ip, vport, proto, " .
575 "object_id, objtype_id, ro.name, dict_value as objtype_name from " .
576 "IPVirtualService as vs inner join IPLoadBalancer as lb on vs.id = lb.vs_id " .
577 "inner join RackObject as ro on lb.object_id = ro.id " .
578 "left join Dictionary on objtype_id=dict_key " .
579 "natural join Chapter " .
580 "where vip between ${db_first} and ${db_last} " .
581 "and chapter_name = 'RackObjectType'" .
582 "order by vport, proto, ro.name, object_id";
583 $result = useSelectBlade ($query);
584 while ($row = $result->fetch (PDO::FETCH_ASSOC))
585 {
586 $ip_bin = ip2long ($row['ip']);
587 if (!isset ($ret['addrlist'][$ip_bin]))
588 {
589 $ret['addrlist'][$ip_bin] = array();
590 $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
591 $ret['addrlist'][$ip_bin]['name'] = '';
592 $ret['addrlist'][$ip_bin]['reserved'] = 'no';
593 $ret['addrlist'][$ip_bin]['references'] = array();
594 $ret['addrlist'][$ip_bin]['lbrefs'] = array();
595 $ret['addrlist'][$ip_bin]['rsrefs'] = array();
596 }
597 $tmp = $qbject = array();
598 foreach (array ('object_id', 'vport', 'proto', 'vs_id') as $cname)
599 $tmp[$cname] = $row[$cname];
600 foreach (array ('name', 'objtype_id', 'objtype_name') as $cname)
601 $qobject[$cname] = $row[$cname];
602 $tmp['object_name'] = displayedName ($qobject);
603 $ret['addrlist'][$ip_bin]['lbrefs'][] = $tmp;
604 }
605 $result->closeCursor();
606 unset ($result);
607
608 $query = "select inet_ntoa(rsip) as ip, rsport, rspool_id, rsp.name as rspool_name from " .
609 "IPRealServer as rs inner join IPRSPool as rsp on rs.rspool_id = rsp.id " .
610 "where rsip between ${db_first} and ${db_last} " .
611 "order by ip, rsport, rspool_id";
612 $result = useSelectBlade ($query);
613 while ($row = $result->fetch (PDO::FETCH_ASSOC))
614 {
615 $ip_bin = ip2long ($row['ip']);
616 if (!isset ($ret['addrlist'][$ip_bin]))
617 {
618 $ret['addrlist'][$ip_bin] = array();
619 $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
620 $ret['addrlist'][$ip_bin]['name'] = '';
621 $ret['addrlist'][$ip_bin]['reserved'] = 'no';
622 $ret['addrlist'][$ip_bin]['references'] = array();
623 $ret['addrlist'][$ip_bin]['lbrefs'] = array();
624 $ret['addrlist'][$ip_bin]['rsrefs'] = array();
625 }
626 $tmp = array();
627 foreach (array ('rspool_id', 'rsport', 'rspool_name') as $cname)
628 $tmp[$cname] = $row[$cname];
629 $ret['addrlist'][$ip_bin]['rsrefs'][] = $tmp;
630 }
631
632 return $ret;
633 }
634
635 // Don't require any records in IPAddress, but if there is one,
636 // merge the data between getting allocation list. Collect enough data
637 // to call displayedName() ourselves.
638 function getIPAddress ($ip=0)
639 {
640 $ret = array();
641 $ret['bonds'] = array();
642 // FIXME: below two aren't neither filled in with data nor rendered (ticket:23)
643 $ret['outpf'] = array();
644 $ret['inpf'] = array();
645 $ret['vslist'] = array();
646 $ret['rslist'] = array();
647 $ret['exists'] = 0;
648 $ret['name'] = '';
649 $ret['reserved'] = 'no';
650 global $dbxlink;
651 $query =
652 "select ".
653 "name, reserved ".
654 "from IPAddress ".
655 "where ip = INET_ATON('$ip') and (reserved = 'yes' or name != '')";
656 $result = $dbxlink->query ($query);
657 if ($result == NULL)
658 {
659 showError ('Query #1 failed', __FUNCTION__);
660 return NULL;
661 }
662 if ($row = $result->fetch (PDO::FETCH_ASSOC))
663 {
664 $ret['exists'] = 1;
665 $ret['name'] = $row['name'];
666 $ret['reserved'] = $row['reserved'];
667 }
668 $result->closeCursor();
669 unset ($result);
670
671 $query =
672 "select ".
673 "IPBonds.object_id as object_id, ".
674 "IPBonds.name as name, ".
675 "IPBonds.type as type, ".
676 "objtype_id, dict_value as objtype_name, " .
677 "RackObject.name as object_name ".
678 "from IPBonds join RackObject on IPBonds.object_id=RackObject.id ".
679 "left join Dictionary on objtype_id=dict_key natural join Chapter " .
680 "where IPBonds.ip=INET_ATON('$ip') ".
681 "and chapter_name = 'RackObjectType' " .
682 "order by RackObject.id, IPBonds.name";
683 $result = $dbxlink->query ($query);
684 $count=0;
685 while ($row = $result->fetch (PDO::FETCH_ASSOC))
686 {
687 $ret['bonds'][$count]['object_id'] = $row['object_id'];
688 $ret['bonds'][$count]['name'] = $row['name'];
689 $ret['bonds'][$count]['type'] = $row['type'];
690 $qo = array();
691 $qo['name'] = $row['object_name'];
692 $qo['objtype_id'] = $row['objtype_id'];
693 $qo['objtype_name'] = $row['objtype_name'];
694 $ret['bonds'][$count]['object_name'] = displayedName ($qo);
695 $count++;
696 $ret['exists'] = 1;
697 }
698 $result->closeCursor();
699 unset ($result);
700
701 $query = "select id, vport, proto, name from IPVirtualService where vip = inet_aton('${ip}')";
702 $result = $dbxlink->query ($query);
703 while ($row = $result->fetch (PDO::FETCH_ASSOC))
704 {
705 $new = $row;
706 $new['vip'] = $ip;
707 $ret['vslist'][] = $new;
708 }
709 $result->closeCursor();
710 unset ($result);
711
712 $query = "select inservice, rsport, IPRSPool.id as pool_id, IPRSPool.name as poolname from " .
713 "IPRealServer inner join IPRSPool on rspool_id = IPRSPool.id " .
714 "where rsip = inet_aton('${ip}')";
715 $result = $dbxlink->query ($query);
716 while ($row = $result->fetch (PDO::FETCH_ASSOC))
717 {
718 $new = $row;
719 $new['rsip'] = $ip;
720 $ret['rslist'][] = $new;
721 }
722 $result->closeCursor();
723 unset ($result);
724
725 return $ret;
726 }
727
728 function bindIpToObject ($ip='', $object_id=0, $name='', $type='')
729 {
730 global $dbxlink;
731
732 $range = getRangeByIp($ip);
733 if (!$range)
734 return 'Non-existant ip address. Try adding IP range first';
735
736 $result = useInsertBlade
737 (
738 'IPBonds',
739 array
740 (
741 'ip' => "INET_ATON('$ip')",
742 'object_id' => "'${object_id}'",
743 'name' => "'${name}'",
744 'type' => "'${type}'"
745 )
746 );
747 return $result ? '' : 'useInsertBlade() failed in bindIpToObject()';
748 }
749
750 // This function looks up 'has_problems' flag for 'T' atoms
751 // and modifies 'hl' key. May be, this should be better done
752 // in getRackData(). We don't honour 'skipped' key, because
753 // the function is also used for thumb creation.
754 function markupObjectProblems (&$rackData)
755 {
756 for ($i = $rackData['height']; $i > 0; $i--)
757 for ($locidx = 0; $locidx < 3; $locidx++)
758 if ($rackData[$i][$locidx]['state'] == 'T')
759 {
760 $object = getObjectInfo ($rackData[$i][$locidx]['object_id']);
761 if ($object['has_problems'] == 'yes')
762 {
763 // Object can be already highlighted.
764 if (isset ($rackData[$i][$locidx]['hl']))
765 $rackData[$i][$locidx]['hl'] = $rackData[$i][$locidx]['hl'] . 'w';
766 else
767 $rackData[$i][$locidx]['hl'] = 'w';
768 }
769 }
770 }
771
772 function search_cmpObj ($a, $b)
773 {
774 return ($a['score'] > $b['score'] ? -1 : 1);
775 }
776
777 // This function performs search and then calculates score for each result.
778 // Given previous search results in $objects argument, it adds new results
779 // to the array and updates score for existing results, if it is greater than
780 // existing score.
781 function mergeSearchResults (&$objects, $terms, $fieldname)
782 {
783 global $dbxlink;
784 $query =
785 "select name, label, asset_no, barcode, ro.id, dict_key as objtype_id, " .
786 "dict_value as objtype_name, asset_no from RackObject as ro inner join Dictionary " .
787 "on objtype_id = dict_key natural join Chapter where chapter_name = 'RackObjectType' and ";
788 $count = 0;
789 foreach (explode (' ', $terms) as $term)
790 {
791 if ($count) $query .= ' or ';
792 $query .= "${fieldname} like '%$term%'";
793 $count++;
794 }
795 $result = useSelectBlade ($query);
796 // FIXME: this dead call was executed 4 times per 1 object search!
797 // $typeList = getObjectTypeList();
798 $clist = array ('id', 'name', 'label', 'asset_no', 'barcode', 'objtype_id', 'objtype_name');
799 while ($row = $result->fetch(PDO::FETCH_ASSOC))
800 {
801 foreach ($clist as $cname)
802 $object[$cname] = $row[$cname];
803 $object['score'] = 0;
804 $object['dname'] = displayedName ($object);
805 unset ($object['objtype_id']);
806 foreach (explode (' ', $terms) as $term)
807 if (strstr ($object['name'], $term))
808 $object['score'] += 1;
809 unset ($object['name']);
810 if (!isset ($objects[$row['id']]))
811 $objects[$row['id']] = $object;
812 elseif ($objects[$row['id']]['score'] < $object['score'])
813 $objects[$row['id']]['score'] = $object['score'];
814 }
815 return $objects;
816 }
817
818 function getObjectSearchResults ($terms)
819 {
820 $objects = array();
821 mergeSearchResults ($objects, $terms, 'name');
822 mergeSearchResults ($objects, $terms, 'label');
823 mergeSearchResults ($objects, $terms, 'asset_no');
824 mergeSearchResults ($objects, $terms, 'barcode');
825 if (count ($objects) == 1)
826 usort ($objects, 'search_cmpObj');
827 return $objects;
828 }
829
830 // This function removes all colons and dots from a string.
831 function l2addressForDatabase ($string)
832 {
833 if (empty ($string))
834 return 'NULL';
835 $pieces = explode (':', $string);
836 // This workaround is for SunOS ifconfig.
837 foreach ($pieces as &$byte)
838 if (strlen ($byte) == 1)
839 $byte = '0' . $byte;
840 // And this workaround is for PHP.
841 unset ($byte);
842 $string = implode ('', $pieces);
843 $pieces = explode ('.', $string);
844 $string = implode ('', $pieces);
845 $string = strtoupper ($string);
846 return "'$string'";
847 }
848
849 function l2addressFromDatabase ($string)
850 {
851 switch (strlen ($string))
852 {
853 case 12: // Ethernet
854 case 16: // FireWire
855 $ret = implode (':', str_split ($string, 2));
856 break;
857 default:
858 $ret = $string;
859 break;
860 }
861 return $ret;
862 }
863
864 // The following 2 functions return previous and next rack IDs for
865 // a given rack ID. The order of racks is the same as in renderRackspace()
866 // or renderRow().
867 function getPrevIDforRack ($row_id = 0, $rack_id = 0)
868 {
869 if ($row_id <= 0 or $rack_id <= 0)
870 {
871 showError ('Invalid arguments passed', __FUNCTION__);
872 return NULL;
873 }
874 $rackList = getRacksForRow ($row_id);
875 doubleLink ($rackList);
876 if (isset ($rackList[$rack_id]['prev_key']))
877 return $rackList[$rack_id]['prev_key'];
878 return NULL;
879 }
880
881 function getNextIDforRack ($row_id = 0, $rack_id = 0)
882 {
883 if ($row_id <= 0 or $rack_id <= 0)
884 {
885 showError ('Invalid arguments passed', __FUNCTION__);
886 return NULL;
887 }
888 $rackList = getRacksForRow ($row_id);
889 doubleLink ($rackList);
890 if (isset ($rackList[$rack_id]['next_key']))
891 return $rackList[$rack_id]['next_key'];
892 return NULL;
893 }
894
895 // This function finds previous and next array keys for each array key and
896 // modifies its argument accordingly.
897 function doubleLink (&$array)
898 {
899 $prev_key = NULL;
900 foreach (array_keys ($array) as $key)
901 {
902 if ($prev_key)
903 {
904 $array[$key]['prev_key'] = $prev_key;
905 $array[$prev_key]['next_key'] = $key;
906 }
907 $prev_key = $key;
908 }
909 }
910
911 // After applying usort() to a rack list we will lose original array keys.
912 // This function restores the keys so they are equal to rack IDs.
913 function restoreRackIDs ($racks)
914 {
915 $ret = array();
916 foreach ($racks as $rack)
917 $ret[$rack['id']] = $rack;
918 return $ret;
919 }
920
921 function sortTokenize ($a, $b)
922 {
923 $aold='';
924 while ($a != $aold)
925 {
926 $aold=$a;
927 $a = ereg_replace('[^a-zA-Z0-9]',' ',$a);
928 $a = ereg_replace('([0-9])([a-zA-Z])','\\1 \\2',$a);
929 $a = ereg_replace('([a-zA-Z])([0-9])','\\1 \\2',$a);
930 }
931
932 $bold='';
933 while ($b != $bold)
934 {
935 $bold=$b;
936 $b = ereg_replace('[^a-zA-Z0-9]',' ',$b);
937 $b = ereg_replace('([0-9])([a-zA-Z])','\\1 \\2',$b);
938 $b = ereg_replace('([a-zA-Z])([0-9])','\\1 \\2',$b);
939 }
940
941
942
943 $ar = explode(' ', $a);
944 $br = explode(' ', $b);
945 for ($i=0; $i<count($ar) && $i<count($br); $i++)
946 {
947 $ret = 0;
948 if (is_numeric($ar[$i]) and is_numeric($br[$i]))
949 $ret = ($ar[$i]==$br[$i])?0:($ar[$i]<$br[$i]?-1:1);
950 else
951 $ret = strcasecmp($ar[$i], $br[$i]);
952 if ($ret != 0)
953 return $ret;
954 }
955 if ($i<count($ar))
956 return 1;
957 if ($i<count($br))
958 return -1;
959 return 0;
960 }
961
962 function sortByName ($a, $b)
963 {
964 return sortTokenize($a['name'], $b['name']);
965 }
966
967 function sortRacks ($a, $b)
968 {
969 return sortTokenize($a['row_name'] . ': ' . $a['name'], $b['row_name'] . ': ' . $b['name']);
970 }
971
972 function eq ($a, $b)
973 {
974 return $a==$b;
975 }
976
977 function neq ($a, $b)
978 {
979 return $a!=$b;
980 }
981
982 function countRefsOfType ($refs, $type, $eq)
983 {
984 $count=0;
985 foreach ($refs as $ref)
986 {
987 if ($eq($ref['type'], $type))
988 $count++;
989 }
990 return $count;
991 }
992
993 function sortEmptyPorts ($a, $b)
994 {
995 $objname_cmp = sortTokenize($a['Object_name'], $b['Object_name']);
996 if ($objname_cmp == 0)
997 {
998 return sortTokenize($a['Port_name'], $b['Port_name']);
999 }
1000 return $objname_cmp;
1001 }
1002
1003 function sortObjectAddressesAndNames ($a, $b)
1004 {
1005 $objname_cmp = sortTokenize($a['object_name'], $b['object_name']);
1006 if ($objname_cmp == 0)
1007 {
1008 $name_a = (isset ($a['port_name'])) ? $a['port_name'] : '';
1009 $name_b = (isset ($b['port_name'])) ? $b['port_name'] : '';
1010 $objname_cmp = sortTokenize($name_a, $name_b);
1011 if ($objname_cmp == 0)
1012 sortTokenize($a['ip'], $b['ip']);
1013 return $objname_cmp;
1014 }
1015 return $objname_cmp;
1016 }
1017
1018
1019
1020 function sortAddresses ($a, $b)
1021 {
1022 $name_cmp = sortTokenize($a['name'], $b['name']);
1023 if ($name_cmp == 0)
1024 {
1025 return sortTokenize($a['ip'], $b['ip']);
1026 }
1027 return $name_cmp;
1028 }
1029
1030 // This function expands port compat list into a matrix.
1031 function buildPortCompatMatrixFromList ($portTypeList, $portCompatList)
1032 {
1033 $matrix = array();
1034 // Create type matrix and markup compatible types.
1035 foreach (array_keys ($portTypeList) as $type1)
1036 foreach (array_keys ($portTypeList) as $type2)
1037 $matrix[$type1][$type2] = FALSE;
1038 foreach ($portCompatList as $pair)
1039 $matrix[$pair['type1']][$pair['type2']] = TRUE;
1040 return $matrix;
1041 }
1042
1043 function newPortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto, $description)
1044 {
1045 global $dbxlink;
1046
1047 $range = getRangeByIp($localip);
1048 if (!$range)
1049 return "$localip: Non existant ip";
1050
1051 $range = getRangeByIp($remoteip);
1052 if (!$range)
1053 return "$remoteip: Non existant ip";
1054
1055 if ( ($localport <= 0) or ($localport >= 65536) )
1056 return "$localport: invaild port";
1057
1058 if ( ($remoteport <= 0) or ($remoteport >= 65536) )
1059 return "$remoteport: invaild port";
1060
1061 $query =
1062 "insert into PortForwarding set object_id='$object_id', localip=INET_ATON('$localip'), remoteip=INET_ATON('$remoteip'), localport='$localport', remoteport='$remoteport', proto='$proto', description='$description'";
1063 $result = $dbxlink->exec ($query);
1064
1065 return '';
1066 }
1067
1068 function deletePortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto)
1069 {
1070 global $dbxlink;
1071
1072 $query =
1073 "delete from PortForwarding where object_id='$object_id' and localip=INET_ATON('$localip') and remoteip=INET_ATON('$remoteip') and localport='$localport' and remoteport='$remoteport' and proto='$proto'";
1074 $result = $dbxlink->exec ($query);
1075 return '';
1076 }
1077
1078 function updatePortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto, $description)
1079 {
1080 global $dbxlink;
1081
1082 $query =
1083 "update PortForwarding set description='$description' where object_id='$object_id' and localip=INET_ATON('$localip') and remoteip=INET_ATON('$remoteip') and localport='$localport' and remoteport='$remoteport' and proto='$proto'";
1084 $result = $dbxlink->exec ($query);
1085 return '';
1086 }
1087
1088 function getNATv4ForObject ($object_id)
1089 {
1090 $ret = array();
1091 $ret['out'] = array();
1092 $ret['in'] = array();
1093 $query =
1094 "select ".
1095 "proto, ".
1096 "INET_NTOA(localip) as localip, ".
1097 "localport, ".
1098 "INET_NTOA(remoteip) as remoteip, ".
1099 "remoteport, ".
1100 "ipa1.name as local_addr_name, " .
1101 "ipa2.name as remote_addr_name, " .
1102 "description ".
1103 "from PortForwarding ".
1104 "left join IPAddress as ipa1 on PortForwarding.localip = ipa1.ip " .
1105 "left join IPAddress as ipa2 on PortForwarding.remoteip = ipa2.ip " .
1106 "where object_id='$object_id' ".
1107 "order by localip, localport, proto, remoteip, remoteport";
1108 $result = useSelectBlade ($query);
1109 $count=0;
1110 while ($row = $result->fetch (PDO::FETCH_ASSOC))
1111 {
1112 foreach (array ('proto', 'localport', 'localip', 'remoteport', 'remoteip', 'description', 'local_addr_name', 'remote_addr_name') as $cname)
1113 $ret['out'][$count][$cname] = $row[$cname];
1114 $count++;
1115 }
1116 $result->closeCursor();
1117 unset ($result);
1118
1119 $query =
1120 "select ".
1121 "proto, ".
1122 "INET_NTOA(localip) as localip, ".
1123 "localport, ".
1124 "INET_NTOA(remoteip) as remoteip, ".
1125 "remoteport, ".
1126 "PortForwarding.object_id as object_id, ".
1127 "RackObject.name as object_name, ".
1128 "description ".
1129 "from ((PortForwarding join IPBonds on remoteip=IPBonds.ip) join RackObject on PortForwarding.object_id=RackObject.id) ".
1130 "where IPBonds.object_id='$object_id' ".
1131 "order by remoteip, remoteport, proto, localip, localport";
1132 $result = useSelectBlade ($query);
1133 $count=0;
1134 while ($row = $result->fetch (PDO::FETCH_ASSOC))
1135 {
1136 foreach (array ('proto', 'localport', 'localip', 'remoteport', 'remoteip', 'object_id', 'object_name', 'description') as $cname)
1137 $ret['in'][$count][$cname] = $row[$cname];
1138 $count++;
1139 }
1140 $result->closeCursor();
1141
1142 return $ret;
1143 }
1144
1145 // This function returns an array of single element of object's FQDN attribute,
1146 // if FQDN is set. The next choice is object's common name, if it looks like a
1147 // hostname. Otherwise an array of all 'regular' IP addresses of the
1148 // object is returned (which may appear 0 and more elements long).
1149 function findAllEndpoints ($object_id, $fallback = '')
1150 {
1151 $values = getAttrValues ($object_id);
1152 foreach ($values as $record)
1153 if ($record['name'] == 'FQDN' && !empty ($record['value']))
1154 return array ($record['value']);
1155 $addresses = getObjectAddresses ($object_id);
1156 $regular = array();
1157 foreach ($addresses as $idx => $address)
1158 if ($address['type'] == 'regular')
1159 $regular[] = $address['ip'];
1160 if (!count ($regular) && !empty ($fallback))
1161 return array ($fallback);
1162 return $regular;
1163 }
1164
1165 // Some records in the dictionary may be written as plain text or as Wiki
1166 // link in the following syntax:
1167 // 1. word
1168 // 2. [[word URL]] // FIXME: this isn't working
1169 // 3. [[word word word | URL]]
1170 // This function parses the line and returns text suitable for either A
1171 // (rendering <A HREF>) or O (for <OPTION>).
1172 function parseWikiLink ($line, $which, $strip_optgroup = FALSE)
1173 {
1174 if (preg_match ('/^\[\[.+\]\]$/', $line) == 0)
1175 {
1176 if ($strip_optgroup)
1177 return ereg_replace ('^.+\^', '', ereg_replace ('^(.+)&', '\\1 ', $line));
1178 else
1179 return $line;
1180 }
1181 $line = preg_replace ('/^\[\[(.+)\]\]$/', '$1', $line);
1182 $s = explode ('|', $line);
1183 $o_value = trim ($s[0]);
1184 if ($strip_optgroup)
1185 $o_value = ereg_replace ('^.+\^', '', ereg_replace ('^(.+)&', '\\1 ', $o_value));
1186 $a_value = trim ($s[1]);
1187 if ($which == 'a')
1188 return "<a href='${a_value}'>${o_value}</a>";
1189 if ($which == 'o')
1190 return $o_value;
1191 }
1192
1193 function buildVServiceName ($vsinfo = NULL)
1194 {
1195 if ($vsinfo == NULL)
1196 {
1197 showError ('NULL argument', __FUNCTION__);
1198 return NULL;
1199 }
1200 return $vsinfo['vip'] . ':' . $vsinfo['vport'] . '/' . $vsinfo['proto'];
1201 }
1202
1203 function buildRSPoolName ($rspool = NULL)
1204 {
1205 if ($rspool == NULL)
1206 {
1207 showError ('NULL argument', __FUNCTION__);
1208 return NULL;
1209 }
1210 return strlen ($rspool['name']) ? $rspool['name'] : 'ANONYMOUS pool';
1211 }
1212
1213 // rackspace usage for a single rack
1214 // (T + W + U) / (height * 3 - A)
1215 function getRSUforRack ($data = NULL)
1216 {
1217 if ($data == NULL)
1218 {
1219 showError ('Invalid argument', __FUNCTION__);
1220 return NULL;
1221 }
1222 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
1223 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1224 for ($locidx = 0; $locidx < 3; $locidx++)
1225 $counter[$data[$unit_no][$locidx]['state']]++;
1226 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
1227 }
1228
1229 // Same for row.
1230 function getRSUforRackRow ($rowData = NULL)
1231 {
1232 if ($rowData === NULL)
1233 {
1234 showError ('Invalid argument', __FUNCTION__);
1235 return NULL;
1236 }
1237 if (!count ($rowData))
1238 return 0;
1239 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
1240 $total_height = 0;
1241 foreach (array_keys ($rowData) as $rack_id)
1242 {
1243 $data = getRackData ($rack_id);
1244 $total_height += $data['height'];
1245 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
1246 for ($locidx = 0; $locidx < 3; $locidx++)
1247 $counter[$data[$unit_no][$locidx]['state']]++;
1248 }
1249 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
1250 }
1251
1252 function getObjectCount ($rackData)
1253 {
1254 $objects = array();
1255 for ($i = $rackData['height']; $i > 0; $i--)
1256 for ($locidx = 0; $locidx < 3; $locidx++)
1257 if
1258 (
1259 $rackData[$i][$locidx]['state'] == 'T' and
1260 !in_array ($rackData[$i][$locidx]['object_id'], $objects)
1261 )
1262 $objects[] = $rackData[$i][$locidx]['object_id'];
1263 return count ($objects);
1264 }
1265
1266 // Perform substitutions and return resulting string
1267 function apply_macros ($macros, $subject)
1268 {
1269 $ret = $subject;
1270 foreach ($macros as $search => $replace)
1271 $ret = str_replace ($search, $replace, $ret);
1272 return $ret;
1273 }
1274
1275 // Make sure the string is always wrapped with LF characters
1276 function lf_wrap ($str)
1277 {
1278 $ret = trim ($str, "\r\n");
1279 if (!empty ($ret))
1280 $ret .= "\n";
1281 return $ret;
1282 }
1283
1284 // Adopted from Mantis BTS code.
1285 function string_insert_hrefs ($s)
1286 {
1287 if (getConfigVar ('DETECT_URLS') != 'yes')
1288 return $s;
1289 # Find any URL in a string and replace it by a clickable link
1290 $s = preg_replace( '/(([[:alpha:]][-+.[:alnum:]]*):\/\/(%[[:digit:]A-Fa-f]{2}|[-_.!~*\';\/?%^\\\\:@&={\|}+$#\(\),\[\][:alnum:]])+)/se',
1291 "'<a href=\"'.rtrim('\\1','.').'\">\\1</a> [<a href=\"'.rtrim('\\1','.').'\" target=\"_blank\">^</a>]'",
1292 $s);
1293 $s = preg_replace( '/\b' . email_regex_simple() . '\b/i',
1294 '<a href="mailto:\0">\0</a>',
1295 $s);
1296 return $s;
1297 }
1298
1299 // Idem.
1300 function email_regex_simple ()
1301 {
1302 return "(([a-z0-9!#*+\/=?^_{|}~-]+(?:\.[a-z0-9!#*+\/=?^_{|}~-]+)*)" . # recipient
1303 "\@((?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?))"; # @domain
1304 }
1305
1306 // Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
1307 // for the requested object_type_id.
1308 function getAutoPorts ($type_id)
1309 {
1310 $ret = array();
1311 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
1312 foreach ($typemap as $equation)
1313 {
1314 $tmp = explode ('=', $equation);
1315 if (count ($tmp) != 2)
1316 continue;
1317 $objtype_id = $tmp[0];
1318 if ($objtype_id != $type_id)
1319 continue;
1320 $portlist = $tmp[1];
1321 foreach (explode ('+', $portlist) as $product)
1322 {
1323 $tmp = explode ('*', $product);
1324 if (count ($tmp) != 3)
1325 continue;
1326 $nports = $tmp[0];
1327 $port_type = $tmp[1];
1328 $format = $tmp[2];
1329 for ($i = 0; $i < $nports; $i++)
1330 $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i));
1331 }
1332 }
1333 return $ret;
1334 }
1335
1336 // Find if a particular tag id exists on the tree, then attach the
1337 // given child tag to it. If the parent tag doesn't exist, return FALSE.
1338 function attachChildTag (&$tree, $parent_id, $child_id, $child_info)
1339 {
1340 foreach ($tree as $tagid => $taginfo)
1341 {
1342 if ($tagid == $parent_id)
1343 {
1344 $tree[$tagid]['kids'][$child_id] = $child_info;
1345 return TRUE;
1346 }
1347 elseif (attachChildTag ($tree[$tagid]['kids'], $parent_id, $child_id, $child_info))
1348 return TRUE;
1349 }
1350 return FALSE;
1351 }
1352
1353 // Build a tree from the tag list and return it.
1354 function getTagTree ()
1355 {
1356 global $taglist;
1357 $mytaglist = $taglist;
1358 $ret = array();
1359 while (count ($mytaglist) > 0)
1360 {
1361 $picked = FALSE;
1362 foreach ($mytaglist as $tagid => $taginfo)
1363 {
1364 $taginfo['kids'] = array();
1365 if ($taginfo['parent_id'] == NULL)
1366 {
1367 $ret[$tagid] = $taginfo;
1368 $picked = TRUE;
1369 unset ($mytaglist[$tagid]);
1370 }
1371 elseif (attachChildTag ($ret, $taginfo['parent_id'], $tagid, $taginfo))
1372 {
1373 $picked = TRUE;
1374 unset ($mytaglist[$tagid]);
1375 }
1376 }
1377 if (!$picked) // Only orphaned items on the list.
1378 break;
1379 }
1380 return $ret;
1381 }
1382
1383 function serializeTags ($trail)
1384 {
1385 $comma = '';
1386 $ret = '';
1387 foreach ($trail as $taginfo)
1388 {
1389 $ret .= $comma . $taginfo['tag'];
1390 $comma = ', ';
1391 }
1392 return $ret;
1393 }
1394
1395 // a helper for getTrailExpansion()
1396 function traceTrail ($tree, $trail)
1397 {
1398 // For each tag find its path from the root, then combine items
1399 // of all paths and add them to the trail, if they aren't there yet.
1400 $ret = array();
1401 foreach ($tree as $taginfo1)
1402 {
1403 $hit = FALSE;
1404 foreach ($trail as $taginfo2)
1405 if ($taginfo1['id'] == $taginfo2['id'])
1406 {
1407 $hit = TRUE;
1408 break;
1409 }
1410 if (count ($taginfo1['kids']) > 0)
1411 {
1412 $subsearch = traceTrail ($taginfo1['kids'], $trail);
1413 if (count ($subsearch))
1414 {
1415 $hit = TRUE;
1416 $ret = array_merge ($ret, $subsearch);
1417 }
1418 }
1419 if ($hit)
1420 $ret[] = $taginfo1;
1421 }
1422 return $ret;
1423 }
1424
1425 // For each tag add all its parent tags onto the list. Don't expect anything
1426 // except user's tags on the trail.
1427 function getTrailExpansion ($trail)
1428 {
1429 global $tagtree;
1430 return traceTrail ($tagtree, $trail);
1431 }
1432
1433 // Return the list of missing implicit tags.
1434 function getImplicitTags ($oldtags)
1435 {
1436 $ret = array();
1437 $newtags = getTrailExpansion ($oldtags);
1438 foreach ($newtags as $newtag)
1439 {
1440 $already_exists = FALSE;
1441 foreach ($oldtags as $oldtag)
1442 if ($newtag['id'] == $oldtag['id'])
1443 {
1444 $already_exists = TRUE;
1445 break;
1446 }
1447 if ($already_exists)
1448 continue;
1449 $ret[] = array ('id' => $newtag['id'], 'tag' => $newtag['tag'], 'parent_id' => $newtag['parent_id']);
1450 }
1451 return $ret;
1452 }
1453
1454 // Minimize the trail: exclude all implicit tags and return the resulting trail.
1455 function getExplicitTagsOnly ($trail, $tree = NULL)
1456 {
1457 global $tagtree;
1458 if ($tree === NULL)
1459 $tree = $tagtree;
1460 $ret = array();
1461 foreach ($tree as $taginfo)
1462 {
1463 if (isset ($taginfo['kids']))
1464 {
1465 $harvest = getExplicitTagsOnly ($trail, $taginfo['kids']);
1466 if (count ($harvest) > 0)
1467 {
1468 $ret = array_merge ($ret, $harvest);
1469 continue;
1470 }
1471 }
1472 // This tag isn't implicit, test is for being explicit.
1473 foreach ($trail as $testtag)
1474 if ($taginfo['id'] == $testtag['id'])
1475 {
1476 $ret[] = $testtag;
1477 break;
1478 }
1479 }
1480 return $ret;
1481 }
1482
1483 // Maximize the trail: for each tag add all tags, for which it is direct or indirect parent.
1484 // Unlike other functions, this one accepts and returns a list of integer tag IDs, not
1485 // a list of tag structures.
1486 function complementByKids ($idlist, $tree = NULL, $getall = FALSE)
1487 {
1488 global $tagtree;
1489 if ($tree === NULL)
1490 $tree = $tagtree;
1491 $getallkids = $getall;
1492 $ret = array();
1493 foreach ($tree as $taginfo)
1494 {
1495 foreach ($idlist as $test_id)
1496 if ($getall or $taginfo['id'] == $test_id)
1497 {
1498 $ret[] = $taginfo['id'];
1499 // Once matched node makes all sub-nodes match, but don't make
1500 // a mistake of matching every other node at the current level.
1501 $getallkids = TRUE;
1502 break;
1503 }
1504 if (isset ($taginfo['kids']))
1505 $ret = array_merge ($ret, complementByKids ($idlist, $taginfo['kids'], $getallkids));
1506 $getallkids = FALSE;
1507 }
1508 return $ret;
1509 }
1510
1511 function loadRackObjectAutoTags()
1512 {
1513 assertUIntArg ('object_id', __FUNCTION__);
1514 $object_id = $_REQUEST['object_id'];
1515 $oinfo = getObjectInfo ($object_id);
1516 $ret = array();
1517 $ret[] = array ('tag' => '$id_' . $_REQUEST['object_id']);
1518 $ret[] = array ('tag' => '$any_object');
1519 return $ret;
1520 }
1521
1522 function loadIPv4PrefixAutoTags()
1523 {
1524 assertUIntArg ('id', __FUNCTION__);
1525 $subnet = getIPRange ($_REQUEST['id']);
1526 $ret = array();
1527 $ret[] = array ('tag' => '$id_' . $_REQUEST['id']);
1528 $ret[] = array ('tag' => '$ipv4net-' . str_replace ('.', '-', $subnet['ip']) . '-' . $subnet['mask']);
1529 // FIXME: find and list tags for all parent networks
1530 $ret[] = array ('tag' => '$any_ipv4net');
1531 $ret[] = array ('tag' => '$any_net');
1532 return $ret;
1533 }
1534
1535 function loadRackAutoTags()
1536 {
1537 assertUIntArg ('rack_id', __FUNCTION__);
1538 $ret = array();
1539 $ret[] = array ('tag' => '$id_' . $_REQUEST['rack_id']);
1540 $ret[] = array ('tag' => '$any_rack');
1541 return $ret;
1542 }
1543
1544 function loadIPv4AddressAutoTags()
1545 {
1546 assertIPv4Arg ('ip', __FUNCTION__);
1547 $ret = array();
1548 $ret[] = array ('tag' => '$ipv4net-' . str_replace ('.', '-', $subnet['ip']) . '-32');
1549 // FIXME: find and list tags for all parent networks
1550 $ret[] = array ('tag' => '$any_ipv4net');
1551 $ret[] = array ('tag' => '$any_net');
1552 return $ret;
1553 }
1554
1555 function loadIPv4VSAutoTags()
1556 {
1557 assertUIntArg ('id', __FUNCTION__);
1558 $ret = array();
1559 $ret[] = array ('tag' => '$id_' . $_REQUEST['id']);
1560 $ret[] = array ('tag' => '$any_ipv4vs');
1561 $ret[] = array ('tag' => '$any_vs');
1562 return $ret;
1563 }
1564
1565 function loadIPv4RSPoolAutoTags()
1566 {
1567 assertUIntArg ('pool_id', __FUNCTION__);
1568 $ret = array();
1569 $ret[] = array ('tag' => '$id_' . $_REQUEST['pool_id']);
1570 $ret[] = array ('tag' => '$any_ipv4rspool');
1571 $ret[] = array ('tag' => '$any_rspool');
1572 return $ret;
1573 }
1574
1575 function getGlobalAutoTags()
1576 {
1577 global $remote_username, $accounts;
1578 $ret = array();
1579 $user_id = 0;
1580 foreach ($accounts as $a)
1581 if ($a['user_name'] == $remote_username)
1582 {
1583 $user_id = $a['user_id'];
1584 break;
1585 }
1586 $ret[] = array ('tag' => '$username_' . $remote_username);
1587 $ret[] = array ('tag' => '$userid_' . $user_id);
1588 return $ret;
1589 }
1590
1591 // Build a tag trail from supplied tag id list and return it.
1592 function buildTrailFromIds ($tagidlist)
1593 {
1594 global $taglist;
1595 $ret = array();
1596 foreach ($tagidlist as $tag_id)
1597 if (isset ($taglist[$tag_id]))
1598 $ret[] = $taglist[$tag_id];
1599 return $ret;
1600 }
1601
1602 // Process a given tag tree and return only meaningful branches. The resulting
1603 // (sub)tree will have refcnt leaves on every last branch.
1604 function getObjectiveTagTree ($tree, $realm)
1605 {
1606 $ret = array();
1607 foreach ($tree as $taginfo)
1608 {
1609 $subsearch = array();
1610 $pick = FALSE;
1611 if (count ($taginfo['kids']))
1612 {
1613 $subsearch = getObjectiveTagTree ($taginfo['kids'], $realm);
1614 $pick = count ($subsearch) > 0;
1615 }
1616 if (isset ($taginfo['refcnt'][$realm]))
1617 $pick = TRUE;
1618 if (!$pick)
1619 continue;
1620 $ret[] = array
1621 (
1622 'id' => $taginfo['id'],
1623 'tag' => $taginfo['tag'],
1624 'parent_id' => $taginfo['parent_id'],
1625 'refcnt' => $taginfo['refcnt'],
1626 'kids' => $subsearch
1627 );
1628 }
1629 return $ret;
1630 }
1631
1632 function getStringFromTrail ($trail)
1633 {
1634 $str = '';
1635 $comma = '';
1636 foreach ($trail as $tag)
1637 {
1638 $str .= $comma . $tag['id'];
1639 $comma = ',';
1640 }
1641 return $str;
1642 }
1643
1644 function getTagFilter ()
1645 {
1646 return isset ($_REQUEST['tagfilter']) ? complementByKids ($_REQUEST['tagfilter']) : array();
1647 }
1648 ?>