r1829 + done some search overhaul according to ticket:20
[racktables] / inc / functions.php
index 101b98a..1b314b7 100644 (file)
@@ -1,4 +1,4 @@
-<?
+<?php
 /*
 *
 *  This file is a library of computational functions for RackTables.
@@ -57,6 +57,10 @@ function rectHeight ($rackData, $startRow, $template_idx)
                        {
                                if (isset ($rackData[$startRow - $height][$locidx]['skipped']))
                                        break 2;
+                               if (isset ($rackData[$startRow - $height][$locidx]['rowspan']))
+                                       break 2;
+                               if (isset ($rackData[$startRow - $height][$locidx]['colspan']))
+                                       break 2;
                                if ($rackData[$startRow - $height][$locidx]['state'] != 'T')
                                        break 2;
                                if ($object_id == 0)
@@ -71,7 +75,9 @@ function rectHeight ($rackData, $startRow, $template_idx)
                $height++;
        }
        while ($startRow - $height > 0);
-//     echo "for startRow==${startRow} and template==(${template[0]}, ${template[1]}, ${template[2]}) height==${height}<br>\n";
+#      echo "for startRow==${startRow} and template==(" . ($template[$template_idx][0] ? 'T' : 'F');
+#      echo ', ' . ($template[$template_idx][1] ? 'T' : 'F') . ', ' . ($template[$template_idx][2] ? 'T' : 'F');
+#      echo ") height==${height}<br>\n";
        return $height;
 }
 
@@ -88,14 +94,16 @@ function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
                        if ($template[$template_idx][$locidx])
                        {
                                // Add colspan/rowspan to the first row met and mark the following ones to skip.
+                               // Explicitly show even single-cell spanned atoms, because rectHeight()
+                               // is expeciting this data for correct calculation.
                                if ($colspan != 0)
                                        $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
                                else
                                {
                                        $colspan = $templateWidth[$template_idx];
-                                       if ($colspan > 1)
+                                       if ($colspan >= 1)
                                                $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
-                                       if ($maxheight > 1)
+                                       if ($maxheight >= 1)
                                                $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
                                }
                        }
@@ -104,44 +112,52 @@ function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
        return;
 }
 
-// This function finds rowspan/solspan/skipped atom attributes for renderRack()
-// What we actually have to do is to find all possible rectangles for each objects
-// and then find the widest of those with the maximal square.
+// This function sets rowspan/solspan/skipped atom attributes for renderRack()
+// What we actually have to do is to find _all_ possible rectangles for each unit
+// and then select the widest of those with the maximal square.
 function markAllSpans (&$rackData = NULL)
 {
        if ($rackData == NULL)
        {
-               showError ('Invalid rackData in markupAllSpans()');
+               showError ('Invalid rackData', __FUNCTION__);
                return;
        }
        for ($i = $rackData['height']; $i > 0; $i--)
+               while (markBestSpan ($rackData, $i));
+}
+
+// Calculate height of 6 possible span templates (array is presorted by width
+// descending) and mark the best (if any).
+function markBestSpan (&$rackData, $i)
+{
+       global $template, $templateWidth;
+       for ($j = 0; $j < 6; $j++)
        {
-               // calculate height of 6 possible span templates (array is presorted by width descending)
-               global $template;
-               for ($j = 0; $j < 6; $j++)
-                       $height[$j] = rectHeight ($rackData, $i, $j);
-               // find the widest rectangle of those with maximal height
-               $maxheight = max ($height);
-               if ($maxheight > 0)
+               $height[$j] = rectHeight ($rackData, $i, $j);
+               $square[$j] = $height[$j] * $templateWidth[$j];
+       }
+       // find the widest rectangle of those with maximal height
+       $maxsquare = max ($square);
+       if (!$maxsquare)
+               return FALSE;
+       $best_template_index = 0;
+       for ($j = 0; $j < 6; $j++)
+               if ($square[$j] == $maxsquare)
                {
-                       $best_template_index = 0;
-                       for ($j = 0; $j < 6; $j++)
-                               if ($height[$j] == $maxheight)
-                               {
-                                       $best_template_index = $j;
-                                       break;
-                               }
-                       // distribute span marks
-                       markSpan ($rackData, $i, $maxheight, $best_template_index);
+                       $best_template_index = $j;
+                       $bestheight = $height[$j];
+                       break;
                }
-       }
+       // distribute span marks
+       markSpan ($rackData, $i, $bestheight, $best_template_index);
+       return TRUE;
 }
 
 function delRow ($row_id = 0)
 {
        if ($row_id == 0)
        {
-               showError ('Not all required args to delRow() are present.');
+               showError ('Not all required args are present.', __FUNCTION__);
                return;
        }
        if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
@@ -154,7 +170,7 @@ function delRow ($row_id = 0)
        $result = $dbxlink->query ("update RackRow set deleted = 'yes' where id=${row_id} limit 1");
        if ($result->rowCount() != 1)
        {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1');
+               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
                return;
        }
        echo 'OK<br>';
@@ -166,7 +182,7 @@ function delRack ($rack_id = 0)
 {
        if ($rack_id == 0)
        {
-               showError ('Not all required args to delRack() are present.');
+               showError ('Not all required args are present.', __FUNCTION__);
                return;
        }
        if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
@@ -179,7 +195,7 @@ function delRack ($rack_id = 0)
        $result = $dbxlink->query ("update Rack set deleted = 'yes' where id=${rack_id} limit 1");
        if ($result->rowCount() != 1)
        {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1');
+               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
                return;
        }
        echo 'OK<br>';
@@ -191,7 +207,7 @@ function delObject ($object_id = 0)
 {
        if ($object_id == 0)
        {
-               showError ('Not all required args to delObject() are present.');
+               showError ('Not all required args are present.', __FUNCTION__);
                return;
        }
        if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
@@ -204,7 +220,7 @@ function delObject ($object_id = 0)
        $result = $dbxlink->query ("update RackObject set deleted = 'yes' where id=${object_id} limit 1");
        if ($result->rowCount() != 1)
        {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1');
+               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
                return;
        }
        echo 'OK<br>';
@@ -358,13 +374,17 @@ function binInvMaskFromDec ($maskL)
        return $binmask;
 }
 
-function addRange ($range='', $name='')
+function addRange ($range='', $name='', $is_bcast = FALSE)
 {
        // $range is in x.x.x.x/x format, split into ip/mask vars
        $rangeArray = explode('/', $range);
+       if (count ($rangeArray) != 2)
+               return "Invalid IP subnet '${range}'";
        $ip = $rangeArray[0];
        $mask = $rangeArray[1];
 
+       if (empty ($ip) or empty ($mask))
+               return "Invalid IP subnet '${range}'";
        $ipL = ip2long($ip);
        $maskL = ip2long($mask);
        if ($ipL == -1 || $ipL === FALSE)
@@ -421,6 +441,14 @@ function addRange ($range='', $name='')
        $query =
                "insert into IPRanges set ip=".sprintf('%u', $ipL).", mask='$maskL', name='$name'";
        $result = $dbxlink->exec ($query);
+
+       if ($is_bcast and $maskL < 31)
+       {
+               $network_addr = long2ip ($ipL);
+               $broadcast_addr = long2ip ($ipL | binInvMaskFromDec ($maskL));
+               updateAddress ($network_addr, 'network', 'yes');
+               updateAddress ($broadcast_addr, 'broadcast', 'yes');
+       }
        return '';
 }
 
@@ -431,141 +459,238 @@ function getIPRange ($id = 0)
                "select ".
                "id as IPRanges_id, ".
                "INET_NTOA(ip) as IPRanges_ip, ".
-               "ip as IPRanges_ip_bin, ".
                "mask as IPRanges_mask, ".
                "name as IPRanges_name ".
                "from IPRanges ".
                "where id = '$id'";
+       $result = useSelectBlade ($query);
+       $ret = array();
+       $row = $result->fetch (PDO::FETCH_ASSOC);
+       if ($row == NULL)
+               return $ret;
+       $ret['id'] = $row['IPRanges_id'];
+       $ret['ip'] = $row['IPRanges_ip'];
+       $ret['ip_bin'] = ip2long ($row['IPRanges_ip']);
+       $ret['mask_bin'] = binMaskFromDec($row['IPRanges_mask']);
+       $ret['mask_bin_inv'] = binInvMaskFromDec($row['IPRanges_mask']);
+       $ret['name'] = $row['IPRanges_name'];
+       $ret['mask'] = $row['IPRanges_mask'];
+       $ret['addrlist'] = array();
+       $result->closeCursor();
+       unset ($result);
+       // We risk losing some significant bits in an unsigned 32bit integer,
+       // unless it is converted to a string.
+       $db_first = "'" . sprintf ('%u', 0x00000000 + $ret['ip_bin'] & $ret['mask_bin']) . "'";
+       $db_last  = "'" . sprintf ('%u', 0x00000000 + $ret['ip_bin'] | ($ret['mask_bin_inv'])) . "'";
+
+       // Don't try to build up the whole structure in a single pass. Request
+       // the list of user comments and reservations and merge allocations in
+       // at a latter point.
+       $query =
+               "select INET_NTOA(ip) as ip, name, reserved from IPAddress " .
+               "where ip between ${db_first} and ${db_last} " .
+               "and (reserved = 'yes' or name != '')";
        $result = $dbxlink->query ($query);
-       if ($result == NULL)
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               return NULL;
+               $ip_bin = ip2long ($row['ip']);
+               $ret['addrlist'][$ip_bin] = $row;
+               $tmp = array();
+               foreach (array ('ip', 'name', 'reserved') as $cname)
+                       $tmp[$cname] = $row[$cname];
+               $tmp['references'] = array();
+               $tmp['lbrefs'] = array();
+               $tmp['rsrefs'] = array();
+               $ret['addrlist'][$ip_bin] = $tmp;
        }
-       else
+       $result->closeCursor();
+       unset ($result);
+
+       $query =
+               "select INET_NTOA(ipb.ip) as ip, ro.id as object_id, " .
+               "ro.name as object_name, ipb.name, ipb.type, objtype_id, " .
+               "dict_value as objtype_name from " .
+               "IPBonds as ipb inner join RackObject as ro on ipb.object_id = ro.id " .
+               "left join Dictionary on objtype_id=dict_key natural join Chapter " .
+               "where ip between ${db_first} and ${db_last} " .
+               "and chapter_name = 'RackObjectType'" .
+               "order by ipb.type, object_name";
+       $result = useSelectBlade ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               $ret=array();
-               if ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ip_bin = ip2long ($row['ip']);
+               if (!isset ($ret['addrlist'][$ip_bin]))
                {
-                       $ret['id'] = $row['IPRanges_id'];
-                       $ret['ip'] = $row['IPRanges_ip'];
-                       $ret['ip_bin'] = ip2long($ret['ip']);
-                       $ret['mask_bin'] = binMaskFromDec($row['IPRanges_mask']);
-                       $ret['mask_bin_inv'] = binInvMaskFromDec($row['IPRanges_mask']);
-                       $ret['name'] = $row['IPRanges_name'];
-                       $ret['mask'] = $row['IPRanges_mask'];
-                       $ret['addrlist'] = array();
-
-                       $result->closeCursor();
-                       $query =
-                               "select ".
-                               "IPAddress.ip as ip_bin, ".
-                               "INET_NTOA(IPAddress.ip) as ip, ".
-                               "IPAddress.name as name, ".
-                               "IPAddress.reserved as reserved, ".
-                               "IPBonds.object_id as object_id, ".
-                               "RackObject.name as object_name, ".
-                               "IPBonds.name as bond_name, ".
-                               "IPBonds.type as bond_type ".
-                               "from IPAddress left join ".
-                                       "(IPBonds join RackObject on IPBonds.object_id = RackObject.id) ".
-                               "on IPAddress.ip = IPBonds.ip ".
-                               "where IPAddress.ip >= '".sprintf('%u', ($ret['ip_bin'] & $ret['mask_bin']))."' and ".
-                                       "IPAddress.ip <= '".sprintf('%u', ($ret['ip_bin'] | ($ret['mask_bin_inv']) ))."' ".
-                               "having (reserved='yes' or name != '' or IPAddress.name != '' or object_id is not NULL) ". 
-                               "order by IPAddress.ip, IPBonds.type, RackObject.name";
-                       $res_list=$dbxlink->query ($query);
-                       $prev_ip=0;
-                       if ($res_list != NULL)
-                       {
-                               while ($row1 = $res_list->fetch (PDO::FETCH_ASSOC))
-                               {
-                                       if ($prev_ip != $row1['ip'])
-                                       {
-                                               $refcount=0;
-                                               $count=ip2long($row1['ip']);
-                                               $ret['addrlist'][$count]['name'] = $row1['name'];
-                                               $ret['addrlist'][$count]['reserved'] = $row1['reserved'];
-                                               $ret['addrlist'][$count]['ip'] = $row1['ip'];
-                                               $ret['addrlist'][$count]['ip_bin'] = ip2long($row1['ip']);
-                                               $prev_ip = $ret['addrlist'][$count]['ip'];
-                                               $ret['addrlist'][$count]['references'] = array();
-                                       }
-
-                                       if ($row1['bond_type'])
-                                       {
-                                               $ret['addrlist'][$count]['references'][$refcount]['type'] = $row1['bond_type'];
-                                               $ret['addrlist'][$count]['references'][$refcount]['name'] = $row1['bond_name'];
-                                               $ret['addrlist'][$count]['references'][$refcount]['object_id'] = $row1['object_id'];
-                                               $ret['addrlist'][$count]['references'][$refcount]['object_name'] = $row1['object_name'];
-                                               $refcount++;
-                                       }
-                               }
-                               $res_list->closeCursor();
-                       }
+                       $ret['addrlist'][$ip_bin] = array();
+                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
+                       $ret['addrlist'][$ip_bin]['name'] = '';
+                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
+                       $ret['addrlist'][$ip_bin]['references'] = array();
+                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
+                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
+               }
+               $tmp = array();
+               foreach (array ('object_id', 'type', 'name') as $cname)
+                       $tmp[$cname] = $row[$cname];
+               $quasiobject['name'] = $row['object_name'];
+               $quasiobject['objtype_id'] = $row['objtype_id'];
+               $quasiobject['objtype_name'] = $row['objtype_name'];
+               $tmp['object_name'] = displayedName ($quasiobject);
+               $ret['addrlist'][$ip_bin]['references'][] = $tmp;
+       }
+       $result->closeCursor();
+       unset ($result);
+
+       $query = "select vs_id, inet_ntoa(vip) as ip, vport, proto, " .
+               "object_id, objtype_id, ro.name, dict_value as objtype_name from " .
+               "IPVirtualService as vs inner join IPLoadBalancer as lb on vs.id = lb.vs_id " .
+               "inner join RackObject as ro on lb.object_id = ro.id " .
+               "left join Dictionary on objtype_id=dict_key " .
+               "natural join Chapter " .
+               "where vip between ${db_first} and ${db_last} " .
+               "and chapter_name = 'RackObjectType'" .
+               "order by vport, proto, ro.name, object_id";
+       $result = useSelectBlade ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               $ip_bin = ip2long ($row['ip']);
+               if (!isset ($ret['addrlist'][$ip_bin]))
+               {
+                       $ret['addrlist'][$ip_bin] = array();
+                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
+                       $ret['addrlist'][$ip_bin]['name'] = '';
+                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
+                       $ret['addrlist'][$ip_bin]['references'] = array();
+                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
+                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
                }
+               $tmp = $qbject = array();
+               foreach (array ('object_id', 'vport', 'proto', 'vs_id') as $cname)
+                       $tmp[$cname] = $row[$cname];
+               foreach (array ('name', 'objtype_id', 'objtype_name') as $cname)
+                       $qobject[$cname] = $row[$cname];
+               $tmp['object_name'] = displayedName ($qobject);
+               $ret['addrlist'][$ip_bin]['lbrefs'][] = $tmp;
        }
+       $result->closeCursor();
+       unset ($result);
+
+       $query = "select inet_ntoa(rsip) as ip, rsport, rspool_id, rsp.name as rspool_name from " .
+               "IPRealServer as rs inner join IPRSPool as rsp on rs.rspool_id = rsp.id " .
+               "where rsip between ${db_first} and ${db_last} " .
+               "order by ip, rsport, rspool_id";
+       $result = useSelectBlade ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               $ip_bin = ip2long ($row['ip']);
+               if (!isset ($ret['addrlist'][$ip_bin]))
+               {
+                       $ret['addrlist'][$ip_bin] = array();
+                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
+                       $ret['addrlist'][$ip_bin]['name'] = '';
+                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
+                       $ret['addrlist'][$ip_bin]['references'] = array();
+                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
+                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
+               }
+               $tmp = array();
+               foreach (array ('rspool_id', 'rsport', 'rspool_name') as $cname)
+                       $tmp[$cname] = $row[$cname];
+               $ret['addrlist'][$ip_bin]['rsrefs'][] = $tmp;
+       }
+
        return $ret;
 }
 
+// Don't require any records in IPAddress, but if there is one,
+// merge the data between getting allocation list. Collect enough data
+// to call displayedName() ourselves.
 function getIPAddress ($ip=0)
 {
        $ret = array();
-       $ret['range'] = getIPRange($ip);
        $ret['bonds'] = array();
+       // FIXME: below two aren't neither filled in with data nor rendered (ticket:23)
        $ret['outpf'] = array();
        $ret['inpf'] = array();
+       $ret['vslist'] = array();
+       $ret['rslist'] = array();
        $ret['exists'] = 0;
+       $ret['name'] = '';
        $ret['reserved'] = 'no';
        global $dbxlink;
        $query =
                "select ".
-               "ip, name, reserved ".
+               "name, reserved ".
                "from IPAddress ".
-               "where ip = INET_ATON('$ip')";
+               "where ip = INET_ATON('$ip') and (reserved = 'yes' or name != '')";
        $result = $dbxlink->query ($query);
        if ($result == NULL)
        {
+               showError ('Query #1 failed', __FUNCTION__);
                return NULL;
        }
-       else
+       if ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               if ($row = $result->fetch (PDO::FETCH_ASSOC))
-               {
-                       $ret['exists'] = 1;
-                       $ret['ip_bin'] = $row['ip'];
-                       $ret['ip'] = $ip;
-                       $ret['name'] = $row['name'];
-                       $ret['reserved'] = $row['reserved'];
-               }
-               $result->fetch (PDO::FETCH_ASSOC);
+               $ret['exists'] = 1;
+               $ret['name'] = $row['name'];
+               $ret['reserved'] = $row['reserved'];
        }
        $result->closeCursor();
+       unset ($result);
 
-       if ($ret['exists'] == 1)
+       $query =
+               "select ".
+               "IPBonds.object_id as object_id, ".
+               "IPBonds.name as name, ".
+               "IPBonds.type as type, ".
+               "objtype_id, dict_value as objtype_name, " .
+               "RackObject.name as object_name ".
+               "from IPBonds join RackObject on IPBonds.object_id=RackObject.id ".
+               "left join Dictionary on objtype_id=dict_key natural join Chapter " .
+               "where IPBonds.ip=INET_ATON('$ip') ".
+               "and chapter_name = 'RackObjectType' " .
+               "order by RackObject.id, IPBonds.name";
+       $result = $dbxlink->query ($query);
+       $count=0;
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               $query =
-                       "select ".
-                       "IPBonds.object_id as object_id, ".
-                       "IPBonds.name as name, ".
-                       "IPBonds.type as type, ".
-                       "RackObject.name as object_name ".
-                       "from IPBonds join RackObject on IPBonds.object_id=RackObject.id ".
-                       "where IPBonds.ip=INET_ATON('$ip') ".
-                       "order by RackObject.id, IPBonds.name";
-               $result1 = $dbxlink->query ($query);
-               $count=0;
-               while ($row = $result1->fetch (PDO::FETCH_ASSOC))
-               {
-                       $ret['bonds'][$count]['object_id'] = $row['object_id'];
-                       $ret['bonds'][$count]['name'] = $row['name'];
-                       $ret['bonds'][$count]['type'] = $row['type'];
-                       $ret['bonds'][$count]['object_name'] = $row['object_name'];
-                       $count++;
-               }
-               $result1->closeCursor();
+               $ret['bonds'][$count]['object_id'] = $row['object_id'];
+               $ret['bonds'][$count]['name'] = $row['name'];
+               $ret['bonds'][$count]['type'] = $row['type'];
+               $qo = array();
+               $qo['name'] = $row['object_name'];
+               $qo['objtype_id'] = $row['objtype_id'];
+               $qo['objtype_name'] = $row['objtype_name'];
+               $ret['bonds'][$count]['object_name'] = displayedName ($qo);
+               $count++;
+               $ret['exists'] = 1;
+       }
+       $result->closeCursor();
+       unset ($result);
 
+       $query = "select id, vport, proto, name from IPVirtualService where vip = inet_aton('${ip}')";
+       $result = $dbxlink->query ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               $new = $row;
+               $new['vip'] = $ip;
+               $ret['vslist'][] = $new;
+       }
+       $result->closeCursor();
+       unset ($result);
 
+       $query = "select inservice, rsport, IPRSPool.id as pool_id, IPRSPool.name as poolname from " .
+               "IPRealServer inner join IPRSPool on rspool_id = IPRSPool.id " .
+               "where rsip = inet_aton('${ip}')";
+       $result = $dbxlink->query ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               $new = $row;
+               $new['rsip'] = $ip;
+               $ret['rslist'][] = $new;
        }
+       $result->closeCursor();
+       unset ($result);
 
        return $ret;
 }
@@ -575,23 +700,21 @@ function bindIpToObject ($ip='', $object_id=0, $name='', $type='')
        global $dbxlink;
 
        $range = getRangeByIp($ip);
-
        if (!$range)
                return 'Non-existant ip address. Try adding IP range first';
 
-       $address = getIPAddress($ip);
-
-       if ($address['exists'] == 0)
-       {
-               $query =
-                       "insert into IPAddress set ip=INET_ATON('$ip')";
-               $dbxlink->exec ($query);
-       }
-
-       $query =
-               "insert into IPBonds set ip=INET_ATON('$ip'), object_id='$object_id', name='$name', type='$type'";
-       $result = $dbxlink->exec ($query);
-       return '';
+       $result = useInsertBlade
+       (
+               'IPBonds',
+               array
+               (
+                       'ip' => "INET_ATON('$ip')",
+                       'object_id' => "'${object_id}'",
+                       'name' => "'${name}'",
+                       'type' => "'${type}'"
+               )
+       );
+       return $result ? '' : 'useInsertBlade() failed in bindIpToObject()';
 }
 
 // This function looks up 'has_problems' flag for 'T' atoms
@@ -639,13 +762,7 @@ function mergeSearchResults (&$objects, $terms, $fieldname)
                $query .= "${fieldname} like '%$term%'";
                $count++;
        }
-       $query .= "";
-       $result = $dbxlink->query($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failed in mergeSearchResults()");
-               return NULL;
-       }
+       $result = useSelectBlade ($query);
 // FIXME: this dead call was executed 4 times per 1 object search!
 //     $typeList = getObjectTypeList();
        $clist = array ('id', 'name', 'label', 'asset_no', 'barcode', 'objtype_id', 'objtype_name');
@@ -668,7 +785,7 @@ function mergeSearchResults (&$objects, $terms, $fieldname)
        return $objects;
 }
 
-function getSearchResults ($terms)
+function getObjectSearchResults ($terms)
 {
        $objects = array();
        mergeSearchResults ($objects, $terms, 'name');
@@ -721,7 +838,7 @@ function getPrevIDforRack ($row_id = 0, $rack_id = 0)
 {
        if ($row_id <= 0 or $rack_id <= 0)
        {
-               showError ('Invalid arguments passed to getPrevIDforRack()');
+               showError ('Invalid arguments passed', __FUNCTION__);
                return NULL;
        }
        $rackList = getRacksForRow ($row_id);
@@ -735,7 +852,7 @@ function getNextIDforRack ($row_id = 0, $rack_id = 0)
 {
        if ($row_id <= 0 or $rack_id <= 0)
        {
-               showError ('Invalid arguments passed to getNextIDforRack()');
+               showError ('Invalid arguments passed', __FUNCTION__);
                return NULL;
        }
        $rackList = getRacksForRow ($row_id);
@@ -817,6 +934,11 @@ function sortByName ($a, $b)
        return sortTokenize($a['name'], $b['name']);
 }
 
+function sortRacks ($a, $b)
+{
+       return sortTokenize($a['row_name'] . ': ' . $a['name'], $b['row_name'] . ': ' . $b['name']);
+}
+
 function eq ($a, $b)
 {
        return $a==$b;
@@ -853,11 +975,11 @@ function sortObjectAddressesAndNames ($a, $b)
        $objname_cmp = sortTokenize($a['object_name'], $b['object_name']);
        if ($objname_cmp == 0)
        {
-               $objname_cmp = sortTokenize($a['port_name'], $b['port_name']);
+               $name_a = (isset ($a['port_name'])) ? $a['port_name'] : '';
+               $name_b = (isset ($b['port_name'])) ? $b['port_name'] : '';
+               $objname_cmp = sortTokenize($name_a, $name_b);
                if ($objname_cmp == 0)
-               {
                        sortTokenize($a['ip'], $b['ip']);
-               }
                return $objname_cmp;
        }
        return $objname_cmp;
@@ -948,21 +1070,20 @@ function getObjectForwards($object_id)
                "localport, ".
                "INET_NTOA(remoteip) as remoteip, ".
                "remoteport, ".
+               "ipa1.name as local_addr_name, " .
+               "ipa2.name as remote_addr_name, " .
                "description ".
                "from PortForwarding inner join Dictionary on proto = dict_key natural join Chapter ".
+               "left join IPAddress as ipa1 on PortForwarding.localip = ipa1.ip " .
+               "left join IPAddress as ipa2 on PortForwarding.remoteip = ipa2.ip " .
                "where object_id='$object_id' and chapter_name = 'Protocols' ".
                "order by localip, localport, proto, remoteip, remoteport";
        $result2 = $dbxlink->query ($query);
        $count=0;
        while ($row = $result2->fetch (PDO::FETCH_ASSOC))
        {
-               $ret['out'][$count]['proto'] = $row['proto'];
-               $ret['out'][$count]['proto_bin'] = $row['proto_bin'];
-               $ret['out'][$count]['localip'] = $row['localip'];
-               $ret['out'][$count]['localport'] = $row['localport'];
-               $ret['out'][$count]['remoteip'] = $row['remoteip'];
-               $ret['out'][$count]['remoteport'] = $row['remoteport'];
-               $ret['out'][$count]['description'] = $row['description'];
+               foreach (array ('proto', 'proto_bin', 'localport', 'localip', 'remoteport', 'remoteip', 'description', 'local_addr_name', 'remote_addr_name') as $cname)
+                       $ret['out'][$count][$cname] = $row[$cname];
                $count++;
        }
        $result2->closeCursor();
@@ -978,22 +1099,15 @@ function getObjectForwards($object_id)
                "PortForwarding.object_id as object_id, ".
                "RackObject.name as object_name, ".
                "description ".
-               "from ((PortForwarding join IPBonds on remoteip=ip) join RackObject on PortForwarding.object_id=RackObject.id) inner join Dictionary on proto = dict_key natural join Chapter ".
+               "from ((PortForwarding join IPBonds on remoteip=IPBonds.ip) join RackObject on PortForwarding.object_id=RackObject.id) inner join Dictionary on proto = dict_key natural join Chapter ".
                "where IPBonds.object_id='$object_id' and chapter_name = 'Protocols' ".
                "order by remoteip, remoteport, proto, localip, localport";
        $result3 = $dbxlink->query ($query);
        $count=0;
        while ($row = $result3->fetch (PDO::FETCH_ASSOC))
        {
-               $ret['in'][$count]['proto'] = $row['proto'];
-               $ret['in'][$count]['proto_bin'] = $row['proto_bin'];
-               $ret['in'][$count]['localport'] = $row['localport'];
-               $ret['in'][$count]['localip'] = $row['localip'];
-               $ret['in'][$count]['remoteport'] = $row['remoteport'];
-               $ret['in'][$count]['remoteip'] = $row['remoteip'];
-               $ret['in'][$count]['object_id'] = $row['object_id'];
-               $ret['in'][$count]['object_name'] = $row['object_name'];
-               $ret['in'][$count]['description'] = $row['description'];
+               foreach (array ('proto', 'proto_bin', 'localport', 'localip', 'remoteport', 'remoteip', 'object_id', 'object_name', 'description') as $cname)
+                       $ret['in'][$count][$cname] = $row[$cname];
                $count++;
        }
        $result3->closeCursor();
@@ -1021,4 +1135,428 @@ function findAllEndpoints ($object_id, $fallback = '')
        return $regular;
 }
 
+// Some records in the dictionary may be written as plain text or as Wiki
+// link in the following syntax:
+// 1. word
+// 2. [[word URL]] // FIXME: this isn't working
+// 3. [[word word word | URL]]
+// This function parses the line and returns text suitable for either A
+// (rendering <A HREF>) or O (for <OPTION>).
+function parseWikiLink ($line, $which, $strip_optgroup = FALSE)
+{
+       if (preg_match ('/^\[\[.+\]\]$/', $line) == 0)
+       {
+               if ($strip_optgroup)
+                       return ereg_replace ('^.+\^', '', $line);
+               else
+                       return $line;
+       }
+       $line = preg_replace ('/^\[\[(.+)\]\]$/', '$1', $line);
+       $s = explode ('|', $line);
+       $o_value = trim ($s[0]);
+       if ($strip_optgroup)
+               $o_value = ereg_replace ('^.+\^', '', $o_value);
+       $a_value = trim ($s[1]);
+       if ($which == 'a')
+               return "<a href='${a_value}'>${o_value}</a>";
+       if ($which == 'o')
+               return $o_value;
+}
+
+function buildVServiceName ($vsinfo = NULL)
+{
+       if ($vsinfo == NULL)
+       {
+               showError ('NULL argument', __FUNCTION__);
+               return NULL;
+       }
+       return $vsinfo['vip'] . ':' . $vsinfo['vport'] . '/' . $vsinfo['proto'];
+}
+
+// rackspace usage for a single rack
+// (T + W + U) / (height * 3 - A)
+function getRSUforRack ($data = NULL)
+{
+       if ($data == NULL)
+       {
+               showError ('Invalid argument', __FUNCTION__);
+               return NULL;
+       }
+       $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
+       for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
+               for ($locidx = 0; $locidx < 3; $locidx++)
+                       $counter[$data[$unit_no][$locidx]['state']]++;
+       return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
+}
+
+// Same for row.
+function getRSUforRackRow ($rowData = NULL)
+{
+       if ($rowData === NULL)
+       {
+               showError ('Invalid argument', __FUNCTION__);
+               return NULL;
+       }
+       if (!count ($rowData))
+               return 0;
+       $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
+       $total_height = 0;
+       foreach (array_keys ($rowData) as $rack_id)
+       {
+               $data = getRackData ($rack_id);
+               $total_height += $data['height'];
+               for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
+                       for ($locidx = 0; $locidx < 3; $locidx++)
+                               $counter[$data[$unit_no][$locidx]['state']]++;
+       }
+       return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
+}
+
+function getObjectCount ($rackData)
+{
+       $objects = array();
+       for ($i = $rackData['height']; $i > 0; $i--)
+               for ($locidx = 0; $locidx < 3; $locidx++)
+                       if
+                       (
+                               $rackData[$i][$locidx]['state'] == 'T' and
+                               !in_array ($rackData[$i][$locidx]['object_id'], $objects)
+                       )
+                               $objects[] = $rackData[$i][$locidx]['object_id'];
+       return count ($objects);
+}
+
+// Perform substitutions and return resulting string
+function apply_macros ($macros, $subject)
+{
+       $ret = $subject;
+       foreach ($macros as $search => $replace)
+               $ret = str_replace ($search, $replace, $ret);
+       return $ret;
+}
+
+// Make sure the string is always wrapped with LF characters
+function lf_wrap ($str)
+{
+       $ret = trim ($str, "\r\n");
+       if (!empty ($ret))
+               $ret .= "\n";
+       return $ret;
+}
+
+// Adopted from Mantis BTS code.
+function string_insert_hrefs ($s)
+{
+       if (getConfigVar ('DETECT_URLS') != 'yes')
+               return $s;
+       # Find any URL in a string and replace it by a clickable link
+       $s = preg_replace( '/(([[:alpha:]][-+.[:alnum:]]*):\/\/(%[[:digit:]A-Fa-f]{2}|[-_.!~*\';\/?%^\\\\:@&={\|}+$#\(\),\[\][:alnum:]])+)/se',
+               "'<a href=\"'.rtrim('\\1','.').'\">\\1</a> [<a href=\"'.rtrim('\\1','.').'\" target=\"_blank\">^</a>]'",
+               $s);
+       $s = preg_replace( '/\b' . email_regex_simple() . '\b/i',
+               '<a href="mailto:\0">\0</a>',
+               $s);
+       return $s;
+}
+
+// Idem.
+function email_regex_simple ()
+{
+       return "(([a-z0-9!#*+\/=?^_{|}~-]+(?:\.[a-z0-9!#*+\/=?^_{|}~-]+)*)" . # recipient
+       "\@((?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?))"; # @domain
+}
+
+// Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
+// for the requested object_type_id.
+function getAutoPorts ($type_id)
+{
+       $ret = array();
+       $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
+       foreach ($typemap as $equation)
+       {
+               $tmp = explode ('=', $equation);
+               if (count ($tmp) != 2)
+                       continue;
+               $objtype_id = $tmp[0];
+               if ($objtype_id != $type_id)
+                       continue;
+               $portlist = $tmp[1];
+               foreach (explode ('+', $portlist) as $product)
+               {
+                       $tmp = explode ('*', $product);
+                       if (count ($tmp) != 3)
+                               continue;
+                       $nports = $tmp[0];
+                       $port_type = $tmp[1];
+                       $format = $tmp[2];
+                       for ($i = 0; $i < $nports; $i++)
+                               $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i));
+               }
+       }
+       return $ret;
+}
+
+// Find if a particular tag id exists on the tree, then attach the
+// given child tag to it. If the parent tag doesn't exist, return FALSE.
+function attachChildTag (&$tree, $parent_id, $child_id, $child_info)
+{
+       foreach ($tree as $tagid => $taginfo)
+       {
+               if ($tagid == $parent_id)
+               {
+                       $tree[$tagid]['kids'][$child_id] = $child_info;
+                       return TRUE;
+               }
+               elseif (attachChildTag ($tree[$tagid]['kids'], $parent_id, $child_id, $child_info))
+                       return TRUE;
+       }
+       return FALSE;
+}
+
+// Build a tree from the tag list and return it.
+function getTagTree ()
+{
+       $tagtree = array();
+       $taglist = getTagList();
+       while (count ($taglist) > 0)
+       {
+               $picked = FALSE;
+               foreach ($taglist as $tagid => $taginfo)
+               {
+                       $taginfo['kids'] = array();
+                       if ($taginfo['parent_id'] == NULL)
+                       {
+                               $tagtree[$tagid] = $taginfo;
+                               $picked = TRUE;
+                               unset ($taglist[$tagid]);
+                       }
+                       elseif (attachChildTag ($tagtree, $taginfo['parent_id'], $tagid, $taginfo))
+                       {
+                               $picked = TRUE;
+                               unset ($taglist[$tagid]);
+                       }
+               }
+               if (!$picked) // Only orphaned items on the list.
+                       break;
+       }
+       return $tagtree;
+}
+
+function serializeTags ($trail)
+{
+       $comma = '';
+       $ret = '';
+       foreach ($trail as $taginfo)
+       {
+               $ret .= $comma . $taginfo['tag'];
+               $comma = ', ';
+       }
+       return $ret;
+}
+
+// a helper for getTrailExpansion()
+function traceTrail ($tree, $trail)
+{
+       // For each tag find its path from the root, then combine items
+       // of all paths and add them to the trail, if they aren't there yet.
+       $ret = array();
+       foreach ($tree as $taginfo1)
+       {
+               $hit = FALSE;
+               foreach ($trail as $taginfo2)
+                       if ($taginfo1['id'] == $taginfo2['id'])
+                       {
+                               $hit = TRUE;
+                               break;
+                       }
+               if (count ($taginfo1['kids']) > 0)
+               {
+                       $subsearch = traceTrail ($taginfo1['kids'], $trail);
+                       if (count ($subsearch))
+                       {
+                               $hit = TRUE;
+                               $ret = array_merge ($ret, $subsearch);
+                       }
+               }
+               if ($hit)
+                       $ret[] = $taginfo1;
+       }
+       return $ret;
+}
+
+// For each tag add all its parent tags onto the list. Don't expect anything
+// except user's tags on the trail.
+function getTrailExpansion ($trail)
+{
+       $tree = getTagTree();
+       return traceTrail ($tree, $trail);
+}
+
+// Return the list of missing implicit tags.
+function getImplicitTags ($oldtags)
+{
+       $ret = array();
+       $newtags = getTrailExpansion ($oldtags);
+       foreach ($newtags as $newtag)
+       {
+               $already_exists = FALSE;
+               foreach ($oldtags as $oldtag)
+                       if ($newtag['id'] == $oldtag['id'])
+                       {
+                               $already_exists = TRUE;
+                               break;
+                       }
+               if ($already_exists)
+                       continue;
+               $ret[] = array ('id' => $newtag['id'], 'tag' => $newtag['tag'], 'parent_id' => $newtag['parent_id']);
+       }
+       return $ret;
+}
+
+// Minimize the trail: exclude all implicit tags and return the resulting trail.
+function getExplicitTagsOnly ($trail, $tree = NULL)
+{
+       if ($tree === NULL)
+               $tree = getTagTree();
+       $ret = array();
+       foreach ($tree as $taginfo)
+       {
+               if (isset ($taginfo['kids']))
+               {
+                       $harvest = getExplicitTagsOnly ($trail, $taginfo['kids']);
+                       if (count ($harvest) > 0)
+                       {
+                               $ret = array_merge ($ret, $harvest);
+                               continue;
+                       }
+               }
+               // This tag isn't implicit, test is for being explicit.
+               foreach ($trail as $testtag)
+                       if ($taginfo['id'] == $testtag['id'])
+                       {
+                               $ret[] = $testtag;
+                               break;
+                       }
+       }
+       return $ret;
+}
+
+// Maximize the trail: for each tag add all tags, for which it is direct or indirect parent.
+// Unlike other functions, this one accepts and returns a list of integer tag IDs, not
+// a list of tag structures.
+function complementByKids ($idlist, $tree = NULL, $getall = FALSE)
+{
+       if ($tree === NULL)
+               $tree = getTagTree();
+       $getallkids = $getall;
+       $ret = array();
+       foreach ($tree as $taginfo)
+       {
+               foreach ($idlist as $test_id)
+                       if ($getall or $taginfo['id'] == $test_id)
+                       {
+                               $ret[] = $taginfo['id'];
+                               // Once matched node makes all sub-nodes match, but don't make
+                               // a mistake of matching every other node at the current level.
+                               $getallkids = TRUE;
+                               break;
+                       }
+               if (isset ($taginfo['kids']))
+                       $ret = array_merge ($ret, complementByKids ($idlist, $taginfo['kids'], $getallkids));
+               $getallkids = FALSE;
+       }
+       return $ret;
+}
+
+function loadRackObjectAutoTags()
+{
+       assertUIntArg ('object_id');
+       $object_id = $_REQUEST['object_id'];
+       $oinfo = getObjectInfo ($object_id);
+       $ret = array();
+       $ret[] = array ('tag' => '$id_' . $_REQUEST['object_id']);
+       $ret[] = array ('tag' => '$any_object');
+       return $ret;
+}
+
+function loadIPv4PrefixAutoTags()
+{
+       assertUIntArg ('id');
+       $subnet = getIPRange ($_REQUEST['id']);
+       $ret = array();
+       $ret[] = array ('tag' => '$id_' . $_REQUEST['id']);
+       $ret[] = array ('tag' => '$ipv4net-' . str_replace ('.', '-', $subnet['ip']) . '-' . $subnet['mask']);
+       // FIXME: find and list tags for all parent networks
+       $ret[] = array ('tag' => '$any_ipv4net');
+       $ret[] = array ('tag' => '$any_net');
+       return $ret;
+}
+
+function loadRackAutoTags()
+{
+       assertUIntArg ('rack_id');
+       $ret = array();
+       $ret[] = array ('tag' => '$id_' . $_REQUEST['rack_id']);
+       $ret[] = array ('tag' => '$any_rack');
+       return $ret;
+}
+
+function loadIPv4AddressAutoTags()
+{
+       assertIPv4Arg ('ip');
+       $ret = array();
+       $ret[] = array ('tag' => '$ipv4net-' . str_replace ('.', '-', $subnet['ip']) . '-32');
+       // FIXME: find and list tags for all parent networks
+       $ret[] = array ('tag' => '$any_ipv4net');
+       $ret[] = array ('tag' => '$any_net');
+       return $ret;
+}
+
+function loadIPv4VSAutoTags()
+{
+       assertUIntArg ('id');
+       $ret = array();
+       $ret[] = array ('tag' => '$id_' . $_REQUEST['id']);
+       $ret[] = array ('tag' => '$any_ipv4vs');
+       $ret[] = array ('tag' => '$any_vs');
+       return $ret;
+}
+
+function loadIPv4RSPoolAutoTags()
+{
+       assertUIntArg ('pool_id');
+       $ret = array();
+       $ret[] = array ('tag' => '$id_' . $_REQUEST['pool_id']);
+       $ret[] = array ('tag' => '$any_ipv4rspool');
+       $ret[] = array ('tag' => '$any_rspool');
+       return $ret;
+}
+
+function getGlobalAutoTags()
+{
+       global $remote_username, $accounts;
+       $ret = array();
+       $user_id = 0;
+       foreach ($accounts as $a)
+               if ($a['user_name'] == $remote_username)
+               {
+                       $user_id = $a['user_id'];
+                       break;
+               }
+       $ret[] = array ('tag' => '$username_' . $remote_username);
+       $ret[] = array ('tag' => '$userid_' . $user_id);
+       return $ret;
+}
+
+// Build a tag trail from supplied tag id list and return it.
+function buildTrailFromIds ($tagidlist)
+{
+       $taglist = getTagList();
+       $ret = array();
+       foreach ($tagidlist as $tag_id)
+               if (isset ($taglist[$tag_id]))
+                       $ret[] = $taglist[$tag_id];
+       return $ret;
+}
+
 ?>