/**
  * Creates a new key
  *
  * @version 1.0
  * @since 1.0
  *
  * @param array $data | one or more arrays, each descrribing a key to create
  *	=> ARR @param int '' | Array index
  *	    => VAL @param string $tree | tree name for key (max 32 chars)
  *	    => VAL @param string $branch | branch name for key (max 32 chars)
  *	    => VAL @param string $name | name for key (max 32 chars)
  *	    => VAL @param string $descr | admin description for key (max 255 chars)
  *
  * @return bool | False on failure. True on success.
  */
 public function createKeyMulti($data)
 {
     global $fox;
     $db = new FOX_db();
     // Make sure that none of the keys to be added already exist
     // =========================================================
     $add_keys = array();
     foreach ($data as $key) {
         $tree = $key["tree"];
         // This is easier to understand than using extract($key)
         $branch = $key["branch"];
         // because it shows the variable names we're using
         $name = $key["name"];
         $descr = $key["descr"];
         $add_keys[$tree][$branch][$name]["descr"] = $descr;
     }
     unset($key, $tree, $branch, $name);
     $add_data = array();
     foreach ($add_keys as $tree_name => $branches) {
         foreach ($branches as $branch_name => $key_arrays) {
             $key_names = array_keys($key_arrays);
             // The foreach() function provides the full "key_name"=>"value"
             // pair in $key_arrays, so we need to use array_keys() to extract
             // the key names
             $args = array(array("col" => "tree", "op" => "=", "val" => $tree_name), array("col" => "branch", "op" => "=", "val" => $branch_name), array("col" => "name", "op" => "=", "val" => $key_names));
             $columns = array("mode" => "include", "col" => array("key_id"));
             $ctrl = array("format" => "col");
             try {
                 $result = $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));
             }
             // If there is no DB entry, we can add the key to our $add_keys array
             if (!$result) {
                 foreach ($key_arrays as $key_name => $vars) {
                     $add_data[] = array("tree" => $tree_name, "branch" => $branch_name, "name" => $key_name, "descr" => $vars["descr"]);
                 }
             } else {
                 throw new FOX_exception(array('numeric' => 1, 'text' => "FOX_uKeyType::createKeyMulti - Attempted to create a key that is already in the database.", 'data' => array("tree" => $tree_name, "branch" => $branch_name, "key" => $key_name), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
         }
     }
     unset($tree_name, $branches, $branch_name, $key_arrays, $key_names, $key_name, $vars);
     // Add the new keys to the database
     // ================================
     try {
         $result = $db->runInsertQueryMulti(self::$struct, $add_data, $columns = null, $ctrl = null);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "DB insert exception", 'data' => array("add_data" => $add_date), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // NOTE: Because we add all keys as a single query, we cannot add their key_id's to the cache
     // because 1) mySQL only returns an insert ID when the table's primary key is a single auto
     // increment column, and 2) it only does it for single insert queries.
     return $result;
 }
 /**
  * Registers one or more "Page Module" <=> "Target" relationships
  *
  * @version 1.0
  * @since 1.0
  *
  * @param array $data | Array of row arrays
  *	=> ARR @param int '' | Individual row array
  *	    => VAL @param string $location | Display location.
  *	    => VAL @param int/string $target | Module target. Must be (int) for pages, (string) for all other locations.
  *	    => VAL @param string $tab_title | Title for the module's tab (not used for pages).
  *	    => VAL @param int $tab_position | Tab position (0-255) (not used for pages).
  *	    => VAL @param int $module_id | Page module id.
  *	    => VAL @param string $php_class | PHP class for the page module.
  *
  * @return int | Exception on failure. Int number of rows changed on success.
  */
 public function addTargetMulti($data)
 {
     $db = new FOX_db();
     $struct = self::_struct();
     $insert_data = array();
     // Process each row
     // ===========================================================
     foreach ($data as $row) {
         if (!is_array($row) || empty($row["location"]) || empty($row["target"]) || empty($row["module_id"]) || empty($row["php_class"])) {
             throw new FOX_exception(array('numeric' => 1, 'text' => "Missing value in data array", 'data' => $row, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
         }
         if ($row["location"] == 'page') {
             if (!is_int($row["target"])) {
                 throw new FOX_exception(array('numeric' => 2, 'text' => "Attempted to use non-int value as a page target", 'data' => $row, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
             $insert_data[] = array("location" => $row["location"], "target" => (int) $row["target"], "module_id" => $row["module_id"], "php_class" => $row["php_class"]);
         } else {
             if (empty($row["tab_title"]) || empty($row["tab_position"])) {
                 throw new FOX_exception(array('numeric' => 3, 'text' => "Non-page targets require tab_title and tab_position to be set", 'data' => $row, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
             if (!is_int($row["tab_position"])) {
                 throw new FOX_exception(array('numeric' => 4, 'text' => "Attempted to use non-int value as tab position", 'data' => $row, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
             $insert_data[] = array("location" => $row["location"], "target" => (string) $row["target"], "tab_title" => $row["tab_title"], "tab_position" => (int) $row["tab_position"], "module_id" => $row["module_id"], "php_class" => $row["php_class"]);
         }
     }
     unset($row);
     // Lock the cache
     // ===========================================================
     try {
         $cache_image = self::lockCache();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 5, 'text' => "Error locking cache", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Update the database
     // ===========================================================
     $columns = null;
     $ctrl = null;
     try {
         $rows_changed = $db->runInsertQueryMulti($struct, $insert_data, $columns, $ctrl);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 6, 'text' => "Error reading from database", 'data' => array('insert_data' => $insert_data, 'columns' => $columns, 'ctrl' => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Rebuild the cache image
     // ===========================================================
     foreach ($insert_data as $row) {
         $row_data = array("module_id" => $row["module_id"], "php_class" => $row["php_class"]);
         if (array_key_exists("tab_title", $row)) {
             $row_data["tab_title"] = $row["tab_title"];
         }
         if (array_key_exists("tab_position", $row)) {
             $row_data["tab_position"] = $row["tab_position"];
         }
         $cache_image["data"][$row["location"]][$row["target"]] = $row_data;
     }
     unset($row);
     // Write the image back to the persistent cache, releasing our lock
     // ===========================================================
     try {
         self::writeCache($cache_image);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 7, 'text' => "Cache write error", 'data' => array('cache_image' => $cache_image), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Update the class cache
     $this->cache = $cache_image;
     return (int) $rows_changed;
 }
 /**
  * Adds one or more keys to one or more groups' keyrings
  *
  * @version 1.0
  * @since 1.0
  *
  * @param int $group_id | id of the group as int. Multiple groups as array of int.
  * @param int/array $key_id | id of the key as int. Multiple keys as array of int.
  * @return bool | False on failure. True on success.
  */
 public function addKey($group_id, $key_id)
 {
     global $fox;
     $db = new FOX_db();
     // Force a load of the entire class cache from the persistent cache
     try {
         self::loadCache();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "loadCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // CASE 1: Single group_id, single key
     // =================================================================
     if (!is_array($group_id) && !is_array($key_id)) {
         // If the group already has the key, return true to indicate no db rows were changed
         if (self::hasKey($group_id, $key_id)) {
             return true;
         } else {
             $data = array("group_id" => $group_id, "key_id" => $key_id);
             try {
                 $result = $db->runInsertQuery(self::$struct, $data, $columns = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 2, 'text' => "Db insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             if ($result) {
                 // Update the cache
                 try {
                     $cache_ok = self::saveCache();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 3, 'text' => "saveCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 if ($cache_ok) {
                     return $result;
                 } else {
                     return false;
                 }
             } else {
                 return $result;
             }
         }
     }
     // CASE 2: Single group_id, multiple keys
     // =================================================================
     if (!is_array($group_id) && is_array($key_id)) {
         $keys_to_add = array();
         // Create an array of "to be added" keys the user doesn't have. Note this algorithm
         // also eliminates duplicate keys, so we don't have to use array_unique() to avoid
         // a failed query if duplicate keys are present in $key_id
         foreach ($key_id as $key) {
             if (!self::hasKey($group_id, $key)) {
                 $keys_to_add[] = $key;
             }
         }
         unset($key);
         // Check that there are actually keys to add, because running an insert query
         // with an empty data array will cause an SQL error
         if (count($keys_to_add) > 0) {
             // Add the new keys to the database
             $data = array();
             foreach ($keys_to_add as $key) {
                 $data[] = array("group_id" => $group_id, "key_id" => $key);
             }
             unset($key);
             try {
                 $result = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 4, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Update the cache
             if ($result) {
                 foreach ($keys_to_add as $key) {
                     $this->cache["keys"][$group_id][$key] = true;
                 }
                 unset($key);
                 try {
                     $cache_ok = self::saveCache();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 5, 'text' => "saveCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 if ($cache_ok) {
                     return $result;
                 } else {
                     return false;
                 }
             } else {
                 return $result;
             }
         } else {
             return true;
         }
     }
     // CASE 3: Multiple group_id's, single key
     // =================================================================
     if (is_array($group_id) && !is_array($key_id)) {
         $groups_to_add = array();
         // Create an array of groups to be given the key that don't have the key yet
         foreach ($group_id as $group) {
             if (!self::hasKey($group, $key_id)) {
                 $groups_to_add[] = $group;
             }
         }
         unset($group);
         // Check that there are actually keys to add, because running an insert query
         // with an empty data array will cause an SQL error
         if (count($groups_to_add) > 0) {
             // Add the new keys to the database
             $data = array();
             foreach ($groups_to_add as $group) {
                 $data[] = array("group_id" => $group, "key_id" => $key_id);
             }
             unset($group);
             try {
                 $result = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 6, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Update the cache
             if ($result) {
                 foreach ($groups_to_add as $group) {
                     $this->cache["keys"][$group][$key_id] = true;
                 }
                 unset($group);
                 try {
                     $cache_ok = self::saveCache();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 7, 'text' => "saveCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 if ($cache_ok) {
                     return $result;
                 } else {
                     return false;
                 }
             } else {
                 return $result;
             }
         } else {
             return true;
         }
     }
     // CASE 4: Multiple group_id's, multiple keys (all groups get same keys)
     // =================================================================
     if (is_array($group_id) && is_array($key_id)) {
         // Create an array of "missing keys" that need to be added. Note this algorithm
         // also eliminates duplicate keys, so we don't have to use array_unique() to avoid
         // a failed query if duplicate keys are present in $key_id
         $keys_to_add = array();
         foreach ($group_id as $group) {
             foreach ($key_id as $key) {
                 if (!self::hasKey($group, $key)) {
                     $keys_to_add[$group][] = $key;
                 }
             }
         }
         unset($group, $key);
         // Check that there are actually keys to add, because running an insert query
         // with an empty data array will cause an SQL error
         if (count($keys_to_add) > 0) {
             // Add the new keys to the database
             $data = array();
             foreach ($keys_to_add as $group => $keys) {
                 foreach ($keys as $key) {
                     $data[] = array("group_id" => $group, "key_id" => $key);
                 }
             }
             unset($group, $key, $keys);
             try {
                 $result = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 8, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Update the cache
             if ($result) {
                 foreach ($keys_to_add as $group => $keys) {
                     foreach ($keys as $key) {
                         $this->cache["keys"][$group][$key] = true;
                     }
                 }
                 unset($group, $key, $keys);
                 try {
                     $cache_ok = self::saveCache();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 1, 'text' => "saveCache exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 if ($cache_ok) {
                     return $result;
                 } else {
                     return false;
                 }
             } else {
                 return $result;
             }
         } else {
             return true;
         }
         // ENDOF: if( count($keys_to_add) > 0 )
     }
     // ENDOF: if( is_array($group_id) && is_array($key_id) )
 }
 /**
  * Adds one or more users to 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 ids as array of int.
  * @return bool | False on failure. True on success.
  */
 public function addToGroup($user_id, $group_id)
 {
     global $fox;
     $db = new FOX_db();
     // CASE 1: Single user_id, single group_id
     // =================================================================
     if (!is_array($user_id) && !is_array($group_id)) {
         // Check if the user is already a member of the group
         if (self::inGroup($user_id, $group_id)) {
             return true;
         }
         // Get the group's keyring
         try {
             $gk = new FOX_uGroupKeyRing();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 1, 'text' => "FOX_uGroupKeyRing constructor exception", 'data' => array("group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         try {
             $group_keyring = $gk->getKeys($group_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 2, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         if ($db->beginTransaction()) {
             // Add the user-group pair to the group members table
             $data = array("user_id" => $user_id, "group_id" => $group_id);
             try {
                 $group_ok = $db->runInsertQuery(self::$struct, $data, $columns = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 3, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Grant the user the group's keyring
             if ($group_keyring) {
                 $ks = new FOX_uKeyRing();
                 try {
                     $keys_ok = $ks->grantKey($user_id, $group_keyring);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 4, 'text' => "FOX_uKeyRing exception", 'data' => array("user_id" => $user_id, "group_keyring" => $group_keyring), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
             } else {
                 // Handle groups with no keys in their keyring
                 $keys_ok = true;
             }
             // If all operations were successful, commit the transaction
             if ($group_ok && $keys_ok) {
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 5, '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] = true;
                     $cache_ok = $fox->cache->set("FOX_uGroupMember", $user_id, $this->cache[$user_id]);
                     return $cache_ok;
                 } else {
                     return false;
                 }
             } else {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 6, '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)) {
         // Load all of the user's "to be added" groups into the cache
         try {
             self::load($user_id, $group_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 7, 'text' => "FOX_uGroupMember load exception", 'data' => array("user_id" => $user_id, "group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         $groups_to_add = array();
         // Create an array of all the groups the user needs to be added to
         foreach ($group_id as $group) {
             if (!$this->cache[$user_id]["keys"][$group]) {
                 $groups_to_add[] = $group;
             }
         }
         unset($group, $in_cache);
         // If the user is already a member of all the requested groups, quit
         if (empty($groups_to_add)) {
             return true;
         }
         // Get combined keyring of groups to add the user to
         try {
             $gk = new FOX_uGroupKeyRing();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 8, 'text' => "FOX_uGroupKeyRing constructor exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         try {
             $keyring = $gk->getKeys($groups_to_add);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 9, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("groups_to_add" => $groups_to_add), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         try {
             $transaction_started = $db->beginTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 10, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($transaction_started) {
             // Add the user-group pairs to the group members table
             $data = array();
             foreach ($groups_to_add as $group) {
                 $data[] = array("user_id" => $user_id, "group_id" => $group);
             }
             unset($group);
             try {
                 $group_ok = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 11, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Grant the user the groups' combined keyrings
             if ($keyring) {
                 $ks = new FOX_uKeyRing();
                 try {
                     $keys_ok = $ks->grantKey($user_id, $keyring);
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 12, 'text' => "FOX_uKeyRing grantKey exception", 'data' => array("user_id" => $user_id, "group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
             } else {
                 // Handle none of the groups having any keys on their keyring
                 $keys_ok = true;
             }
             // If all operations were successful, commit the transaction
             if ($group_ok && $keys_ok) {
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 13, '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 ($groups_to_add as $group) {
                         $this->cache[$user_id]["keys"][$group] = true;
                     }
                     unset($group);
                     $cache_ok = $fox->cache->set("FOX_uGroupMember", $user_id, $this->cache[$user_id]);
                     return $cache_ok;
                 } else {
                     return false;
                 }
             } else {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 14, '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 user's to be added to the group into the cache
         try {
             self::load($user_id, $group_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 15, 'text' => "FOX_uGroupMember load exception", 'data' => array("user_id" => $user_id, "group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         $users_to_add = array();
         // Create an array of "to be added" users that aren't members of the group yet
         foreach ($user_id as $user) {
             if (!$this->cache[$user]["keys"][$group_id]) {
                 $users_to_add[] = $user;
             }
         }
         unset($user, $in_cache);
         // If there are no users that are not already in the group, quit
         if (empty($users_to_add)) {
             return true;
         }
         // Get the group's keyring
         try {
             $gk = new FOX_uGroupKeyRing();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 16, 'text' => "FOX_uGroupKeyRing constructor exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         try {
             $group_keyring = $gk->getKeys($group_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 17, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         try {
             $transaction_started = $db->beginTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 18, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($transaction_started) {
             // Add the user-group pairs to the groupstore db table
             $data = array();
             foreach ($users_to_add as $user) {
                 $data[] = array("user_id" => $user, "group_id" => $group_id);
             }
             unset($user);
             try {
                 $group_ok = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 19, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Grant the group's keyring to each user
             if ($group_keyring) {
                 $ks = new FOX_uKeyRing();
                 $keys_ok = true;
                 foreach ($users_to_add as $user) {
                     try {
                         $grant_ok = $ks->grantKey($user, $group_keyring);
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 20, 'text' => "FOX_uKeyRing grantKey exception", 'data' => array("user" => $user, "group_key_ring" => $group_keyring), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     if (!$grant_ok) {
                         $keys_ok = false;
                     }
                 }
                 unset($user);
             } else {
                 // Handle the group having no keys on its keyring
                 $keys_ok = true;
             }
             // If all operations were successful, commit the transaction
             if ($group_ok && $keys_ok) {
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 21, 'text' => "commitTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 // Update the cache
                 if ($commit_ok) {
                     $all_ok = true;
                     foreach ($users_to_add as $user) {
                         // Load, update, writeback
                         $this->cache[$user] = $fox->cache->get("FOX_uGroupMember", $user);
                         $this->cache[$user]["keys"][$group_id] = true;
                         $cache_ok = $fox->cache->set("FOX_uGroupMember", $user, $this->cache[$user]);
                         if (!$cache_ok) {
                             $all_ok = false;
                         }
                     }
                     unset($user);
                     return $all_ok;
                 } else {
                     return false;
                 }
             } else {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 22, '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 user-group pairs to be added into the cache
         try {
             self::load($user_id, $group_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 23, 'text' => "FOX_uGroupMember load exception", 'data' => array("user_id" => $user_id, "group_id" => $group_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // Create an array of user-group pairs that need to be added
         $groups_to_add = array();
         foreach ($user_id as $user) {
             foreach ($group_id as $group) {
                 if (!$this->cache[$user]["keys"][$group]) {
                     $groups_to_add[$user][] = $group;
                 }
             }
         }
         unset($user, $group);
         // @@@@@@ BEGIN TRANSACTION @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
         try {
             $transaction_started = $db->beginTransaction();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 24, 'text' => "beginTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($transaction_started) {
             $all_ok = true;
             try {
                 $gk = new FOX_uGroupKeyRing();
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 25, 'text' => "FOX_uGroupKeyRing constructor exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             foreach ($user_id as $user) {
                 if ($groups_to_add[$user]) {
                     // Add the user-group pairs to the groupstore db table
                     $data = array();
                     $add_group_ids = array();
                     foreach ($groups_to_add[$user] as $group) {
                         $data[] = array("user_id" => $user, "group_id" => $group);
                         $add_group_ids[] = $group;
                     }
                     unset($group);
                     try {
                         $groups_ok = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 26, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     // Get the groups' combined keyring
                     try {
                         $keyring = $gk->getKeys($add_group_ids);
                     } catch (FOX_exception $child) {
                         throw new FOX_exception(array('numeric' => 27, 'text' => "FOX_uGroupKeyRing getKeys exception", 'data' => array("add_group_ids" => $add_group_ids), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                     }
                     // Grant the user the groups' combined keyrings
                     if ($keyring) {
                         $ks = new FOX_uKeyRing();
                         try {
                             $keys_ok = $ks->grantKey($user, $keyring);
                         } catch (FOX_exception $child) {
                             throw new FOX_exception(array('numeric' => 28, 'text' => "FOX_uketRing grantKey exception", 'data' => array("user" => $user, "keyring" => $keyring), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                         }
                     } else {
                         // Handle none of the groups having any keys on their keyring
                         $keys_ok = true;
                     }
                     if ($groups_ok && $keys_ok) {
                         $user_count++;
                     } else {
                         $all_ok = false;
                     }
                 }
             }
             unset($user);
             // If all operations completed successfully, commit the transaction
             if ($all_ok && $user_count >= 1) {
                 try {
                     $commit_ok = $db->commitTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 29, '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] = true;
                         }
                         $cache_ok = $fox->cache->set("FOX_uGroupMember", $user, $this->cache[$user]);
                         if (!$cache_ok) {
                             $all_ok = false;
                         }
                     }
                     unset($user, $group);
                     return $all_ok;
                 } else {
                     return false;
                 }
             } elseif ($all_ok && $user_count < 1) {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 30, 'text' => "rollbackTransaction exception", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
                 }
                 return true;
             } else {
                 try {
                     $db->rollbackTransaction();
                 } catch (FOX_exception $child) {
                     throw new FOX_exception(array('numeric' => 31, 'text' => "beginTransaction 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;
 }
 /**
  * Creates a new key, adding it to the database and cache.
  *
  * @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 $key_id | Single key_id as int. Multiple key_id's as array of ints.
  * @return bool | False on failure. True on success but no db rows changed. Int number of rows changed on success.
  */
 public function grantKey($user_id, $key_id)
 {
     global $fox;
     $db = new FOX_db();
     // CASE 1: Single user_id, single key
     // =================================================================
     if (!is_array($user_id) && !is_array($key_id)) {
         // If the user already has the key, return true to indicate no db rows were changed
         if (self::hasKey($user_id, $key_id)) {
             return true;
         } else {
             $data = array("user_id" => $user_id, "key_id" => $key_id);
             try {
                 $rows_changed = $db->runInsertQuery(self::$struct, $data, $columns = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 1, 'text' => "DB insert exception", 'data' => array("data" => $data), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             if ($rows_changed) {
                 // Load the user's keyring into the class cache from the persistent cache
                 $this->cache[$user_id] = $fox->cache->get("FOX_uKeyRing", $user_id);
                 // Add the new key
                 $this->cache[$user_id]["keys"][$key_id] = true;
                 // Update the persistent cache
                 $cache_ok = $fox->cache->set("FOX_uKeyRing", $user_id, $this->cache[$user_id]);
                 if ($cache_ok) {
                     return $rows_changed;
                 } else {
                     return false;
                 }
             } else {
                 return $rows_changed;
             }
         }
     }
     // CASE 2: Single user_id, multiple keys
     // =================================================================
     if (!is_array($user_id) && is_array($key_id)) {
         // Load the user's entire keyring into the cache (if not cached already)
         try {
             self::getKeys($user_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 2, 'text' => "FOX_uKeyRing getKeys exception", 'data' => array("user_id" => $user_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // Create an array of "to be added" keys the user doesn't have.
         $keys_to_add = array();
         foreach ($key_id as $key) {
             if (!$this->cache[$user_id]["keys"][$key]) {
                 // This test condition gets both missing
                 $keys_to_add[] = $key;
                 // keys (null) and negative keys (false)
             }
         }
         unset($key);
         if (count($keys_to_add) > 0) {
             // Add the new keys to the database
             $data = array();
             foreach ($keys_to_add as $key) {
                 $data[] = array("user_id" => $user_id, "key_id" => $key);
             }
             unset($key);
             try {
                 $rows_changed = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 3, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Update the cache
             if ($rows_changed) {
                 // Load the user's keyring into the class cache from the persistent cache
                 $this->cache[$user_id] = $fox->cache->get("FOX_uKeyRing", $user_id);
                 // Add the new keys to the class cache
                 foreach ($keys_to_add as $key) {
                     $this->cache[$user_id]["keys"][$key] = true;
                 }
                 unset($key);
                 // Update the persistent cache from the class cache
                 $cache_ok = $fox->cache->set("FOX_uKeyRing", $user_id, $this->cache[$user_id]);
                 if ($cache_ok) {
                     return $rows_changed;
                 } else {
                     return false;
                 }
             } else {
                 return $rows_changed;
             }
         } else {
             return true;
         }
         // ENDOF: if( count($keys_to_add) > 0 )
     }
     // CASE 3: Multiple user_id's, single key
     // =================================================================
     if (is_array($user_id) && !is_array($key_id)) {
         $users_to_add = array();
         // Create an array of "to be added" users that don't have the key
         foreach ($user_id as $user) {
             try {
                 $has_key = self::hasKey($user, $key_id);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 4, 'text' => "FOX_uKeyRing hasKey exception", 'data' => array("user" => $user, "key_id" => $key_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             if (!$has_key) {
                 $users_to_add[] = $user;
             }
         }
         unset($user);
         if (count($users_to_add) > 0) {
             // Add the new keys to the database
             $data = array();
             foreach ($users_to_add as $user) {
                 $data[] = array("user_id" => $user, "key_id" => $key_id);
             }
             unset($user);
             try {
                 $rows_changed = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 5, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Update the cache
             if ($rows_changed) {
                 $all_ok = true;
                 foreach ($users_to_add as $user) {
                     // Load the user's keyring into the class cache from the persistent cache
                     $this->cache[$user] = $fox->cache->get("FOX_uKeyRing", $user);
                     // Add the new key to the class cache
                     $this->cache[$user]["keys"][$key_id] = true;
                     // Update the persistent cache from the class cache
                     $cache_ok = $fox->cache->set("FOX_uKeyRing", $user, $this->cache[$user]);
                     if (!$cache_ok) {
                         $all_ok = false;
                     }
                 }
                 unset($user);
                 if ($all_ok) {
                     return $rows_changed;
                 } else {
                     return false;
                 }
             } else {
                 return $rows_changed;
             }
         } else {
             return true;
         }
         // ENDOF: if( count($users_to_add) > 0 )
     }
     // CASE 4: Multiple user_id's, multiple keys (all users get same keys)
     // =================================================================
     if (is_array($user_id) && is_array($key_id)) {
         // Load each user's full keyring into the cache (if not cached already)
         try {
             self::getKeysMulti($user_id);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 6, 'text' => "FOX_uKeyRing getKeys exception", 'data' => array("user_id" => $user_id), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         // Create an array of keys that need to be added to each user_id
         $keys_to_add = array();
         foreach ($user_id as $user) {
             foreach ($key_id as $key) {
                 if (!$this->cache[$user]["keys"][$key]) {
                     // This test condition gets both missing
                     $keys_to_add[$user][] = $key;
                     // keys (null) and negative keys (false)
                 }
             }
             unset($key);
         }
         unset($user);
         // Add the new keys to the database
         if (count($keys_to_add) > 0) {
             $data = array();
             foreach ($keys_to_add as $user => $keys) {
                 foreach ($keys as $key) {
                     $data[] = array("user_id" => $user, "key_id" => $key);
                 }
                 unset($key);
             }
             unset($user, $keys);
             try {
                 $rows_changed = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
             } catch (FOX_exception $child) {
                 throw new FOX_exception(array('numeric' => 7, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
             }
             // Update the cache
             if ($rows_changed) {
                 $all_ok = true;
                 foreach ($keys_to_add as $user => $keys) {
                     // Load the user's keyring into the class cache from the persistent cache
                     $this->cache[$user] = $fox->cache->get("FOX_uKeyRing", $user);
                     // Add the new keys to the class cache
                     foreach ($keys as $key) {
                         $this->cache[$user]["keys"][$key] = true;
                     }
                     unset($key);
                     // Update the persistent cache from the class cache
                     $cache_ok = $fox->cache->set("FOX_uKeyRing", $user, $this->cache[$user]);
                     if (!$cache_ok) {
                         $all_ok = false;
                     }
                 }
                 unset($user, $keys);
                 if ($all_ok) {
                     return $rows_changed;
                 } else {
                     return false;
                 }
             } else {
                 return $rows_changed;
             }
         } else {
             return true;
         }
         // ENDOF: if( count($keys_to_add) > 0 )
     }
 }
 /**
  * Registers ownership of one or more slugs to a FoxFire page module
  *
  * @version 1.0
  * @since 1.0
  *
  * @param array $data | Array of row arrays
  *	=> ARR @param int '' | Individual row array
  *	    => VAL @param string $module_type | module type
  *	    => VAL @param int $module_id | id of the module
  *	    => VAL @param string $php_class | PHP class for the module
  *	    => VAL @param int $module_slug | module module_slug
  *
  * @return bool | Exception on failure. True on success.
  */
 public function addSlugMulti($data)
 {
     $db = new FOX_db();
     $struct = self::_struct();
     $columns = null;
     $ctrl = null;
     try {
         $db->runInsertQueryMulti($struct, $data, $columns, $ctrl);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "Error writing to database", 'data' => array('data' => $data, 'columns' => $columns, 'ctrl' => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Fetch the persistent cache image
     // ===================================================
     try {
         $cache_image = self::readCache();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 2, 'text' => "Cache read error", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Rebuild the cache image
     // ===================================================
     foreach ($data as $row) {
         $row_data = array("module_id" => $row["module_id"], "php_class" => $row["php_class"]);
         $cache_image["data"][$row["module_type"]][$row["module_slug"]] = $row_data;
     }
     unset($row, $row_data);
     // Update the persistent cache
     // ===================================================
     try {
         self::writeCache($cache_image);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 3, 'text' => "Cache write error", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Update the class cache
     $this->cache = $cache_image;
     return true;
 }
 /**
  * Creates a new user class
  *
  * @version 1.0
  * @since 1.0
  *
  * @param array $data |
  *	=> VAL @param string $name | Name of this user class
  *	=> VAL @param int $is_default | Default class users are placed into when signing-up, if true.
  *	=> VAL @param int $transfer_to | Class users are placed in when moved out of this class by the system
  *	=> VAL @param int $tag_who | 0 = off, 1 = friends, 2 = all users
  *	=> VAL @param int $in_what | 0 = off, 1 = in own items, 2 = in friends items, 3 = in friends^2 items, 4 = in all items
  *	=> VAL @param int $tag_self | 0 = off, 1 = in own items, 2 = in friends items, 3 = in friends^2 items, 4 = in all items
  *	=> VAL @param int $can_comment | 0 = off, 1 = own items, 2 = friends items, 3 = friends^2 items, 4 = all items
  *	=> VAL @param int $can_view | 0 = default, 1 = override friends, 2 = override private
  *	=> VAL @param int $ghost | (make user invisible) 0 = off, 1 = from public, 2 = from friends^2, 3 = from friends
  * @return bool | False on failure. True on success.
  */
 public function addClass($data)
 {
     // Trap missing *required* fields. empty() returns true for unset, null, or zero
     // variables, isset returns true for unset or null variables (but not zero).
     if (empty($data["name"]) || empty($data["is_default"]) || empty($data["transfer_to"]) || isset($data["tag_who"]) || isset($data["in_what"]) || isset($data["tag_self"]) || isset($data["can_comment"]) || isset($data["can_view"]) || isset($data["ghost"])) {
         echo "\n Missing required parameter in FOX_userClass::addClass\n";
         echo "Data array was: \n";
         var_dump($data);
         die;
     }
     $data = array($data);
     // Must wrap our data in an array because
     // runInsertQueryMulti can handle multiple inserts at once
     $db = new FOX_db();
     try {
         $this->id = $db->runInsertQueryMulti(self::$struct, $data, $columns = null, $ctrl = null);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "DB insert exception", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     return $this->id;
 }