r1907 + make tags clickable for every realm
[racktables] / inc / functions.php
index 48454b1eb2d899674550faf9340e405ab714e924..63769e8ef8bbd0877b2a4532f97085b5d2228a2a 100644 (file)
@@ -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,9 +112,9 @@ 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)
@@ -115,26 +123,34 @@ function markAllSpans (&$rackData = NULL)
                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)
@@ -358,13 +374,17 @@ function binInvMaskFromDec ($maskL)
        return $binmask;
 }
 
-function addRange ($range='', $name='', $is_bcast = FALSE)
+function addRange ($range='', $name='', $is_bcast = FALSE, $taglist = array())
 {
        // $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)
@@ -400,9 +420,7 @@ function addRange ($range='', $name='', $is_bcast = FALSE)
                "from IPRanges ";
        
 
-       global $dbxlink;
-
-       $result = $dbxlink->query ($query);
+       $result = useSelectBlade ($query);
 
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
@@ -418,9 +436,17 @@ function addRange ($range='', $name='', $is_bcast = FALSE)
                        return "This subnet intersects with ".long2ip($row['ip'])."/${row['mask']}";
        }
        $result->closeCursor();
-       $query =
-               "insert into IPRanges set ip=".sprintf('%u', $ipL).", mask='$maskL', name='$name'";
-       $result = $dbxlink->exec ($query);
+       unset ($result);
+       $result = useInsertBlade
+       (
+               'IPRanges',
+               array
+               (
+                       'ip' => sprintf ('%u', $ipL),
+                       'mask' => "'${maskL}'",
+                       'name' => "'${name}'"
+               )
+       );
 
        if ($is_bcast and $maskL < 31)
        {
@@ -429,7 +455,31 @@ function addRange ($range='', $name='', $is_bcast = FALSE)
                updateAddress ($network_addr, 'network', 'yes');
                updateAddress ($broadcast_addr, 'broadcast', 'yes');
        }
-       return '';
+       if (!count ($taglist))
+               return '';
+       if (($result = useSelectBlade ('select last_insert_id()')) == NULL) 
+               return 'Query #3 failed in ' . __FUNCTION__;
+       $row = $result->fetch (PDO::FETCH_NUM);
+       $netid = $row[0];
+       $result->closeCursor();
+       unset ($result);
+       $errcount = 0;
+       foreach ($taglist as $tag_id)
+               if (useInsertBlade
+               (
+                       'TagStorage',
+                       array
+                       (
+                               'target_realm' => "'ipv4net'",
+                               'target_id' => $netid,
+                               'tag_id' => $tag_id
+                       )
+               ) == FALSE)
+                       $errcount++;    
+       if (!$errcount)
+               return '';
+       else
+               return "Experienced ${errcount} errors adding tags for the network";
 }
 
 function getIPRange ($id = 0)
@@ -443,9 +493,7 @@ function getIPRange ($id = 0)
                "name as IPRanges_name ".
                "from IPRanges ".
                "where id = '$id'";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-               return NULL;
+       $result = useSelectBlade ($query);
        $ret = array();
        $row = $result->fetch (PDO::FETCH_ASSOC);
        if ($row == NULL)
@@ -459,6 +507,7 @@ function getIPRange ($id = 0)
        $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']) . "'";
@@ -471,10 +520,8 @@ function getIPRange ($id = 0)
                "select INET_NTOA(ip) as ip, name, reserved from IPAddress " .
                "where ip between ${db_first} and ${db_last} " .
                "and (reserved = 'yes' or name != '')";
-       $ipa_res = $dbxlink->query ($query);
-       if ($ipa_res == NULL)
-               return $ret;
-       while ($row = $ipa_res->fetch (PDO::FETCH_ASSOC))
+       $result = $dbxlink->query ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
                $ip_bin = ip2long ($row['ip']);
                $ret['addrlist'][$ip_bin] = $row;
@@ -482,9 +529,12 @@ function getIPRange ($id = 0)
                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;
        }
-       $ipa_res->closeCursor();
+       $result->closeCursor();
+       unset ($result);
 
        $query =
                "select INET_NTOA(ipb.ip) as ip, ro.id as object_id, " .
