r2906 - maintenance->trunk sync of changesets 2886~2894
[racktables] / inc / database.php
index 14c3829..f9c20ee 100644 (file)
@@ -23,6 +23,8 @@ $SQLSchema = array
                        'row_id' => '(select row_id from Rack where id = rack_id)',
                        'Row_name' => '(select name from RackRow where id = row_id)',
                        'objtype_name' => '(select dict_value from Dictionary where dict_key = objtype_id)',
+                       'has_problems' => 'has_problems',
+                       'comment' => 'comment',
                ),
                'keycolumn' => 'id',
                'ordcolumns' => array ('name'),
@@ -255,12 +257,15 @@ function getNarrowObjectList ($varname = '')
 // enough information for judgeCell() to execute.
 function listCells ($realm, $parent_id = 0)
 {
-       global $SQLSchema;
-       if (!isset ($SQLSchema[$realm]))
+       if (!$parent_id)
        {
-               showError ('invalid arg', __FUNCTION__);
-               return NULL;
+               global $entityCache;
+               if (isset ($entityCache['complete'][$realm]))
+                       return $entityCache['complete'][$realm];
        }
+       global $SQLSchema;
+       if (!isset ($SQLSchema[$realm]))
+               throw new RealmNotFoundException ($realm);
        $SQLinfo = $SQLSchema[$realm];
        $query = 'SELECT tag_id';
        foreach ($SQLinfo['columns'] as $alias => $expression)
@@ -304,19 +309,14 @@ function listCells ($realm, $parent_id = 0)
                                'parent_id' => $taglist[$row['tag_id']]['parent_id'],
                        );
        }
-       // Add necessary finish to the list before returning it.
+       // Add necessary finish to the list before returning it. Maintain caches.
+       if (!$parent_id)
+               unset ($entityCache['partial'][$realm]);
        foreach (array_keys ($ret) as $entity_id)
        {
                $ret[$entity_id]['etags'] = getExplicitTagsOnly ($ret[$entity_id]['etags']);
                $ret[$entity_id]['itags'] = getImplicitTags ($ret[$entity_id]['etags']);
-               switch ($realm)
-               {
-               case 'ipv4net':
-                       $ret[$entity_id]['atags'] = generateEntityAutoTags ($realm, $ret[$entity_id]);
-                       break;
-               default:
-                       $ret[$entity_id]['atags'] = generateEntityAutoTags ($realm, $entity_id);
-               }
+               $ret[$entity_id]['atags'] = generateEntityAutoTags ($ret[$entity_id]);
                switch ($realm)
                {
                case 'object':
@@ -332,6 +332,10 @@ function listCells ($realm, $parent_id = 0)
                default:
                        break;
                }
+               if (!$parent_id)
+                       $entityCache['complete'][$realm][$entity_id] = $ret[$entity_id];
+               else
+                       $entityCache['partial'][$realm][$entity_id] = $ret[$entity_id];
        }
        return $ret;
 }
@@ -340,12 +344,18 @@ function listCells ($realm, $parent_id = 0)
 // if it does not exist).
 function spotEntity ($realm, $id)
 {
+       global $entityCache;
+       if (isset ($entityCache['complete'][$realm]))
+       // Emphasize the absence of record, if listCells() has already been called.
+               if (isset ($entityCache['complete'][$realm][$id]))
+                       return $entityCache['complete'][$realm][$id];
+               else
+                       throw new EntityNotFoundException ($realm, $id);
+       elseif (isset ($entityCache['partial'][$realm][$id]))
+               return $entityCache['partial'][$realm][$id];
        global $SQLSchema;
        if (!isset ($SQLSchema[$realm]))
-       {
-               showError ('invalid arg', __FUNCTION__);
-               return NULL;
-       }
+               throw new RealmNotFoundException ($realm);
        $SQLinfo = $SQLSchema[$realm];
        $query = 'SELECT tag_id';
        foreach ($SQLinfo['columns'] as $alias => $expression)
@@ -381,17 +391,10 @@ function spotEntity ($realm, $id)
                        );
        unset ($result);
        if (!isset ($ret['realm'])) // no rows were returned
-               return NULL;
+               throw new EntityNotFoundException ($realm, $id);
        $ret['etags'] = getExplicitTagsOnly ($ret['etags']);
        $ret['itags'] = getImplicitTags ($ret['etags']);
-       switch ($realm)
-       {
-       case 'ipv4net':
-               $ret['atags'] = generateEntityAutoTags ($realm, $ret);
-               break;
-       default:
-               $ret['atags'] = generateEntityAutoTags ($realm, $id);
-       }
+       $ret['atags'] = generateEntityAutoTags ($ret);
        switch ($realm)
        {
        case 'object':
@@ -407,6 +410,7 @@ function spotEntity ($realm, $id)
        default:
                break;
        }
+       $entityCache['partial'][$realm][$id] = $ret;
        return $ret;
 }
 
@@ -514,26 +518,6 @@ function amplifyCell (&$record, $dummy = NULL)
        }
 }
 
-// This is a popular helper.
-function getObjectInfo ($object_id = 0, $set_dname = TRUE)
-{
-       $query =
-               "select RackObject.id as id, RackObject.name as name, label, barcode, dict_value as objtype_name, asset_no, dict_key as objtype_id, has_problems, comment from " .
-               "RackObject inner join Dictionary on objtype_id = dict_key join Chapter on Chapter.id = Dictionary.chapter_id " .
-               "where RackObject.id = '${object_id}' and Chapter.name = 'RackObjectType'";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       if (($ret = $result->fetch (PDO::FETCH_ASSOC)) == NULL)
-       {
-               showError ('Query succeeded, but returned no data', __FUNCTION__);
-               return NULL;
-       }
-       $result->closeCursor();
-       // It's safe now to run subsequent queries.
-       if ($set_dname)
-               $ret['dname'] = displayedName ($ret);
-       return $ret;
-}
-
 function getPortTypes ()
 {
        return readChapter ('PortType');
@@ -555,6 +539,7 @@ function getObjectPortsAndLinks ($object_id)
                $row['remote_name'] = NULL;
                $row['remote_object_id'] = NULL;
                $row['remote_object_name'] = NULL;
+               $row['remote_type_id'] = NULL;
                $ret[] = $row;
        }
        unset ($result);
@@ -585,13 +570,14 @@ function getObjectPortsAndLinks ($object_id)
                                $ret[$tmpkey]['remote_name'] = $row['port_name'];
                                $ret[$tmpkey]['remote_object_id'] = $row['object_id'];
                                $ret[$tmpkey]['remote_object_name'] = $row['object_name'];
+                               $ret[$tmpkey]['remote_type_id'] = $row['port_type'];
                        }
                        $ret[$tmpkey]['remote_id'] = $remote_id;
                        unset ($result);
                        // only call displayedName() when necessary
-                       if (empty ($ret[$tmpkey]['remote_object_name']) and !empty ($ret[$tmpkey]['remote_object_id']))
+                       if (!strlen ($ret[$tmpkey]['remote_object_name']) and strlen ($ret[$tmpkey]['remote_object_id']))
                        {
-                               $oi = getObjectInfo ($ret[$tmpkey]['remote_object_id']);
+                               $oi = spotEntity ('object', $ret[$tmpkey]['remote_object_id']);
                                $ret[$tmpkey]['remote_object_name'] = $oi['dname'];
                        }
                }
