/**
  * Deletes an entire tree of keys
  *
  * @version 1.0
  * @since 1.0
  *
  * @param string $tree | The tree name
  * @return bool | False on failure. True on success.
  */
 public function dropTree($tree)
 {
     global $fox;
     $db = new FOX_db();
     // Fetch the key_id's for all matching keys from the db
     $args = array(array("col" => "tree", "op" => "=", "val" => $tree));
     $columns = array("mode" => "include", "col" => array("key_id"));
     $ctrl = array("format" => "col");
     try {
         $drop_ids = $db->runSelectQuery(self::$struct, $args, $columns, $ctrl);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "DB select exception", 'data' => array("args" => $args, "columns" => $columns, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     try {
         $started_transaction = $db->beginTransaction();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 2, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     if ($started_transaction) {
         $args = array(array("col" => "key_id", "op" => "=", "val" => $drop_ids));
         try {
             $db->runDeleteQuery(self::$struct, $args);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 3, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // TODO:
         // 1) drop the keys from the user keystore
         // 2) purge the dropped keys from the user keystore cache
         // 3) drop the keys from all groups that grant them
         try {
             $query_ok = $db->commitTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 4, 'text' => "commitTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
     } else {
         $query_ok = false;
     }
     // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     // Update the cache
     if ($query_ok) {
         // Update the class cache from the persistent cache
         try {
             self::loadCache();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 5, 'text' => "loadCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // Flush the deleted branch from the class cache
         unset($this->cache["keys"][$tree_name]);
         unset($this->cache["branches"][$tree_name]);
         unset($this->cache["trees"][$tree_name]);
         // Write the updated class cache array to the persistent cache
         try {
             $cache_ok = self::saveCache();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 6, 'text' => "saveCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         return $cache_ok;
     } else {
         return false;
     }
 }
示例#2
0
 /**
  * Creates or updates one or more keys.
  *
  * @version 1.0
  * @since 1.0
  *
  * @param array $data | Array of row arrays
  *	=> ARR @param int '' | Individual row array
  *	    => VAL @param int $user_id | user id
  *	    => VAL @param int $tree | tree id
  *	    => VAL @param int $branch | branch id
  *	    => VAL @param string $key | key name
  *	    => VAL @param bool/int/float/string/array/obj $val | key value
  * @return bool | False on failure. True on success.
  */
 public function setKeyMulti($data)
 {
     global $rad;
     $db = new FOX_db();
     //var_dump($data);
     $cache_update = array();
     $insert_data = array();
     // Process each row
     // ===========================================================
     foreach ($data as $row) {
         if (empty($row["user_id"])) {
             throw new FOX_exception(array('numeric' => 1, 'text' => "Empty user_id", 'data' => $row, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
         }
         if (empty($row["tree"])) {
             throw new FOX_exception(array('numeric' => 2, 'text' => "Empty tree name", 'data' => $row, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
         }
         if (empty($row["branch"])) {
             throw new FOX_exception(array('numeric' => 3, 'text' => "Empty branch name", 'data' => $row, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
         }
         if (empty($row["key"])) {
             throw new FOX_exception(array('numeric' => 4, 'text' => "Empty key name", 'data' => $row, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
         }
         // Expand the key into a heirarchical array
         $update_data[$row["user_id"]][$row["tree"]][$row["branch"]][$row["key"]] = $row["val"];
     }
     unset($row);
     // Load the persistent cache records for all the module_id's into the temp class cache array
     try {
         $update_cache = $rad->cache->getMulti("FOX_uData", array_keys($update_data));
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 5, 'text' => "Cache get error", 'data' => $update_data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     try {
         $begin_ok = $db->beginTransaction();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 6, 'text' => "Couldn't initiate transaction", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     foreach ($update_data as $user_id => $trees) {
         foreach ($trees as $tree => $branches) {
             foreach ($branches as $branch => $keys) {
                 foreach ($keys as $key => $val) {
                     $insert_data = array("user_id" => $user_id, "tree" => $tree, "branch" => $branch, "node" => $key, "val" => $val);
                     try {
                         $query_ok = $db->runIndateQuery(self::$struct, $insert_data, $columns = null);
                     } catch (FOX_exception $child) {
                         try {
                             $db->rollbackTransaction();
                         } catch (FOX_exception $child) {
                             throw new FOX_exception(array('numeric' => 8, 'text' => "rollbackTransaction", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                         }
                         throw new FOX_exception(array('numeric' => 7, 'text' => "Error while writing to the database", 'data' => $insert_data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     // Overwrite the class cache array with the data set in the db query
                     $update_cache[$user_id]["keys"][$tree][$branch][$key] = $val;
                 }
                 unset($key, $val);
             }
             unset($branch, $key);
         }
         unset($tree, $branches);
     }
     unset($user_id, $trees);
     try {
         $commit_ok = $db->commitTransaction();
     } catch (FOX_exception $child) {
         // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         throw new FOX_exception(array('numeric' => 8, 'text' => "Error commiting transaction to database", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Overwrite the persistent cache records with the temp class cache array items
     try {
         $cache_ok = $rad->cache->setMulti("FOX_uData", $update_cache);
     } catch (BPN_exception $child) {
         throw new FOX_exception(array('numeric' => 9, 'text' => "Cache set error", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Write the temp class cache array items to the class cache array
     foreach ($update_cache as $key => $val) {
         $this->cache[$key] = $val;
     }
     unset($key, $val);
     return true;
 }
示例#3
0
 /**
  * Deletes a single group, given its group_id
  *
  * @version 1.0
  * @since 1.0
  *
  * @param int $group_id | id of the group
  * @return bool | False on failure. True on success.
  */
 public function deleteGroup($group_id)
 {
     global $fox;
     $db = new FOX_db();
     $columns = array("mode" => "include", "col" => array("is_default", "name"));
     $ctrl = array("format" => "row_array");
     try {
         $group = $db->runSelectQueryCol(self::$struct, "group_id", "=", $group_id, $columns, $ctrl);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "DB select exception", 'data' => array("data" => $data, "col" => "group_id", "op" => "=", "val" => $group_id, "columns" => $columns, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // If the group we're trying to delete is the default group, reject the action. There must *always* be a
     // default group on the system. If the admin wants to delete the default group, they have to make
     // another group the default group first.
     if ($group["is_default"] == true) {
         //echo "\nclass.user.group.types::deleteGroup() - attempted delete on default group\n";
         return false;
     }
     // Trap trying to delete a nonexistent group
     if (!$group) {
         //echo "\nclass.user.group.types::deleteGroup() - attempted delete on nonexistent group: $group_id \n";
         return false;
     }
     // Get the user_id of every user in the group we're deleting
     $columns = array("mode" => "include", "col" => "user_id");
     $ctrl = array("format" => "col");
     try {
         $user_ids = $db->runSelectQueryCol(FOX_uGroupMember::_struct(), "group_id", "=", $group_id, $columns, $ctrl);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 2, 'text' => "DB select exception", 'data' => array("data" => $data, "col" => "group_id", "op" => "=", "val" => $group_id, "columns" => $columns, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // CASE 1: There are users that are members of the group
     // ===============================================================================
     if ($user_ids) {
         // Load all of the groups that each user is currently in, except
         // the group we're removing them from
         $args = array(array("col" => "user_id", "op" => "=", "val" => $user_ids), array("col" => "group_id", "op" => "!=", "val" => $group_id));
         $ctrl = array("format" => "array_key_array_grouped", "key_col" => array("user_id", "group_id"));
         try {
             $in_groups = $db->runSelectQuery(FOX_uGroupMember::_struct(), $args, $columns = null, $ctrl);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 3, 'text' => "DB select exception", 'data' => array("args" => $args, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         try {
             $started_transaction = $db->beginTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 4, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($started_transaction) {
             $keys_ok = true;
             try {
                 $gk = new FOX_uGroupKeyRing();
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 2, 'text' => "FOX_uGroupKeyRing constructor exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             foreach ($user_ids as $user) {
                 // Get the combined keyring of all the user's other groups
                 try {
                     $keep_keys = $gk->getKeys($in_groups[$user]);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 5, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("user" => $in_groups[$user]), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Get the keyring of the group we're removing the user from
                 try {
                     $drop_keys = $gk->getKeys($group_id);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 6, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Intersect the $keep_keys and $drop_keys arrays to get
                 // a list of keys we need to revoke from the user
                 if ($keep_keys && $drop_keys) {
                     $revoke_keys = array_diff($drop_keys, $keep_keys);
                 } else {
                     $revoke_keys = $drop_keys;
                 }
                 // Revoke all the keys we previously calculated
                 if ($revoke_keys) {
                     $ks = new FOX_uKeyRing();
                     try {
                         $revoke_ok = $ks->revokeKey($user, $revoke_keys);
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 7, 'text' => "FOX_uKeyRing revokeKeys exception", 'data' => array("user" => $user, "revoke_keys" => $revoke_keys), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     if (!$revoke_ok) {
                         $keys_ok = false;
                     }
                 } else {
                     // Handle no keys to revoke
                     $keys_ok = true;
                 }
             }
             unset($user);
             // Because we are inside a transaction, we have to directly delete items from
             // the other class's db tables. If we deleted items using the other class's
             // functions, the other classes would remove them from their caches before we
             // could confirm all steps in the transaction were successful.
             // ============================================================================
             // Drop the group-user pairs from the group members table
             $args = array(array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $gm_ok = $db->runDeleteQuery(FOX_uGroupMember::_struct(), $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 8, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Drop the group-key pairs from the group keyring table
             $args = array(array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $gk_ok = $db->runDeleteQuery(FOX_uGroupKeyRing::_struct(), $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 9, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Drop the group from the group types table
             $args = array(array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $gt_ok = $db->runDeleteQuery(self::$struct, $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 10, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Update the cache
             if ($keys_ok && $gm_ok !== false && $gk_ok !== false && $gt_ok) {
                 // Handle groups with no members and
                 // groups with no keys returning (int)0
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 11, 'text' => "commitTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 if ($commit_ok) {
                     // Because we directly modified other class's db tables, we have to
                     // flush the cache for the affected classes
                     try {
                         $fox->cache->flushNamespace("FOX_uGroupMember");
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 12, 'text' => "FOX_uGroupMember flushNamespace exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     try {
                         $fox->cache->flushNamespace("FOX_uGroupKeyRing");
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 13, 'text' => "FOX_uGroupKeyRing flushNamespace exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     // Load, update, writeback
                     try {
                         self::loadCache();
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 14, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("user" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     unset($this->cache["ids"][$group["name"]]);
                     $cache_ok = self::saveCache();
                     return $cache_ok;
                 } else {
                     return false;
                 }
             }
         } else {
             // If we couldn't start a transaction, return false
             return false;
         }
         // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     } else {
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         try {
             $started_transaction = $db->beginTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 15, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($started_transaction) {
             // Because we are inside a transaction, we have to directly delete items from
             // the other class's db tables. If we deleted items using the other class's
             // functions, the other classes would remove them from their caches before we
             // could confirm all steps in the transaction were successful.
             // ============================================================================
             // Drop the group-key pairs from the group keyring table
             $args = array(array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $gk_ok = $db->runDeleteQuery(FOX_uGroupKeyRing::_struct(), $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 16, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Drop the group from the group types table
             $args = array(array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $gt_ok = $db->runDeleteQuery(self::$struct, $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 17, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Update the cache
             if ($gk_ok !== false && $gt_ok) {
                 // Handle groups with no keys
                 // returning (int)0
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 18, 'text' => "commitTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 if ($commit_ok) {
                     // Because we directly modified another class's db table, we
                     // have to flush the cache for the affected class
                     try {
                         $fox->cache->flushNamespace("FOX_uGroupKeyRing");
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 2, 'text' => "FOX_uGroupKeyRing flushNamespace exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     // Load, update, writeback
                     try {
                         self::loadCache();
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 19, 'text' => "loadCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     unset($this->cache["ids"][$group["name"]]);
                     try {
                         $cache_ok = self::saveCache();
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 20, 'text' => "saveCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     return $cache_ok;
                 } else {
                     return false;
                 }
             }
         } else {
             // If we couldn't start a transaction, return false
             return false;
         }
         // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
     // It might be possible to do this using a sophisticated query
     // Remove all keys granted by the group, from every user on the site, unless another group grants
     // the key, and the user is a member of that other group
     // ========================================================
     // DELETE kst
     // FROM user_keystore_table AS kst
     // INNER JOIN group_members_table AS gmt ON kst.user_id = gmt.user_id	    // user has to be a member of the deleted group
     // INNER JOIN group_keyring_table AS gkt ON gmt.group_id = gkt.group_id	    // key has to be granted by the deleted group
     // WHERE kst.key_id NOT IN (SELECT key_id
     //			    FROM group_keyring_table AS gkt2
     //			    INNER JOIN group_members_table AS gmt2 ON gkt2.group_id = gmt2.group_id
     //			    WHERE gmt2.group_id != gmt.group_id	    // where the key does not belong to another group
     //			    AND gmt2.user_id = gmt.user_id )	    // and the user is a member of that group
     // AND gkt.group_id = [this group]
     // AND gmt.group_id = [this group]
     // ...It also might be possible to do this using MySQL "foreign keys"
 }
 /**
  * Removes one or more users from one or more groups
  *
  * @version 1.0
  * @since 1.0
  *
  * @param int/array $user_id | Single user_id as int. Multiple user_id's as array of ints.
  * @param int/array $group_id | Single group_id as int. Multiple group_id's as array of ints.
  * @return int | False on failure. Int number of rows deleted on success. Int 0 if no rows deleted.
  */
 public function removeFromGroup($user_id, $group_id)
 {
     global $fox;
     $db = new FOX_db();
     try {
         $gk = new FOX_uGroupKeyRing();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "FOX_uGroupKeyRing constructor exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // CASE 1: Single user_id, single group_id
     // =================================================================
     if (!is_array($user_id) && !is_array($group_id)) {
         // Get the combined keyring of the user's current groups,
         // minus the one we're removing the user from
         $args = array(array("col" => "user_id", "op" => "=", "val" => $user_id), array("col" => "group_id", "op" => "!=", "val" => $group_id));
         $columns = array("mode" => "include", "col" => "group_id");
         $ctrl = array("format" => "col");
         try {
             $in_groups = $db->runSelectQuery(self::$struct, $args, $columns, $ctrl);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 2, 'text' => "DB select exception", 'data' => array("args" => $args, "columns" => $columns, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         try {
             $keep_keys = $gk->getKeys($in_groups);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 3, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("in_groups" => $in_groups), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // Get the keyring for the group we're removing the user from
         try {
             $drop_keys = $gk->getKeys($group_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 4, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // Intersect the remove group's keyring with the user's other groups' keyrings
         // to determine the keys that need to be revoked from the user's keyring
         if ($keep_keys && $drop_keys) {
             $revoke_keys = array_diff($drop_keys, $keep_keys);
         } else {
             $revoke_keys = $drop_keys;
         }
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         try {
             $transaction_started = $db->beginTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 5, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($transaction_started) {
             // Drop the user-group pair from the groupstore db table
             $args = array(array("col" => "user_id", "op" => "=", "val" => $user_id), array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $rows_changed = $db->runDeleteQuery(self::$struct, $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 6, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // If the user is not a member of the group we are attempting to delete
             // them from, return "0" to indicate that no rows were changed
             if (!$rows_changed) {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 7, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return 0;
             }
             // Revoke the keys on the group's keyring that the user is not
             // being granted by other groups
             if ($revoke_keys) {
                 $ks = new FOX_uKeyRing();
                 try {
                     $keys_ok = $ks->revokeKey($user_id, $revoke_keys);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 8, 'text' => "FOX_uKeyRing revokeKey exception", 'data' => array("user_id" => $user_id, "revoke_keys" => $revoke_keys), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
             } else {
                 // Handle no keys to revoke
                 $keys_ok = true;
             }
             // If all operations were successful, commit the transaction
             if ($rows_changed && $keys_ok) {
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 9, 'text' => "commitTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Update the cache
                 if ($commit_ok) {
                     // Load, update, writeback
                     $this->cache[$user_id] = $fox->cache->get("FOX_uGroupMember", $user_id);
                     $this->cache[$user_id]["keys"][$group_id] = false;
                     $cache_ok = $fox->cache->set("FOX_uGroupMember", $user_id, $this->cache[$user_id]);
                     if ($cache_ok) {
                         return $rows_changed;
                     } else {
                         return false;
                     }
                 } else {
                     return false;
                 }
             } else {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 10, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return false;
             }
         } else {
             // If we couldn't start a transaction, return false
             return false;
         }
         // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
     // CASE 2: Single user_id, multiple groups
     // =================================================================
     if (!is_array($user_id) && is_array($group_id)) {
         // Get the combined keyring of all the user's current groups,
         // minus the groups we're removing them from
         $args = array(array("col" => "user_id", "op" => "=", "val" => $user_id), array("col" => "group_id", "op" => "!=", "val" => $group_id));
         $columns = array("mode" => "include", "col" => "group_id");
         $ctrl = array("format" => "col");
         try {
             $in_groups = $db->runSelectQuery(self::$struct, $args, $columns, $ctrl);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 11, 'text' => "DB select exception", 'data' => array("args" => $args, "columns" => $columns, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         try {
             $keep_keys = $gk->getKeys($in_groups);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 12, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("in_groups" => $in_groups), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // Get the combined keyring of all the groups that we're
         // removing the user from
         try {
             $drop_keys = $gk->getKeys($group_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 13, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("groiup_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // Intersect the $keep_keys and $drop_keys arrays to get
         // a list of keys we need to revoke from the user
         if ($keep_keys && $drop_keys) {
             $revoke_keys = array_diff($drop_keys, $keep_keys);
         } else {
             $revoke_keys = $drop_keys;
         }
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         try {
             $transaction_started = $db->beginTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 14, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($transaction_started) {
             // Drop the user-group pairs from the groupstore db table
             $args = array(array("col" => "user_id", "op" => "=", "val" => $user_id), array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $rows_changed = $db->runDeleteQuery(self::$struct, $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 15, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // If the user is not a member of any of the groups we're trying to delete
             // them from, return "0" to indicate that no db rows were changed
             if (!$rows_changed) {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 16, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return 0;
             }
             // Revoke all the keys we previously calculated
             if ($revoke_keys) {
                 $ks = new FOX_uKeyRing();
                 try {
                     $keys_ok = $ks->revokeKey($user_id, $revoke_keys);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 17, 'text' => "FOX_uKeyRing revokeKey exception", 'data' => array("user_id" => $user_id, "revoke_keys" => $revoke_keys), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
             } else {
                 // Handle no keys to revoke
                 $keys_ok = true;
             }
             // If all operations were successful, commit the transaction
             if ($rows_changed && $keys_ok) {
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 18, 'text' => "commitTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Update the cache
                 if ($commit_ok) {
                     // Load, update, writeback
                     $this->cache[$user_id] = $fox->cache->get("FOX_uGroupMember", $user_id);
                     foreach ($group_id as $group) {
                         $this->cache[$user_id]["keys"][$group] = false;
                     }
                     unset($group);
                     $cache_ok = $fox->cache->set("FOX_uGroupMember", $user_id, $this->cache[$user_id]);
                     if ($cache_ok) {
                         return $rows_changed;
                     } else {
                         return false;
                     }
                 } else {
                     return false;
                 }
             } else {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 19, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return false;
             }
         } else {
             // If we couldn't start a transaction, return false
             return false;
         }
         // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
     // CASE 3: Multiple user_id's, single group
     // =================================================================
     if (is_array($user_id) && !is_array($group_id)) {
         // Load all of the groups that each user is currently in, except
         // the group we're removing them from
         $args = array(array("col" => "user_id", "op" => "=", "val" => $user_id), array("col" => "group_id", "op" => "!=", "val" => $group_id));
         $ctrl = array("format" => "array_key_array_grouped", "key_col" => array("user_id", "group_id"));
         try {
             $in_groups = $db->runSelectQuery(self::$struct, $args, $columns = null, $ctrl);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 20, 'text' => "DB select exception", 'data' => array("args" => $args, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         try {
             $started_transaction = $db->beginTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 21, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($started_transaction) {
             $all_ok = true;
             try {
                 $gk = new FOX_uGroupKeyRing();
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 22, 'text' => "FOX_uGroupKeyRing constructor exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             foreach ($user_id as $user) {
                 // Get the combined keyring of all the user's other groups
                 try {
                     $keep_keys = $gk->getKeys($in_groups[$user]);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 23, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("user" => $in_groups[$user]), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Get the keyring of the group we're removing the user from
                 try {
                     $drop_keys = $gk->getKeys($group_id);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 24, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Intersect the $keep_keys and $drop_keys arrays to get
                 // a list of keys we need to revoke from the user
                 if ($keep_keys && $drop_keys) {
                     $revoke_keys = array_diff($drop_keys, $keep_keys);
                 } else {
                     $revoke_keys = $drop_keys;
                 }
                 // Revoke all the keys we previously calculated
                 if ($revoke_keys) {
                     $ks = new FOX_uKeyRing();
                     try {
                         $keys_ok = $ks->revokeKey($user, $revoke_keys);
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 25, 'text' => "FOX_uKeyRing revokeKey exception", 'data' => array("user" => $user, "revoke_keys" => $revoke_keys), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     if ($keys_ok === false) {
                         // Handles (int)0 return value from revokeKey()
                         $all_ok = false;
                         // if we try to remove a key from a user that doesn't
                     }
                     // have the key, as part of removing a key from
                 }
                 // a large batch of users
             }
             unset($user);
             // Drop the user-group pairs from the groupstore db table
             $args = array(array("col" => "user_id", "op" => "=", "val" => $user_id), array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $rows_changed = $db->runDeleteQuery(self::$struct, $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 26, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // If none of the users are a member of the group we're trying to delete
             // them from, return "0" to indicate that no db rows were changed
             if (!$rows_changed) {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 27, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return 0;
             }
             // If all operations completed successfully, commit the transaction and
             // return the result of the commit.
             // =======================================================
             if ($rows_changed && $all_ok) {
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 28, 'text' => "commitTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Update the cache
                 if ($commit_ok) {
                     $all_ok = true;
                     foreach ($user_id as $user) {
                         // Load, update, writeback
                         $this->cache[$user] = $fox->cache->get("FOX_uGroupMember", $user);
                         $this->cache[$user]["keys"][$group_id] = false;
                         $cache_ok = $fox->cache->set("FOX_uGroupMember", $user, $this->cache[$user]);
                         if (!$cache_ok) {
                             $all_ok = false;
                         }
                     }
                     unset($user);
                     if ($all_ok) {
                         return $rows_changed;
                     } else {
                         return false;
                     }
                 } else {
                     return false;
                 }
             } else {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 29, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return false;
             }
         } else {
             // If we couldn't start a transaction, return false
             return false;
         }
         // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
     // CASE 4: Multiple user_id's, multiple groups
     // =================================================================
     if (is_array($user_id) && is_array($group_id)) {
         // Load all of the groups that each user is currently in, except
         // for the groups we're deleting users from
         $args = array(array("col" => "user_id", "op" => "=", "val" => $user_id), array("col" => "group_id", "op" => "!=", "val" => $group_id));
         $ctrl = array("format" => "array_key_array_grouped", "key_col" => array("user_id", "group_id"));
         try {
             $in_groups = $db->runSelectQuery(self::$struct, $args, $columns = null, $ctrl);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 30, 'text' => "DB select exception", 'data' => array("args" => $args, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         if ($db->beginTransaction()) {
             $all_ok = true;
             try {
                 $gk = new FOX_uGroupKeyRing();
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 31, 'text' => "FOX_uGroupKeyRing constructor exception", 'data' => array("in_groups" => $in_groups), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             foreach ($user_id as $user) {
                 // Get the combined keyring of all the user's other groups
                 try {
                     $keep_keys = $gk->getKeys($in_groups[$user]);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 32, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("user" => $in_groups[$user]), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Get the keyring of the groups we're removing the user from
                 try {
                     $drop_keys = $gk->getKeys($group_id);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 33, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Intersect the $keep_keys and $drop_keys arrays to get
                 // a list of keys we need to revoke from the user
                 if ($keep_keys && $drop_keys) {
                     $revoke_keys = array_diff($drop_keys, $keep_keys);
                 } else {
                     $revoke_keys = $drop_keys;
                 }
                 // Revoke all the keys we previously calculated
                 if ($revoke_keys) {
                     $ks = new FOX_uKeyRing();
                     try {
                         $keys_ok = $ks->revokeKey($user, $revoke_keys);
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 34, 'text' => "FOX_uKeyRing revokeKey exception", 'data' => array("user" => $user, "revoke_keys" => $revoke_keys), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     if ($keys_ok === false) {
                         // Handles (int)0 return value from revokeKey()
                         $all_ok = false;
                         // if we try to remove a key from a user that doesn't
                     }
                     // have the key, as part of removing a key from
                 }
                 // a large batch of users
             }
             unset($user);
             // Drop the user-group pairs from the groupstore db table
             $args = array(array("col" => "user_id", "op" => "=", "val" => $user_id), array("col" => "group_id", "op" => "=", "val" => $group_id));
             try {
                 $rows_changed = $db->runDeleteQuery(self::$struct, $args);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 35, 'text' => "DB delete exception", 'data' => $args, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // If none of the users are a members of any of the groups we're trying to delete
             // them from, return "0" to indicate that no db rows were changed
             if (!$rows_changed) {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 36, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return 0;
             }
             // If all operations completed successfully, commit the transaction and
             // return the result of the commit.
             // =======================================================
             if ($rows_changed && $all_ok) {
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 37, 'text' => "commitTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Update the cache
                 if ($commit_ok) {
                     $all_ok = true;
                     foreach ($user_id as $user) {
                         // Load, update, writeback
                         $this->cache[$user] = $fox->cache->get("FOX_uGroupMember", $user);
                         foreach ($group_id as $group) {
                             $this->cache[$user]["keys"][$group] = false;
                         }
                         $cache_ok = $fox->cache->set("FOX_uGroupMember", $user, $this->cache[$user]);
                         if (!$cache_ok) {
                             $all_ok = false;
                         }
                     }
                     unset($user, $group);
                     if ($all_ok) {
                         return $rows_changed;
                     } else {
                         return false;
                     }
                 } else {
                     return false;
                 }
             } else {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 38, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return false;
             }
         } else {
             // If we couldn't start a transaction, return false
             return false;
         }
         // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     }
 }
 /**
  * Creates or replaces a module's entire policy.
  *
  * @version 1.0
  * @since 1.0
  *
  * @param int $module_id | module_id
  *
  * @param array $data | Array of type_id arrays
  *	    => ARR @param string $type_id | type_id array
  *		=> ARR @param string $branch_id | branch_id array
  *		    => ARR @param int $key_id | key_id array
  *			=> VAL @param bool/int/float/string/array/obj $ctrl_val | control value
  *
  * @return bool | Exception on failure. True on success.
  */
 public function setPolicy($module_id, $data)
 {
     $db = new FOX_db();
     $struct = $this->_struct();
     if (empty($module_id)) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "Empty or incorrect module_id", 'data' => array("module_id" => $module_id, "data" => $data), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => null));
     }
     if (empty($data) || !is_array($data)) {
         throw new FOX_exception(array('numeric' => 2, 'text' => "Empty or malformed data array", 'data' => array("module_id" => $module_id, "data" => $data), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => null));
     }
     // Lock the module_id's cache page
     // ===========================================================
     try {
         self::lockCachePage($module_id);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 3, 'text' => "Error locking cache page", 'data' => array("module_id" => $module_id), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     try {
         $db->beginTransaction();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 4, 'text' => "Couldn't initiate transaction", 'data' => array("module_id" => $module_id, "data" => $data), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // Clear all entries for the module_id from the db
     // ===========================================================
     $args = array(array("col" => "module_id", "op" => "=", "val" => $module_id));
     try {
         $db->runDeleteQuery($struct, $args, $ctrl = null);
     } catch (FOX_exception $child) {
         try {
             $db->rollbackTransaction();
         } catch (FOX_exception $child_2) {
             throw new FOX_exception(array('numeric' => 5, 'text' => "Error while deleting from the database. Error rolling back.", 'data' => array('rollback_exception' => $child_2, "module_id" => $module_id), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
         }
         throw new FOX_exception(array('numeric' => 6, 'text' => "Error while deleting from the database. Successful rollback.", 'data' => array("module_id" => $module_id), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // Flatten the heirarchical $data array into an array of row arrays
     // while calculating the cache flags
     // ================================================================
     $update_cache = array();
     $insert_data = array();
     foreach ($data as $type_id => $branch_ids) {
         $update_cache["type_id"][$type_id] = true;
         foreach ($branch_ids as $branch_id => $key_ids) {
             $update_cache["branch_id"][$type_id][$branch_id] = true;
             foreach ($key_ids as $key_id => $ctrl_val) {
                 $update_cache["keys"][$type_id][$branch_id][$key_id] = $ctrl_val;
                 $insert_data[] = array("module_id" => $module_id, "type_id" => $type_id, "branch_id" => $branch_id, "key_id" => $key_id, "ctrl_val" => $ctrl_val);
             }
             unset($key_id, $ctrl_val);
         }
         unset($branch_id, $key_ids);
     }
     unset($type_id, $branch_ids);
     // Write to db
     // ===========================================================
     try {
         $db->runInsertQueryMulti($struct, $insert_data, $columns = null, $ctrl = null);
     } catch (FOX_exception $child) {
         try {
             $db->rollbackTransaction();
         } catch (FOX_exception $child_2) {
             throw new FOX_exception(array('numeric' => 7, 'text' => "Error while writing to the database. Error rolling back.", 'data' => array('insert_data' => $insert_data, 'rollback_exception' => $child_2), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
         }
         throw new FOX_exception(array('numeric' => 8, 'text' => "Error while writing to the database. Successful rollback.", 'data' => $insert_data, 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     try {
         $db->commitTransaction();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 9, 'text' => "Error commiting transaction to database", 'data' => $insert_data, 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // Overwrite the module_id's cache page, releasing our lock
     // ===========================================================
     try {
         self::writeCachePage(array($module_id => $update_cache));
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 10, 'text' => "Cache set error", 'data' => array("module_id" => $module_id, "update_data" => $update_cache), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // Write the temp class cache to the class cache
     $this->cache[$module_id] = $update_cache;
     return true;
 }
 /**
  * Updates the ranks of all levels within an object type
  *
  * @version 1.0
  * @since 1.0
  *
  * @param int $module_id | id of module that owns the object type
  * @param int $type_id | id of object type that owns the level id's
  * @param array $ranks | array of ranks to apply to the object type's access levels in the form "type_id"=>"rank"
  *
  * @return bool | Exception on failure. False on no change. True on success.
  */
 public function setRanks($module_id, $type_id, $ranks)
 {
     $db = new FOX_db();
     $result_array = array();
     // Check that the $ranks array has no missing ranks
     // ========================================================================
     asort($ranks);
     $rank_check = 1;
     foreach ($ranks as $rank) {
         if ($rank != $rank_check) {
             throw new FOX_exception(array('numeric' => 1, 'text' => "Missing rank key", 'data' => array('module_id' => $module_id, 'type_id' => $type_id, 'ranks' => $ranks), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
         }
         $rank_check++;
     }
     unset($rank, $rank_check);
     // If *all* of the levels for a type_id have been cached, there will be an
     // array containing it's level_id's in $this->cache["type_levels"][$type_id],
     // if it exists, we can use the cached data instead of querying the database
     // ========================================================================
     if (FOX_sUtil::keyExists($this->cache["type_levels"], $type_id)) {
         $levels = $this->cache["type_levels"][$type_id];
         foreach ($levels as $level_id) {
             $result_array[$level_id] = $this->cache["keys"][$level_id];
         }
         unset($levels, $level_id);
     } else {
         try {
             self::loadCache();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 2, 'text' => "Cache read error", 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
         }
         if (FOX_sUtil::keyExists($this->cache["type_levels"], $type_id)) {
             $levels = $this->cache["type_levels"][$type_id];
             foreach ($levels as $level_id) {
                 $result_array[$level_id] = $this->cache["keys"][$level_id];
             }
             unset($levels, $level_id);
         } else {
             $args = array(array("col" => "module_id", "op" => "=", "val" => $module_id), array("col" => "type_id", "op" => "=", "val" => $type_id));
             $ctrl = array("format" => "array_key_array", "key_col" => array("level_id"));
             try {
                 $db_result = $db->runSelectQuery($this->_struct(), $args, $columns = null, $ctrl);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 3, 'text' => "Error reading from database", 'data' => array('args' => $args, 'ctrl' => $ctrl), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
             }
             if ($db_result) {
                 foreach ($db_result as $level_id => $row_data) {
                     $result_array[$level_id] = $row_data;
                 }
                 unset($level_id, $row_data);
             }
         }
     }
     // Check the number of keys in the $ranks array matches the number of
     // levels that the object type has
     // ========================================================================
     $ranks_count = count($ranks);
     $db_count = count($result_array);
     if ($ranks_count > $db_count) {
         throw new FOX_exception(array('numeric' => 4, 'text' => "Rank array has more keys than database", 'data' => array('ranks' => $ranks, 'result_array' => $result_array), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => null));
     } elseif ($ranks_count < $db_count) {
         throw new FOX_exception(array('numeric' => 5, 'text' => "Rank array has less keys than database", 'data' => array('ranks' => $ranks, 'result_array' => $result_array), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => null));
     }
     // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     try {
         $db->beginTransaction();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 6, 'text' => "Couldn't initiate transaction", 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // Lock the cache
     // ===============================
     try {
         $cache_image = self::lockCache();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 7, 'text' => "Cache read error", 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     $rows_changed = 0;
     foreach ($result_array as $level_id => $row_data) {
         if ($ranks[$level_id] != $row_data["rank"]) {
             $row_data["rank"] = $ranks[$level_id];
             $args = array(array("col" => "module_id", "op" => "=", "val" => $module_id), array("col" => "type_id", "op" => "=", "val" => $type_id), array("col" => "level_id", "op" => "=", "val" => $level_id));
             try {
                 $rows_changed += (int) $db->runUpdateQuery($this->_struct(), $row_data, $args, $columns = null);
             } catch (FOX_exception $child) {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child_2) {
                     throw new FOX_exception(array('numeric' => 8, 'text' => "Error writing to database. Rollback failed.", 'data' => array('rollback_exception' => $child_2, 'args' => $args, 'columns' => $columns, 'ctrl' => $ctrl), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
                 }
                 throw new FOX_exception(array('numeric' => 9, 'text' => "Error writing to database. Rollback successful.", 'data' => array('row_data' => $row_data, 'args' => $args), 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
             }
         }
     }
     unset($level_id, $row_data);
     try {
         $db->commitTransaction();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 10, 'text' => "Couldn't commit transaction", 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // @@@@@@ END TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
     // Rebuild the cache image
     // ======================================
     foreach ($result_array as $level_id => $row_data) {
         if ($ranks[$level_id] != $row_data["rank"]) {
             $row_data["rank"] = $ranks[$level_id];
             $cache_image["slug_to_level_id"][$row_data["module_id"]][$row_data["type_id"]][$row_data["level_slug"]] = $level_id;
             $cache_image["keys"][$level_id] = $row_data;
         }
     }
     unset($level_id, $row_data);
     $cache_image["type_levels"][$type_id] = array_keys($ranks);
     // Update the persistent cache, releasing our lock
     // ================================================
     try {
         self::writeCache($cache_image);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 11, 'text' => "Cache write error", 'file' => __FILE__, 'class' => __CLASS__, 'function' => __FUNCTION__, 'line' => __LINE__, 'child' => $child));
     }
     // Overwrite the class cache
     $this->cache = $cache_image;
     return (bool) $rows_changed;
 }