@@ -495,8 +545,8 @@ function getIPRange ($id = 0)
                "where ip between ${db_first} and ${db_last} " .
                "and chapter_name = 'RackObjectType'" .
                "order by ipb.type, object_name";
-       $ipb_res = $dbxlink->query ($query);
-       while ($row = $ipb_res->fetch (PDO::FETCH_ASSOC))
+       $result = useSelectBlade ($query);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
                $ip_bin = ip2long ($row['ip']);
                if (!isset ($ret['addrlist'][$ip_bin]))
@@ -506,6 +556,8 @@ function getIPRange ($id = 0)
                        $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)
@@ -516,7 +568,66 @@ function getIPRange ($id = 0)
                $tmp['object_name'] = displayedName ($quasiobject);
                $ret['addrlist'][$ip_bin]['references'][] = $tmp;
        }
-       $ipb_res->closeCursor();
+       $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;
 }
@@ -528,8 +639,11 @@ function getIPAddress ($ip=0)
 {
        $ret = array();
        $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';
@@ -541,7 +655,10 @@ function getIPAddress ($ip=0)
                "where ip = INET_ATON('$ip') and (reserved = 'yes' or name != '')";
        $result = $dbxlink->query ($query);
        if ($result == NULL)
+       {
+               showError ('Query #1 failed', __FUNCTION__);
                return NULL;
+       }
        if ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
                $ret['exists'] = 1;
@@ -549,6 +666,7 @@ function getIPAddress ($ip=0)
                $ret['reserved'] = $row['reserved'];
        }
        $result->closeCursor();
+       unset ($result);
 
        $query =
                "select ".
@@ -578,6 +696,31 @@ function getIPAddress ($ip=0)
                $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;
 }
@@ -649,13 +792,7 @@ function mergeSearchResults (&$objects, $terms, $fieldname)
                $query .= "${fieldname} like '%$term%'";
                $count++;
        }
-       $query .= "";
-       $result = $dbxlink->query($query);
-       if ($result == NULL)
-       {
-               showError ("SQL query failed", __FUNCTION__);
-               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');
@@ -678,7 +815,7 @@ function mergeSearchResults (&$objects, $terms, $fieldname)
        return $objects;
 }
 
-function getSearchResults ($terms)
+function getObjectSearchResults ($terms)
 {
        $objects = array();
        mergeSearchResults ($objects, $terms, 'name');
@@ -868,11 +1005,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,17 +1085,14 @@ function updatePortForwarding($object_id, $localip, $localport, $remoteip, $remo
        return '';
 }
 
-function getObjectForwards($object_id)
+function getNATv4ForObject ($object_id)
 {
-       global $dbxlink;
-
        $ret = array();
        $ret['out'] = array();
        $ret['in'] = array();
        $query =
                "select ".
-               "dict_value as proto, ".
-               "proto as proto_bin, ".
+               "proto, ".
                "INET_NTOA(localip) as localip, ".
                "localport, ".
                "INET_NTOA(remoteip) as remoteip, ".
@@ -966,25 +1100,25 @@ function getObjectForwards($object_id)
                "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 ".
+               "from PortForwarding ".
                "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' ".
+               "where object_id='$object_id' ".
                "order by localip, localport, proto, remoteip, remoteport";
-       $result2 = $dbxlink->query ($query);
+       $result = useSelectBlade ($query);
        $count=0;
-       while ($row = $result2->fetch (PDO::FETCH_ASSOC))
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               foreach (array ('proto', 'proto_bin', 'localport', 'localip', 'remoteport', 'remoteip', 'description', 'local_addr_name', 'remote_addr_name') as $cname)
+               foreach (array ('proto', 'localport', 'localip', 'remoteport', 'remoteip', 'description', 'local_addr_name', 'remote_addr_name') as $cname)
                        $ret['out'][$count][$cname] = $row[$cname];
                $count++;
        }
-       $result2->closeCursor();
+       $result->closeCursor();
+       unset ($result);
 
        $query =
                "select ".
-               "dict_value as proto, ".
-               "proto as proto_bin, ".
+               "proto, ".
                "INET_NTOA(localip) as localip, ".
                "localport, ".
                "INET_NTOA(remoteip) as remoteip, ".
@@ -992,18 +1126,18 @@ function getObjectForwards($object_id)
                "PortForwarding.object_id as object_id, ".
                "RackObject.name as object_name, ".
                "description ".
-               "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' ".
+               "from ((PortForwarding join IPBonds on remoteip=IPBonds.ip) join RackObject on PortForwarding.object_id=RackObject.id) ".
+               "where IPBonds.object_id='$object_id' ".
                "order by remoteip, remoteport, proto, localip, localport";
-       $result3 = $dbxlink->query ($query);
+       $result = useSelectBlade ($query);
        $count=0;
-       while ($row = $result3->fetch (PDO::FETCH_ASSOC))
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               foreach (array ('proto', 'proto_bin', 'localport', 'localip', 'remoteport', 'remoteip', 'object_id', 'object_name', 'description') as $cname)
+               foreach (array ('proto', 'localport', 'localip', 'remoteport', 'remoteip', 'object_id', 'object_name', 'description') as $cname)
                        $ret['in'][$count][$cname] = $row[$cname];
                $count++;
        }
