r1907 + make tags clickable for every realm
[racktables] / inc / functions.php
index 26cc224e7a7dbd2f9be040c6e18cb75a44c66896..63769e8ef8bbd0877b2a4532f97085b5d2228a2a 100644 (file)
@@ -374,7 +374,7 @@ 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);
@@ -420,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))
        {
@@ -438,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)
        {
@@ -449,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)
@@ -463,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)
@@ -479,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']) . "'";
@@ -491,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;
@@ -502,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, " .
@@ -515,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]))
@@ -526,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)
@@ -536,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;
 }
@@ -607,10 +698,27 @@ function getIPAddress ($ip=0)
        $result->closeCursor();
        unset ($result);
 
-       $query = "select id, inet_ntoa(vip) as vip, vport, proto, name from IPVirtualService where vip = inet_aton('${ip}')";
+       $query = "select id, vport, proto, name from IPVirtualService where vip = inet_aton('${ip}')";
        $result = $dbxlink->query ($query);
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
-               $ret['vslist'][] = $row;
+       {
+               $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);
 
@@ -684,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');
@@ -713,7 +815,7 @@ function mergeSearchResults (&$objects, $terms, $fieldname)
        return $objects;
 }
 
-function getSearchResults ($terms)
+function getObjectSearchResults ($terms)
 {
        $objects = array();
        mergeSearchResults ($objects, $terms, 'name');
@@ -903,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;
@@ -983,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, ".
@@ -1001,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, ".
@@ -1027,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;
 }
@@ -1070,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>";
@@ -1094,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)
@@ -1187,4 +1303,377 @@ function email_regex_simple ()
        "\@((?:[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;
+}
+
 ?>