@@ -601,7 +587,7 @@ function getObjectPortsAndLinks ($object_id)
 
 function commitAddRack ($name, $height = 0, $row_id = 0, $comment, $taglist)
 {
-       if ($row_id <= 0 or $height <= 0 or empty ($name))
+       if ($row_id <= 0 or $height <= 0 or !strlen ($name))
                return FALSE;
        $result = useInsertBlade
        (
@@ -633,11 +619,11 @@ function commitAddObject ($new_name, $new_label, $new_barcode, $new_type_id, $ne
                'RackObject',
                array
                (
-                       'name' => empty ($new_name) ? 'NULL' : "'${new_name}'",
+                       'name' => !strlen ($new_name) ? 'NULL' : "'${new_name}'",
                        'label' => "'${new_label}'",
-                       'barcode' => empty ($new_barcode) ? 'NULL' : "'${new_barcode}'",
+                       'barcode' => !strlen ($new_barcode) ? 'NULL' : "'${new_barcode}'",
                        'objtype_id' => $new_type_id,
-                       'asset_no' => empty ($new_asset_no) ? 'NULL' : "'${new_asset_no}'"
+                       'asset_no' => !strlen ($new_asset_no) ? 'NULL' : "'${new_asset_no}'"
                )
        );
        if ($result1 == NULL)
@@ -660,15 +646,12 @@ function commitAddObject ($new_name, $new_label, $new_barcode, $new_type_id, $ne
 
 function commitUpdateObject ($object_id = 0, $new_name = '', $new_label = '', $new_barcode = '', $new_type_id = 0, $new_has_problems = 'no', $new_asset_no = '', $new_comment = '')
 {
-       if ($object_id == 0 || $new_type_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return FALSE;
-       }
+       if ($new_type_id == 0)
+               throw new InvalidArgException ('$new_type_id', $new_type_id);
        global $dbxlink;
-       $new_asset_no = empty ($new_asset_no) ? 'NULL' : "'${new_asset_no}'";
-       $new_barcode = empty ($new_barcode) ? 'NULL' : "'${new_barcode}'";
-       $new_name = empty ($new_name) ? 'NULL' : "'${new_name}'";
+       $new_asset_no = !strlen ($new_asset_no) ? 'NULL' : "'${new_asset_no}'";
+       $new_barcode = !strlen ($new_barcode) ? 'NULL' : "'${new_barcode}'";
+       $new_name = !strlen ($new_name) ? 'NULL' : "'${new_name}'";
        $query = "update RackObject set name=${new_name}, label='${new_label}', barcode=${new_barcode}, objtype_id='${new_type_id}', " .
                "has_problems='${new_has_problems}', asset_no=${new_asset_no}, comment='${new_comment}' " .
                "where id='${object_id}' limit 1";
@@ -682,23 +665,25 @@ function commitUpdateObject ($object_id = 0, $new_name = '', $new_label = '', $n
        return recordHistory ('RackObject', "id = ${object_id}");
 }
 
+// Remove file links related to the entity, but leave the entity and file(s) intact.
+function releaseFiles ($entity_realm, $entity_id)
+{
+       global $dbxlink;
+       $dbxlink->exec ("DELETE FROM FileLink WHERE entity_type = '${entity_realm}' AND entity_id = ${entity_id}");
+}
+
 // There are times when you want to delete all traces of an object
 function commitDeleteObject ($object_id = 0)
 {
-       if ($object_id <= 0)
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
        global $dbxlink;
+       releaseFiles ('object', $object_id);
+       destroyTagsForEntity ('object', $object_id);
        $dbxlink->query("DELETE FROM AttributeValue WHERE object_id = ${object_id}");
-       $dbxlink->query("DELETE FROM File WHERE id IN (SELECT file_id FROM FileLink WHERE entity_id = 'object' AND entity_id = ${object_id})");
        $dbxlink->query("DELETE FROM IPv4LB WHERE object_id = ${object_id}");
        $dbxlink->query("DELETE FROM IPv4Allocation WHERE object_id = ${object_id}");
        $dbxlink->query("DELETE FROM Port WHERE object_id = ${object_id}");
        $dbxlink->query("DELETE FROM IPv4NAT WHERE object_id = ${object_id}");
        $dbxlink->query("DELETE FROM RackSpace WHERE object_id = ${object_id}");
-       $dbxlink->query("DELETE FROM TagStorage WHERE entity_realm = 'object' and entity_id = ${object_id}");
        $dbxlink->query("DELETE FROM Atom WHERE molecule_id IN (SELECT new_molecule_id FROM MountOperation WHERE object_id = ${object_id})");
        $dbxlink->query("DELETE FROM Molecule WHERE id IN (SELECT new_molecule_id FROM MountOperation WHERE object_id = ${object_id})");
        $dbxlink->query("DELETE FROM MountOperation WHERE object_id = ${object_id}");
@@ -711,10 +696,10 @@ function commitDeleteObject ($object_id = 0)
 function commitDeleteRack($rack_id)
 {
        global $dbxlink;
+       releaseFiles ('rack', $rack_id);
+       destroyTagsForEntity ('rack', $rack_id);
        $query = "delete from RackSpace where rack_id = '${rack_id}'";
        $dbxlink->query ($query);
-       $query = "delete from TagStorage where entity_realm='rack' and entity_id='${rack_id}'";
-       $dbxlink->query ($query);
        $query = "delete from RackHistory where id = '${rack_id}'";
        $dbxlink->query ($query);
        $query = "delete from Rack where id = '${rack_id}'";
@@ -724,7 +709,7 @@ function commitDeleteRack($rack_id)
 
 function commitUpdateRack ($rack_id, $new_name, $new_height, $new_row_id, $new_comment)
 {
-       if (empty ($rack_id) || empty ($new_name) || empty ($new_height))
+       if (!strlen ($rack_id) || !strlen ($new_name) || !strlen ($new_height))
        {
                showError ('Not all required args are present.', __FUNCTION__);
                return FALSE;
@@ -735,6 +720,7 @@ function commitUpdateRack ($rack_id, $new_name, $new_height, $new_row_id, $new_c
        $check_sql = "SELECT COUNT(*) AS count FROM RackSpace WHERE rack_id = '${rack_id}' AND unit_no > '{$new_height}'";
        $check_result = $dbxlink->query($check_sql);
        $check_row = $check_result->fetch (PDO::FETCH_ASSOC);
+       unset ($check_result);
        if ($check_row['count'] > 0) {
                showError ('Cannot shrink rack, objects are still mounted there', __FUNCTION__);
                return FALSE;
@@ -831,11 +817,6 @@ function processGridForm (&$rackData, $unchecked_state, $checked_state, $object_
 // the requested object.
 function getMoleculeForObject ($object_id = 0)
 {
-       if ($object_id == 0)
-       {
-               showError ("object_id == 0", __FUNCTION__);
-               return NULL;
-       }
        $query =
                "select rack_id, unit_no, atom from RackSpace " .
                "where state = 'T' and object_id = ${object_id} order by rack_id, unit_no, atom";
@@ -848,11 +829,6 @@ function getMoleculeForObject ($object_id = 0)
 // This function builds a list of rack-unit-atom records for requested molecule.
 function getMolecule ($mid = 0)
 {
-       if ($mid == 0)
-       {
-               showError ("mid == 0", __FUNCTION__);
-               return NULL;
-       }
        $query =
                "select rack_id, unit_no, atom from Atom " .
                "where molecule_id=${mid}";
@@ -939,11 +915,6 @@ function getRackspaceHistory ()
 // This function is used in renderRackspaceHistory()
 function getOperationMolecules ($op_id = 0)
 {
-       if ($op_id <= 0)
-       {
-               showError ("Missing argument", __FUNCTION__);
-               return;
-       }
        $query = "select old_molecule_id, new_molecule_id from MountOperation where id = ${op_id}";
        $result = useSelectBlade ($query, __FUNCTION__);
        // We expect one row.
@@ -961,11 +932,6 @@ function getOperationMolecules ($op_id = 0)
 
 function getResidentRacksData ($object_id = 0, $fetch_rackdata = TRUE)
 {
-       if ($object_id <= 0)
-       {
-               showError ('Invalid object_id', __FUNCTION__);
-               return;
-       }
        $query = "select distinct rack_id from RackSpace where object_id = ${object_id} order by rack_id";
        $result = useSelectBlade ($query, __FUNCTION__);
        $rows = $result->fetchAll (PDO::FETCH_NUM);
@@ -991,11 +957,6 @@ function getResidentRacksData ($object_id = 0, $fetch_rackdata = TRUE)
 
 function commitAddPort ($object_id = 0, $port_name, $port_type_id, $port_label, $port_l2address)
 {
-       if ($object_id <= 0)
-       {
-               showError ('Invalid object_id', __FUNCTION__);
-               return;
-       }
        if (NULL === ($db_l2address = l2addressForDatabase ($port_l2address)))
                return "Invalid L2 address ${port_l2address}";
        $result = useInsertBlade
@@ -1181,7 +1142,7 @@ function scanIPv4Space ($pairlist)
 {
        $ret = array();
        if (!count ($pairlist)) // this is normal for a network completely divided into smaller parts
-               return $ret;;
+               return $ret;
        $dnamechache = array();
        // FIXME: this is a copy-and-paste prototype
        $or = '';
@@ -1354,10 +1315,7 @@ function scanIPv4Space ($pairlist)
 function getIPv4Address ($dottedquad = '')
 {
        if ($dottedquad == '')
-       {
-               showError ('Invalid arg', __FUNCTION__);
-               return NULL;
-       }
+               throw new InvalidArgException ('$dottedquad', $dottedquad);
        $i32 = ip2long ($dottedquad); // signed 32 bit
        $scanres = scanIPv4Space (array (array ('i32_first' => $i32, 'i32_last' => $i32)));
        if (!isset ($scanres[$i32]))
@@ -1511,17 +1469,17 @@ function getIPv4AddressSearchResult ($terms)
 
 function getIPv4RSPoolSearchResult ($terms)
 {
-       $query = "select id as pool_id, name from IPv4RSPool where ";
-       $or = '';
-       foreach (explode (' ', $terms) as $term)
-       {
-               $query .= $or . "name like '%${term}%'";
-               $or = ' or ';
-       }
-       $result = useSelectBlade ($query, __FUNCTION__);
+       $byname = getSearchResultByField
+       (
+               'IPv4RSPool',
+               array ('id'),
+               'name',
+               $terms,
+               'name'
+       );
        $ret = array();
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-               $ret[] = $row;
+       foreach ($byname as $row)
+               $ret[] = spotEntity ('ipv4rspool', $row['id']);
        return $ret;
 }
 
@@ -1576,7 +1534,7 @@ function getAccountSearchResult ($terms)
 
 function getFileSearchResult ($terms)
 {
-       $byFilename = getSearchResultByField
+       $byName = getSearchResultByField
        (
                'File',
                array ('id'),
@@ -1593,7 +1551,7 @@ function getFileSearchResult ($terms)
                'name'
        );
        // Filter out dupes.
-       foreach ($byFilename as $res1)
+       foreach ($byName as $res1)
                foreach (array_keys ($byComment) as $key2)
                        if ($res1['id'] == $byComment[$key2]['id'])
                        {
@@ -1601,11 +1559,43 @@ function getFileSearchResult ($terms)
                                continue 2;
                        }
        $ret = array();
-       foreach (array_merge ($byFilename, $byComment) as $row)
+       foreach (array_merge ($byName, $byComment) as $row)
                $ret[] = spotEntity ('file', $row['id']);
        return $ret;
 }
 
+function getRackSearchResult ($terms)
+{
+       $byName = getSearchResultByField
+       (
+               'Rack',
+               array ('id'),
+               'name',
+               $terms,
+               'name'
+       );
+       $byComment = getSearchResultByField
+       (
+               'Rack',
+               array ('id'),
+               'comment',
+               $terms,
+               'name'
+       );
+       // Filter out dupes.
+       foreach ($byName as $res1)
+               foreach (array_keys ($byComment) as $key2)
+                       if ($res1['id'] == $byComment[$key2]['id'])
+                       {
+                               unset ($byComment[$key2]);
+                               continue 2;
+                       }
+       $ret = array();
+       foreach (array_merge ($byName, $byComment) as $row)
+               $ret[] = spotEntity ('rack', $row['id']);
+       return $ret;
+}
+
 function getSearchResultByField ($tname, $rcolumns, $scolumn, $terms, $ocolumn = '')
 {
        $pfx = '';
@@ -1728,10 +1718,10 @@ function addPortCompat ($type1 = 0, $type2 = 0)
 // to merge it with readChapter().
 function getDict ($parse_links = FALSE)
 {
-       $query1 =
+       $query =
                "select Chapter.name as chapter_name, Chapter.id as chapter_no, dict_key, dict_value, sticky from " .
                "Chapter left join Dictionary on Chapter.id = Dictionary.chapter_id order by Chapter.name, dict_value";
-       $result = useSelectBlade ($query1, __FUNCTION__);
+       $result = useSelectBlade ($query, __FUNCTION__);
        $dict = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
@@ -1750,27 +1740,41 @@ function getDict ($parse_links = FALSE)
                        $dict[$chapter_no]['refcnt'][$row['dict_key']] = 0;
                }
        }
-       $result->closeCursor();
        unset ($result);
-// Find the list of all assigned values of dictionary-addressed attributes, each with
-// chapter/word keyed reference counters. Use the structure to adjust reference counters
-// of the returned disctionary words.
-       $query2 = "select a.id as attr_id, am.chapter_id as chapter_no, uint_value, count(object_id) as refcnt " .
+       // Find the list of all assigned values of dictionary-addressed attributes, each with
+       // chapter/word keyed reference counters. Use the structure to adjust reference counters
+       // of the returned disctionary words.
+       $query = "select a.id as attr_id, am.chapter_id as chapter_no, uint_value, count(object_id) as refcnt " .
                "from Attribute as a inner join AttributeMap as am on a.id = am.attr_id " .
                "inner join AttributeValue as av on a.id = av.attr_id " .
                "inner join Dictionary as d on am.chapter_id = d.chapter_id and av.uint_value = d.dict_key " .
                "where a.type = 'dict' group by a.id, am.chapter_id, uint_value " .
                "order by a.id, am.chapter_id, uint_value";
-       $result = useSelectBlade ($query2, __FUNCTION__);
+       $result = useSelectBlade ($query, __FUNCTION__);
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
                $dict[$row['chapter_no']]['refcnt'][$row['uint_value']] = $row['refcnt'];
-       $result->closeCursor();
+       unset ($result);
+       // PortType chapter is referenced by PortCompat and Port tables
+       $query = 'select dict_key as uint_value, chapter_id as chapter_no, (select count(*) from PortCompat where type1 = dict_key or type2 = dict_key) + ' .
+               '(select count(*) from Port where type = dict_key) as refcnt ' .
+               'from Dictionary where chapter_id = 2';
+       $result = useSelectBlade ($query, __FUNCTION__);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $dict[$row['chapter_no']]['refcnt'][$row['uint_value']] = $row['refcnt'];
+       unset ($result);
+       // RackObjectType chapter is referenced by AttributeMap and RackObject tables
+       $query = 'select dict_key as uint_value, chapter_id as chapter_no, (select count(*) from AttributeMap where objtype_id = dict_key) + ' .
+               '(select count(*) from RackObject where objtype_id = dict_key) as refcnt from Dictionary where chapter_id = 1';
+       $result = useSelectBlade ($query, __FUNCTION__);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $dict[$row['chapter_no']]['refcnt'][$row['uint_value']] = $row['refcnt'];
+       unset ($result);
        return $dict;
 }
 
 function getDictStats ()
 {
-       $stock_chapters = array (1, 2, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25);
+       $stock_chapters = array (1, 2, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26);
        $query =
                "select Chapter.id as chapter_no, Chapter.name as chapter_name, count(dict_key) as wc from " .
                "Chapter left join Dictionary on Chapter.id = Dictionary.chapter_id group by Chapter.id";
@@ -1849,7 +1853,7 @@ function getRackspaceStats ()
        {
                $result = useSelectBlade ($item['q'], __FUNCTION__);
                $row = $result->fetch (PDO::FETCH_NUM);
-               $ret[$item['txt']] = empty ($row[0]) ? 0 : $row[0];
+               $ret[$item['txt']] = !strlen ($row[0]) ? 0 : $row[0];
                $result->closeCursor();
                unset ($result);
        }
@@ -1917,13 +1921,16 @@ mysql> select tag_id from TagStorage left join TagTree on tag_id = id where id i
 
 */
 
+// See below why chapter_id is necessary.
 function commitUpdateDictionary ($chapter_no = 0, $dict_key = 0, $dict_value = '')
 {
-       if ($chapter_no <= 0 or $dict_key <= 0 or empty ($dict_value))
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
+       if ($chapter_no <= 0)
+               throw new InvalidArgException ('$chapter_no', $chapter_no);
+       if ($dict_key <= 0)
+               throw new InvalidArgException ('$dict_key', $dict_key);
+       if (!strlen ($dict_value))
+               throw new InvalidArgException ('$dict_value', $dict_value);
+
        global $dbxlink;
        $query =
                "update Dictionary set dict_value = '${dict_value}' where chapter_id=${chapter_no} " .
@@ -1939,11 +1946,10 @@ function commitUpdateDictionary ($chapter_no = 0, $dict_key = 0, $dict_value = '
 
 function commitSupplementDictionary ($chapter_no = 0, $dict_value = '')
 {
-       if ($chapter_no <= 0 or empty ($dict_value))
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
+       if ($chapter_no <= 0)
+               throw new InvalidArgException ('$chapter_no', $chapter_no);
+       if (!strlen ($dict_value))
+               throw new InvalidArgException ('$dict_value', $dict_value);
        return useInsertBlade
        (
                'Dictionary',
@@ -1951,13 +1957,11 @@ function commitSupplementDictionary ($chapter_no = 0, $dict_value = '')
        );
 }
 
+// Technically dict_key is enough to delete, but including chapter_id into
+// WHERE clause makes sure, that the action actually happends for the same
+// chapter, which authorization was granted for.
 function commitReduceDictionary ($chapter_no = 0, $dict_key = 0)
 {
-       if ($chapter_no <= 0 or $dict_key <= 0)
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
        global $dbxlink;
        $query =
                "delete from Dictionary where chapter_id=${chapter_no} " .
@@ -1973,11 +1977,8 @@ function commitReduceDictionary ($chapter_no = 0, $dict_key = 0)
 
 function commitAddChapter ($chapter_name = '')
 {
-       if (empty ($chapter_name))
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
+       if (!strlen ($chapter_name))
+               throw new InvalidArgException ('$chapter_name', $chapter_name);
        return useInsertBlade
        (
                'Chapter',
@@ -1987,11 +1988,8 @@ function commitAddChapter ($chapter_name = '')
 
 function commitUpdateChapter ($chapter_no = 0, $chapter_name = '')
 {
-       if ($chapter_no <= 0 or empty ($chapter_name))
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
+       if (!strlen ($chapter_name))
+               throw new InvalidArgException ('$chapter_name', $chapter_name);
        global $dbxlink;
        $query =
                "update Chapter set name = '${chapter_name}' where id = ${chapter_no} " .
@@ -2007,11 +2005,6 @@ function commitUpdateChapter ($chapter_no = 0, $chapter_name = '')
 
 function commitDeleteChapter ($chapter_no = 0)
 {
-       if ($chapter_no <= 0)
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
        global $dbxlink;
        $query =
                "delete from Chapter where id = ${chapter_no} and sticky = 'no' limit 1";
@@ -2028,11 +2021,8 @@ function commitDeleteChapter ($chapter_no = 0)
 // nice <select> drop-downs.
 function readChapter ($chapter_name = '')
 {
-       if (empty ($chapter_name))
-       {
-               showError ('invalid argument', __FUNCTION__);
-               return NULL;
-       }
+       if (!strlen ($chapter_name))
+               throw new InvalidArgException ('$chapter_name', $chapter_name);
        $query =
                "select dict_key, dict_value from Dictionary join Chapter on Chapter.id = Dictionary.chapter_id " .
                "where Chapter.name = '${chapter_name}'";
@@ -2046,47 +2036,49 @@ function readChapter ($chapter_name = '')
        return $chapter;
 }
 
+// Return a list of all stickers with sticker map applied. Each sticker records will
+// list all its ways on the map with refcnt set.
 function getAttrMap ()
 {
        $query =
-               "select a.id as attr_id, a.type as attr_type, a.name as attr_name, am.objtype_id, " .
-               "d.dict_value as objtype_name, am.chapter_id, c2.name as chapter_name from " .
-               "Attribute as a left join AttributeMap as am on a.id = am.attr_id " .
-               "left join Dictionary as d on am.objtype_id = d.dict_key " .
-               "left join Chapter as c1 on d.chapter_id = c1.id " .
-               "left join Chapter as c2 on am.chapter_id = c2.id " .
-               "where c1.name = 'RackObjectType' or c1.name is null " .
-               "order by a.name";
+               'SELECT id, type, name, chapter_id, (SELECT name FROM Chapter WHERE id = chapter_id) ' .
+               'AS chapter_name, objtype_id, (SELECT dict_value FROM Dictionary WHERE dict_key = objtype_id) ' .
+               'AS objtype_name, (SELECT COUNT(object_id) FROM AttributeValue AS av INNER JOIN RackObject AS ro ' .
+               'ON av.object_id = ro.id WHERE av.attr_id = Attribute.id AND ro.objtype_id = AttributeMap.objtype_id) ' .
+               'AS refcnt FROM Attribute LEFT JOIN AttributeMap ON id = attr_id ORDER BY Attribute.name, objtype_id';
        $result = useSelectBlade ($query, __FUNCTION__);
        $ret = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
-               $attr_id = $row['attr_id'];
-               if (!isset ($ret[$attr_id]))
-               {
-                       $ret[$attr_id]['id'] = $attr_id;
-                       $ret[$attr_id]['type'] = $row['attr_type'];
-                       $ret[$attr_id]['name'] = $row['attr_name'];
-                       $ret[$attr_id]['application'] = array();
-               }
+               if (!isset ($ret[$row['id']]))
+                       $ret[$row['id']] = array
+                       (
+                               'id' => $row['id'],
+                               'type' => $row['type'],
+                               'name' => $row['name'],
+                               'application' => array(),
+                       );
                if ($row['objtype_id'] == '')
                        continue;
-               $application['objtype_id'] = $row['objtype_id'];
-               $application['objtype_name'] = $row['objtype_name'];
-               if ($row['attr_type'] == 'dict')
+               $application = array
+               (
+                       'objtype_id' => $row['objtype_id'],
+                       'objtype_name' => $row['objtype_name'],
+                       'refcnt' => $row['refcnt'],
+               );
+               if ($row['type'] == 'dict')
                {
                        $application['chapter_no'] = $row['chapter_id'];
                        $application['chapter_name'] = $row['chapter_name'];
                }
-               $ret[$attr_id]['application'][] = $application;
+               $ret[$row['id']]['application'][] = $application;
        }
-       $result->closeCursor();
        return $ret;
 }
 
 function commitUpdateAttribute ($attr_id = 0, $attr_name = '')
 {
-       if ($attr_id <= 0 or empty ($attr_name))
+       if ($attr_id <= 0 or !strlen ($attr_name))
        {
                showError ('Invalid args', __FUNCTION__);
                die;
@@ -2106,7 +2098,7 @@ function commitUpdateAttribute ($attr_id = 0, $attr_name = '')
 
 function commitAddAttribute ($attr_name = '', $attr_type = '')
 {
-       if (empty ($attr_name))
+       if (!strlen ($attr_name))
        {
                showError ('Invalid args', __FUNCTION__);
                die;
@@ -2256,7 +2248,7 @@ function commitUpdateAttrValue ($object_id = 0, $attr_id = 0, $value = '')
                showError ('Invalid arguments', __FUNCTION__);
                die;
        }
-       if (empty ($value))
+       if (!strlen ($value))
                return commitResetAttrValue ($object_id, $attr_id);
        global $dbxlink;
        $query1 = "select type as attr_type from Attribute where id = ${attr_id}";
@@ -2366,7 +2358,6 @@ function useSelectBlade ($query, $caller = 'N/A')
        {
                $ei = $dbxlink->errorInfo();
                showError ("SQL query '${query}'\n failed in useSelectBlade with error ${ei[1]} (${ei[2]})", $caller);
-               debug_print_backtrace();
                return NULL;
        }
        return $result;
@@ -2387,11 +2378,10 @@ function loadConfigCache ()
 function storeConfigVar ($varname = NULL, $varvalue = NULL)
 {
        global $dbxlink;
-       if (empty ($varname) || $varvalue === NULL)
-       {
-               showError ('Invalid arguments', __FUNCTION__);
-               return FALSE;
-       }
+       if (!strlen ($varname))
+               throw new InvalidArgException ('$varname', $varname);
+       if ($varvalue === NULL)
+               throw new InvalidArgException ('$varvalue', $varvalue);
        $query = "update Config set varvalue='${varvalue}' where varname='${varname}' limit 1";
        $result = $dbxlink->query ($query);
        if ($result == NULL)
@@ -2422,7 +2412,7 @@ function getDatabaseVersion ()
                die (__FUNCTION__ . ': SQL query #1 failed with error ' . $errorInfo[2]);
        }
        $rows = $result->fetchAll (PDO::FETCH_NUM);
-       if (count ($rows) != 1 || empty ($rows[0][0]))
+       if (count ($rows) != 1 || !strlen ($rows[0][0]))
        {
                $result->closeCursor();
                die (__FUNCTION__ . ': Cannot guess database version. Config table is present, but DB_VERSION is missing or invalid. Giving up.');
@@ -2470,11 +2460,8 @@ function getSLBSummary ()
 function addRStoRSPool ($pool_id = 0, $rsip = '', $rsport = 0, $inservice = 'no', $rsconfig = '')
 {
        if ($pool_id <= 0)
-       {
-               showError ('Invalid arguments', __FUNCTION__);
-               die;
-       }
-       if (empty ($rsport) or $rsport == 0)
+               throw new InvalidArgException ('$pool_id', $pool_id);
+       if (!strlen ($rsport) or $rsport === 0)
                $rsport = 'NULL';
        return useInsertBlade
        (
@@ -2485,15 +2472,19 @@ function addRStoRSPool ($pool_id = 0, $rsip = '', $rsport = 0, $inservice = 'no'
                        'rsport' => $rsport,
                        'rspool_id' => $pool_id,
                        'inservice' => ($inservice == 'yes' ? "'yes'" : "'no'"),
-                       'rsconfig' => (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'")
+                       'rsconfig' => (!strlen ($rsconfig) ? 'NULL' : "'${rsconfig}'")
                )
        );
 }
 
 function commitCreateVS ($vip = '', $vport = 0, $proto = '', $name = '', $vsconfig, $rsconfig, $taglist = array())
 {
-       if (empty ($vip) or $vport <= 0 or empty ($proto))
-               return __FUNCTION__ . ': invalid arguments';
+       if (!strlen ($vip))
+               throw new InvalidArgException ('$vip', $vip);
+       if ($vport <= 0)
+               throw new InvalidArgException ('$vport', $vport);
+       if (!strlen ($proto))
+               throw new InvalidArgException ('$proto', $proto);
        if (!useInsertBlade
        (
                'IPv4VS',
@@ -2502,9 +2493,9 @@ function commitCreateVS ($vip = '', $vport = 0, $proto = '', $name = '', $vsconf
                        'vip' => "inet_aton('${vip}')",
                        'vport' => $vport,
                        'proto' => "'${proto}'",
-                       'name' => (empty ($name) ? 'NULL' : "'${name}'"),
-                       'vsconfig' => (empty ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
-                       'rsconfig' => (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'")
+                       'name' => (!strlen ($name) ? 'NULL' : "'${name}'"),
+                       'vsconfig' => (!strlen ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
+                       'rsconfig' => (!strlen ($rsconfig) ? 'NULL' : "'${rsconfig}'")
                )
        ))
                return __FUNCTION__ . ': SQL insertion failed';
@@ -2513,11 +2504,12 @@ function commitCreateVS ($vip = '', $vport = 0, $proto = '', $name = '', $vsconf
 
 function addLBtoRSPool ($pool_id = 0, $object_id = 0, $vs_id = 0, $vsconfig = '', $rsconfig = '')
 {
-       if ($pool_id <= 0 or $object_id <= 0 or $vs_id <= 0)
-       {
-               showError ('Invalid arguments', __FUNCTION__);
-               die;
-       }
+       if ($pool_id <= 0)
+               throw new InvalidArgException ('$pool_id', $pool_id);
+       if ($object_id <= 0)
+               throw new InvalidArgException ('$object_id', $object_id);
+       if ($vs_id <= 0)
+               throw new InvalidArgException ('$vs_id', $vs_id);
        return useInsertBlade
        (
                'IPv4LB',
@@ -2526,8 +2518,8 @@ function addLBtoRSPool ($pool_id = 0, $object_id = 0, $vs_id = 0, $vsconfig = ''
                        'object_id' => $object_id,
                        'rspool_id' => $pool_id,
                        'vs_id' => $vs_id,
-                       'vsconfig' => (empty ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
-                       'rsconfig' => (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'")
+                       'vsconfig' => (!strlen ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
+                       'rsconfig' => (!strlen ($rsconfig) ? 'NULL' : "'${rsconfig}'")
                )
        );
 }
@@ -2541,8 +2533,7 @@ function commitDeleteRS ($id = 0)
 
 function commitDeleteVS ($id = 0)
 {
-       if ($id <= 0)
-               return FALSE;
+       releaseFiles ('ipv4vs', $id);
        return useDeleteBlade ('IPv4VS', 'id', $id) && destroyTagsForEntity ('ipv4vs', $id);
 }
 
@@ -2564,22 +2555,14 @@ function commitDeleteLB ($object_id = 0, $pool_id = 0, $vs_id = 0)
 
 function commitUpdateRS ($rsid = 0, $rsip = '', $rsport = 0, $rsconfig = '')
 {
-       if ($rsid <= 0)
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
        if (long2ip (ip2long ($rsip)) !== $rsip)
-       {
-               showError ("Invalid IP address '${rsip}'", __FUNCTION__);
-               die;
-       }
-       if (empty ($rsport) or $rsport == 0)
+               throw new InvalidArgException ('$rsip', $rsip);
+       if (!strlen ($rsport) or $rsport === 0)
                $rsport = 'NULL';
        global $dbxlink;
        $query =
                "update IPv4RS set rsip = inet_aton('${rsip}'), rsport = ${rsport}, rsconfig = " .
-               (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
+               (!strlen ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
                " where id = ${rsid} limit 1";
        $result = $dbxlink->query ($query);
        if ($result == NULL)
@@ -2592,17 +2575,12 @@ function commitUpdateRS ($rsid = 0, $rsip = '', $rsport = 0, $rsconfig = '')
 
 function commitUpdateLB ($object_id = 0, $pool_id = 0, $vs_id = 0, $vsconfig = '', $rsconfig = '')
 {
-       if ($object_id <= 0 or $pool_id <= 0 or $vs_id <= 0)
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
        global $dbxlink;
        $query =
                "update IPv4LB set vsconfig = " .
-               (empty ($vsconfig) ? 'NULL' : "'${vsconfig}'") .
+               (!strlen ($vsconfig) ? 'NULL' : "'${vsconfig}'") .
                ', rsconfig = ' .
-               (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
+               (!strlen ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
                " where object_id = ${object_id} and rspool_id = ${pool_id} " .
                "and vs_id = ${vs_id} limit 1";
        $result = $dbxlink->exec ($query);
@@ -2614,19 +2592,20 @@ function commitUpdateLB ($object_id = 0, $pool_id = 0, $vs_id = 0, $vsconfig = '
 
 function commitUpdateVS ($vsid = 0, $vip = '', $vport = 0, $proto = '', $name = '', $vsconfig = '', $rsconfig = '')
 {
-       if ($vsid <= 0 or empty ($vip) or $vport <= 0 or empty ($proto))
-       {
-               showError ('Invalid args', __FUNCTION__);
-               die;
-       }
+       if (!strlen ($vip))
+               throw new InvalidArgException ('$vip', $vip);
+       if ($vport <= 0)
+               throw new InvalidArgException ('$vport', $vport);
+       if (!strlen ($proto))
+               throw new InvalidArgException ('$proto', $proto);
        global $dbxlink;
        $query = "update IPv4VS set " .
                "vip = inet_aton('${vip}'), " .
                "vport = ${vport}, " .
                "proto = '${proto}', " .
-               'name = ' . (empty ($name) ? 'NULL,' : "'${name}', ") .
-               'vsconfig = ' . (empty ($vsconfig) ? 'NULL,' : "'${vsconfig}', ") .
-               'rsconfig = ' . (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
+               'name = ' . (!strlen ($name) ? 'NULL,' : "'${name}', ") .
+               'vsconfig = ' . (!strlen ($vsconfig) ? 'NULL,' : "'${vsconfig}', ") .
+               'rsconfig = ' . (!strlen ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
                " where id = ${vsid} limit 1";
        $result = $dbxlink->exec ($query);
        if ($result === NULL)
@@ -2650,11 +2629,8 @@ function loadThumbCache ($rack_id = 0)
 function saveThumbCache ($rack_id = 0, $cache = NULL)
 {
        global $dbxlink;
-       if ($rack_id == 0 or $cache == NULL)
-       {
-               showError ('Invalid arguments', __FUNCTION__);
-               return;
-       }
+       if ($cache == NULL)
+               throw new InvalidArgException ('$cache', $cache);
        $data = base64_encode ($cache);
        $query = "update Rack set thumb_data = '${data}' where id = ${rack_id} limit 1";
        $result = $dbxlink->exec ($query);
@@ -2663,11 +2639,6 @@ function saveThumbCache ($rack_id = 0, $cache = NULL)
 function resetThumbCache ($rack_id = 0)
 {
        global $dbxlink;
-       if ($rack_id == 0)
-       {
-               showError ('Invalid argument', __FUNCTION__);
-               return;
-       }
        $query = "update Rack set thumb_data = NULL where id = ${rack_id} limit 1";
        $result = $dbxlink->exec ($query);
 }
@@ -2678,11 +2649,6 @@ function resetThumbCache ($rack_id = 0)
 // current object.
 function getRSPoolsForObject ($object_id = 0)
 {
-       if ($object_id <= 0)
-       {
-               showError ('Invalid object_id', __FUNCTION__);
-               return NULL;
-       }
        $query = 'select vs_id, inet_ntoa(vip) as vip, vport, proto, vs.name, pool.id as pool_id, ' .
                'pool.name as pool_name, count(rsip) as rscount, lb.vsconfig, lb.rsconfig from ' .
                'IPv4LB as lb inner join IPv4RSPool as pool on lb.rspool_id = pool.id ' .
@@ -2701,16 +2667,16 @@ function getRSPoolsForObject ($object_id = 0)
 
 function commitCreateRSPool ($name = '', $vsconfig = '', $rsconfig = '', $taglist = array())
 {
-       if (empty ($name))
-               return __FUNCTION__ . ': invalid arguments';
+       if (!strlen ($name))
+               throw new InvalidArgException ('$name', $name);
        if (!useInsertBlade
        (
                'IPv4RSPool',
                array
                (
-                       'name' => (empty ($name) ? 'NULL' : "'${name}'"),
-                       'vsconfig' => (empty ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
-                       'rsconfig' => (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'")
+                       'name' => (!strlen ($name) ? 'NULL' : "'${name}'"),
+                       'vsconfig' => (!strlen ($vsconfig) ? 'NULL' : "'${vsconfig}'"),
+                       'rsconfig' => (!strlen ($rsconfig) ? 'NULL' : "'${rsconfig}'")
                )
        ))
                return __FUNCTION__ . ': SQL insertion failed';
@@ -2722,21 +2688,17 @@ function commitDeleteRSPool ($pool_id = 0)
        global $dbxlink;
        if ($pool_id <= 0)
                return FALSE;
+       releaseFiles ('ipv4rspool', $pool_id);
        return useDeleteBlade ('IPv4RSPool', 'id', $pool_id) && destroyTagsForEntity ('ipv4rspool', $pool_id);
 }
 
 function commitUpdateRSPool ($pool_id = 0, $name = '', $vsconfig = '', $rsconfig = '')
 {
-       if ($pool_id <= 0)
-       {
-               showError ('Invalid arg', __FUNCTION__);
-               die;
-       }
        global $dbxlink;
        $query = "update IPv4RSPool set " .
-               'name = ' . (empty ($name) ? 'NULL,' : "'${name}', ") .
-               'vsconfig = ' . (empty ($vsconfig) ? 'NULL,' : "'${vsconfig}', ") .
-               'rsconfig = ' . (empty ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
+               'name = ' . (!strlen ($name) ? 'NULL,' : "'${name}', ") .
+               'vsconfig = ' . (!strlen ($vsconfig) ? 'NULL,' : "'${vsconfig}', ") .
+               'rsconfig = ' . (!strlen ($rsconfig) ? 'NULL' : "'${rsconfig}'") .
                " where id = ${pool_id} limit 1";
        $result = $dbxlink->exec ($query);
        if ($result === NULL)
@@ -2773,17 +2735,12 @@ function getLBList ()
        return $ret;
 }
 
-// For the given object return: it vsconfig/rsconfig; the list of RS pools
+// For the given object return: its vsconfig/rsconfig; the list of RS pools
 // attached (each with vsconfig/rsconfig in turn), each with the list of
 // virtual services terminating the pool. Each pool also lists all real
 // servers with rsconfig.
 function getSLBConfig ($object_id)
 {
-       if ($object_id <= 0)
-       {
-               showError ('Invalid arg', __FUNCTION__);
-               return NULL;
-       }
        $ret = array();
        $query = 'select vs_id, inet_ntoa(vip) as vip, vport, proto, vs.name as vs_name, ' .
                'vs.vsconfig as vs_vsconfig, vs.rsconfig as vs_rsconfig, ' .
@@ -2814,11 +2771,10 @@ function getSLBConfig ($object_id)
 
 function commitSetInService ($rs_id = 0, $inservice = '')
 {
-       if ($rs_id <= 0 or empty ($inservice))
-       {
-               showError ('Invalid args', __FUNCTION__);
-               return NULL;
-       }
+       if (!strlen ($inservice))
+               throw new InvalidArgException ('$inservice', $inservice);
+       if ($rs_id <= 0)
+               throw new InvalidArgException ('$rs_id', $rs_id);
        global $dbxlink;
        $query = "update IPv4RS set inservice = '${inservice}' where id = ${rs_id} limit 1";
        $result = $dbxlink->exec ($query);
@@ -2832,11 +2788,10 @@ function commitSetInService ($rs_id = 0, $inservice = '')
 
 function executeAutoPorts ($object_id = 0, $type_id = 0)
 {
-       if ($object_id == 0 or $type_id == 0)
-       {
-               showError ('Invalid arguments', __FUNCTION__);
-               die;
-       }
+       if ($object_id == 0)
+               throw new InvalidArgException ('$object_id', $object_id);
+       if ($type_id == 0)
+               throw new InvalidArgException ('$type_id', $type_id);
        $ret = TRUE;
        foreach (getAutoPorts ($type_id) as $autoport)
                $ret = $ret and '' == commitAddPort ($object_id, $autoport['name'], $autoport['type'], '', '');
@@ -2951,6 +2906,8 @@ function destroyTagsForEntity ($entity_realm, $entity_id)
 }
 
 // Drop only one record. This operation doesn't involve retossing other tags, unlike when adding.
+// FIXME: this function could be used by 3rd-party scripts, dismiss it at some later point,
+// but not now.
 function deleteTagForEntity ($entity_realm, $entity_id, $tag_id)
 {
        global $dbxlink;
@@ -2965,7 +2922,7 @@ function deleteTagForEntity ($entity_realm, $entity_id, $tag_id)
 // Push a record into TagStorage unconditionally.
 function addTagForEntity ($realm = '', $entity_id, $tag_id)
 {
-       if (empty ($realm))
+       if (!strlen ($realm))
                return FALSE;
        return useInsertBlade
        (
@@ -3032,7 +2989,7 @@ function createIPv4Prefix ($range = '', $name = '', $is_bcast = FALSE, $taglist
        $ip = $rangeArray[0];
        $mask = $rangeArray[1];
 
-       if (empty ($ip) or empty ($mask))
+       if (!strlen ($ip) or !strlen ($mask))
                return "Invalid IPv4 prefix '${range}'";
        $ipL = ip2long($ip);
        $maskL = ip2long($mask);
@@ -3090,6 +3047,7 @@ function destroyIPv4Prefix ($id = 0)
 {
        if ($id <= 0)
                return __FUNCTION__ . ': Invalid IPv4 prefix ID';
+       releaseFiles ('ipv4net', $id);
        if (!useDeleteBlade ('IPv4Network', 'id', $id))
                return __FUNCTION__ . ': SQL query #1 failed';
        if (!destroyTagsForEntity ('ipv4net', $id))
@@ -3107,13 +3065,10 @@ function loadScript ($name)
                return NULL;
 }
 
-function saveScript ($name, $text)
+function saveScript ($name = '', $text)
 {
-       if (empty ($name))
-       {
-               showError ('Invalid argument');
-               return FALSE;
-       }
+       if (!strlen ($name))
+               throw new InvalidArgException ('$name', $name);
        // delete regardless of existence
        useDeleteBlade ('Script', 'script_name', "'${name}'");
        return useInsertBlade
@@ -3275,20 +3230,14 @@ function mergeSearchResults (&$objects, $terms, $fieldname)
        return $objects;
 }
 
-function getLostIPv4Addresses ()
-{
-       dragon();
-}
-
-// Return a list of files which are not linked to the specified record. This list
+// Return a list of files, which are not linked to the specified record. This list
 // will be used by printSelect().
 function getAllUnlinkedFiles ($entity_type = NULL, $entity_id = 0)
 {
-       if ($entity_type == NULL || $entity_id == 0)
-       {
-               showError ('Invalid parameters', __FUNCTION__);
-               return NULL;
-       }
+       if ($entity_type == NULL)
+               throw new InvalidArgException ('$entity_type', $entity_type);
+       if ($entity_id == 0)
+               throw new InvalidArgException ('$entity_id', $entity_id);
        global $dbxlink;
        $sql =
                'SELECT id, name FROM File ' .
@@ -3308,11 +3257,10 @@ function getAllUnlinkedFiles ($entity_type = NULL, $entity_id = 0)
 // it conveniently.
 function getFilesOfEntity ($entity_type = NULL, $entity_id = 0)
 {
-       if ($entity_type == NULL || $entity_id == 0)
-       {
-               showError ('Invalid parameters', __FUNCTION__);
-               return NULL;
-       }
+       if ($entity_type === NULL)
+               throw new InvalidArgException ('$entity_type', $entity_type);
+       if ($entity_id <= 0)
+               throw new InvalidArgException ('$entity_id', $entity_id);
        global $dbxlink;
        $sql =
                'SELECT FileLink.file_id, FileLink.id AS link_id, name, type, size, ctime, mtime, atime, comment ' .
@@ -3340,11 +3288,6 @@ function getFilesOfEntity ($entity_type = NULL, $entity_id = 0)
 
 function getFile ($file_id = 0)
 {
-       if ($file_id == 0)
-       {
-               showError ('Invalid file_id', __FUNCTION__);
-               return NULL;
-       }
        global $dbxlink;
        $query = $dbxlink->prepare('SELECT * FROM File WHERE id = ?');
        $query->bindParam(1, $file_id);
@@ -3377,46 +3320,8 @@ function getFile ($file_id = 0)
        return $ret;
 }
 
-function getFileInfo ($file_id = 0)
-{
-       if ($file_id == 0)
-       {
-               showError ('Invalid file_id', __FUNCTION__);
-               return NULL;
-       }
-       global $dbxlink;
-       $query = $dbxlink->prepare('SELECT id, name, type, size, ctime, mtime, atime, comment FROM File WHERE id = ?');
-       $query->bindParam(1, $file_id);
-       $query->execute();
-       if (($row = $query->fetch (PDO::FETCH_ASSOC)) == NULL)
-       {
-               showError ('Query succeeded, but returned no data', __FUNCTION__);
-               $ret = NULL;
-       }
-       else
-       {
-               $ret = array();
-               $ret['id'] = $row['id'];
-               $ret['name'] = $row['name'];
-               $ret['type'] = $row['type'];
-               $ret['size'] = $row['size'];
-               $ret['ctime'] = $row['ctime'];
-               $ret['mtime'] = $row['mtime'];
-               $ret['atime'] = $row['atime'];
-               $ret['comment'] = $row['comment'];
-               $query->closeCursor();
-       }
-       return $ret;
-}
-
 function getFileLinks ($file_id = 0)
 {
-       if ($file_id <= 0)
-       {
-               showError ('Invalid file_id', __FUNCTION__);
-               return NULL;
-       }
-
        global $dbxlink;
        $query = $dbxlink->prepare('SELECT * FROM FileLink WHERE file_id = ? ORDER BY entity_type, entity_id');
        $query->bindParam(1, $file_id);
@@ -3449,7 +3354,7 @@ function getFileLinks ($file_id = 0)
                        case 'object':
                                $page = 'object';
                                $id_name = 'object_id';
-                               $parent = getObjectInfo($row['entity_id']);
+                               $parent = spotEntity ($row['entity_type'], $row['entity_id']);
                                $name = $parent['dname'];
                                break;
                        case 'rack':
@@ -3461,13 +3366,13 @@ function getFileLinks ($file_id = 0)
                        case 'user':
                                $page = 'user';
                                $id_name = 'user_id';
-                               $userinfo = getUserInfo ($row['entity_id']);
-                               $name = $userinfo['user_name'];
+                               $parent = spotEntity ($row['entity_type'], $row['entity_id']);
+                               $name = $parent['user_name'];
                                break;
                }
 
                // name needs to have some value for hrefs to work
-        if (empty($name))
+        if (!strlen ($name))
                        $name = sprintf("[Unnamed %s]", formatEntityName($row['entity_type']));
 
                $ret[$row['id']] = array(
@@ -3481,46 +3386,30 @@ function getFileLinks ($file_id = 0)
        return $ret;
 }
 
-// Return list of possible file parents along with the number of children.
-// Used on main Files listing page.
-function getFileLinkInfo ()
+function getFileStats ()
 {
-       global $dbxlink;
        $query = 'SELECT entity_type, COUNT(*) AS count FROM FileLink GROUP BY entity_type';
-
        $result = useSelectBlade ($query, __FUNCTION__);
        $ret = array();
-       $ret[0] = array ('entity_type' => 'all', 'name' => 'ALL files');
-       $clist = array ('entity_type', 'name', 'count');
-       $total = 0;
-       $i=2;
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
                if ($row['count'] > 0)
-               {
-                       $total += $row['count'];
-                       $row['name'] = formatEntityName ($row['entity_type']);
-                       foreach ($clist as $cname)
-                               $ret[$i][$cname] = $row[$cname];
-                               $i++;
-               }
-       $result->closeCursor();
+                       $ret["Links in realm '${row['entity_type']}'"] = $row['count'];
+       unset ($result);
 
        // Find number of files without any linkage
        $linkless_sql =
                'SELECT COUNT(*) ' .
                'FROM File ' .
                'WHERE id NOT IN (SELECT file_id FROM FileLink)';
-       $q_linkless = useSelectBlade ($linkless_sql, __FUNCTION__);
-       $ret[1] = array ('entity_type' => 'no_links', 'name' => 'Files w/no links', 'count' => $q_linkless->fetchColumn ());
-       $q_linkless->closeCursor();
+       $result = useSelectBlade ($linkless_sql, __FUNCTION__);
+       $ret["Unattached files"] = $result->fetchColumn ();
+       unset ($result);
 
        // Find total number of files
        $total_sql = 'SELECT COUNT(*) FROM File';
-       $q_total = useSelectBlade ($total_sql, __FUNCTION__);
-       $ret[0]['count'] = $q_total->fetchColumn ();
-       $q_total->closeCursor();
+       $result = useSelectBlade ($total_sql, __FUNCTION__);
+       $ret["Total files"] = $result->fetchColumn ();
 
-       ksort($ret);
        return $ret;
 }
 
@@ -3568,12 +3457,6 @@ function commitLinkFile ($file_id, $entity_type, $entity_id)
 
 function commitReplaceFile ($file_id = 0, $contents)
 {
-       if ($file_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return FALSE;
-       }
-
        global $dbxlink;
        $query = $dbxlink->prepare('UPDATE File SET mtime = NOW(), contents = ?, size = LENGTH(contents) WHERE id = ?');
        $query->bindParam(1, $contents, PDO::PARAM_LOB);
@@ -3590,11 +3473,10 @@ function commitReplaceFile ($file_id = 0, $contents)
 
 function commitUpdateFile ($file_id = 0, $new_name = '', $new_type = '', $new_comment = '')
 {
-       if ($file_id <= 0 or empty ($new_name) or empty ($new_type))
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return FALSE;
-       }
+       if (!strlen ($new_name))
+               throw new InvalidArgException ('$new_name', $new_name);
+       if (!strlen ($new_type))
+               throw new InvalidArgException ('$new_type', $new_type);
        global $dbxlink;
        $query = $dbxlink->prepare('UPDATE File SET name = ?, type = ?, comment = ? WHERE id = ?');
        $query->bindParam(1, $new_name);
@@ -3617,6 +3499,7 @@ function commitUnlinkFile ($link_id)
 
 function commitDeleteFile ($file_id)
 {
+       destroyTagsForEntity ('file', $file_id);
        if (useDeleteBlade ('File', 'id', $file_id) != TRUE)
                return __FUNCTION__ . ': useDeleteBlade() failed';
        return '';
@@ -3714,17 +3597,4 @@ function getUserIDByUsername ($username)
        return NULL;
 }
 
-function getUserInfo ($user_id)
-{
-       $query = "select 'user' as realm, user_id, user_name, user_password_hash, user_realname from UserAccount where user_id = ${user_id}";
-       if (($result = useSelectBlade ($query, __FUNCTION__)) == NULL)
-       {
-               showError ('SQL query failed', __FUNCTION__);
-               die;
-       }
-       if ($row = $result->fetch (PDO::FETCH_ASSOC))
-               return $row;
-       return NULL;
-}
-
 ?>