-       $result3->closeCursor();
+       $result->closeCursor();
 
        return $ret;
 }
@@ -1035,13 +1169,20 @@ function findAllEndpoints ($object_id, $fallback = '')
 // 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)
+function parseWikiLink ($line, $which, $strip_optgroup = FALSE)
 {
        if (preg_match ('/^\[\[.+\]\]$/', $line) == 0)
-               return $line;
+       {
+               if ($strip_optgroup)
+                       return ereg_replace ('^.+\^', '', ereg_replace ('^(.+)&', '\\1 ', $line));
+               else
+                       return $line;
+       }
        $line = preg_replace ('/^\[\[(.+)\]\]$/', '$1', $line);
        $s = explode ('|', $line);
        $o_value = trim ($s[0]);
+       if ($strip_optgroup)
+               $o_value = ereg_replace ('^.+\^', '', ereg_replace ('^(.+)&', '\\1 ', $o_value));
        $a_value = trim ($s[1]);
        if ($which == 'a')
                return "<a href='${a_value}'>${o_value}</a>";
@@ -1059,6 +1200,16 @@ function buildVServiceName ($vsinfo = NULL)
        return $vsinfo['vip'] . ':' . $vsinfo['vport'] . '/' . $vsinfo['proto'];
 }
 
+function buildRSPoolName ($rspool = NULL)
+{
+       if ($rspool == NULL)
+       {
+               showError ('NULL argument', __FUNCTION__);
+               return NULL;
+       }
+       return strlen ($rspool['name']) ? $rspool['name'] : 'ANONYMOUS pool';
+}
+
 // rackspace usage for a single rack
 // (T + W + U) / (height * 3 - A)
 function getRSUforRack ($data = NULL)
@@ -1078,12 +1229,15 @@ function getRSUforRack ($data = NULL)
 // Same for row.
 function getRSUforRackRow ($rowData = NULL)
 {
-       if ($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);
@@ -1095,4 +1249,431 @@ function getRSUforRackRow ($rowData = NULL)
        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 ()
+{
+       global $taglist;
+       $mytaglist = $taglist;
+       $ret = array();
+       while (count ($mytaglist) > 0)
+       {
+               $picked = FALSE;
+               foreach ($mytaglist as $tagid => $taginfo)
+               {
+                       $taginfo['kids'] = array();
+                       if ($taginfo['parent_id'] == NULL)
+                       {
+                               $ret[$tagid] = $taginfo;
+                               $picked = TRUE;
+                               unset ($mytaglist[$tagid]);
+                       }
+                       elseif (attachChildTag ($ret, $taginfo['parent_id'], $tagid, $taginfo))
+                       {
+                               $picked = TRUE;
+                               unset ($mytaglist[$tagid]);
+                       }
+               }
+               if (!$picked) // Only orphaned items on the list.
+                       break;
+       }
+       return $ret;
+}
+
+// Build a tree from the tag list and return everything _except_ the tree.
+function getOrphanedTags ()
+{
+       global $taglist;
+       $mytaglist = $taglist;
+       $dummy = array();
+       while (count ($mytaglist) > 0)
+       {
+               $picked = FALSE;
+               foreach ($mytaglist as $tagid => $taginfo)
+               {
+                       $taginfo['kids'] = array();
+                       if ($taginfo['parent_id'] == NULL)
+                       {
+                               $dummy[$tagid] = $taginfo;
+                               $picked = TRUE;
+                               unset ($mytaglist[$tagid]);
+                       }
+                       elseif (attachChildTag ($dummy, $taginfo['parent_id'], $tagid, $taginfo))
+                       {
+                               $picked = TRUE;
+                               unset ($mytaglist[$tagid]);
+                       }
+               }
+               if (!$picked) // Only orphaned items on the list.
+                       return $mytaglist;
+       }
+       return array();
+}
+
+function serializeTags ($trail, $baseurl = '')
+{
+       $comma = '';
+       $ret = '';
+       foreach ($trail as $taginfo)
+       {
+               $ret .= $comma .
+                       ($baseurl == '' ? '' : "<a href='${baseurl}tagfilter[]=${taginfo['id']}'>") .
+                       $taginfo['tag'] .
+                       ($baseurl == '' ? '' : '</a>');
+               $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)
+{
+       global $tagtree;
+       return traceTrail ($tagtree, $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)
+{
+       global $tagtree;
+       if ($tree === NULL)
+               $tree = $tagtree;
+       $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)
+{
+       global $tagtree;
+       if ($tree === NULL)
+               $tree = $tagtree;
+       $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', __FUNCTION__);
+       $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', __FUNCTION__);
+       $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', __FUNCTION__);
+       $ret = array();
+       $ret[] = array ('tag' => '$id_' . $_REQUEST['rack_id']);
+       $ret[] = array ('tag' => '$any_rack');
+       return $ret;
+}
+
+function loadIPv4AddressAutoTags()
+{
+       assertIPv4Arg ('ip', __FUNCTION__);
+       $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', __FUNCTION__);
+       $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', __FUNCTION__);
+       $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)
+{
+       global $taglist;
+       $ret = array();
+       foreach ($tagidlist as $tag_id)
+               if (isset ($taglist[$tag_id]))
+                       $ret[] = $taglist[$tag_id];
+       return $ret;
+}
+
+// Process a given tag tree and return only meaningful branches. The resulting
+// (sub)tree will have refcnt leaves on every last branch.
+function getObjectiveTagTree ($tree, $realm)
+{
+       $ret = array();
+       foreach ($tree as $taginfo)
+       {
+               $subsearch = array();
+               $pick = FALSE;
+               if (count ($taginfo['kids']))
+               {
+                       $subsearch = getObjectiveTagTree ($taginfo['kids'], $realm);
+                       $pick = count ($subsearch) > 0;
+               }
+               if (isset ($taginfo['refcnt'][$realm]))
+                       $pick = TRUE;
+               if (!$pick)
+                       continue;
+               $ret[] = array
+               (
+                       'id' => $taginfo['id'],
+                       'tag' => $taginfo['tag'],
+                       'parent_id' => $taginfo['parent_id'],
+                       'refcnt' => $taginfo['refcnt'],
+                       'kids' => $subsearch
+               );
+       }
+       return $ret;
+}
+
+function getTagFilter ()
+{
+       return isset ($_REQUEST['tagfilter']) ? complementByKids ($_REQUEST['tagfilter']) : array();
+}
+
+function getTagFilterStr ($tagfilter = array())
+{
+       $ret = '';
+       $tagfilter = isset ($_REQUEST['tagfilter']) ? $_REQUEST['tagfilter'] : array();
+       foreach (getExplicitTagsOnly (buildTrailFromIds ($tagfilter)) as $taginfo)
+               $ret .= "&tagfilter[]=" . $taginfo['id'];
+       return $ret;
+}
+
 ?>