/**
  * Edits fields for an existing key, given the key's key_id
  *
  * @version 1.0
  * @since 1.0
  *
  * @param array $data | one or more arrays, each descrribing a key to create
  *	=> VAL @param int $key_id | key_id for the key to edit
  *	=> 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 editKey($data)
 {
     global $fox;
     $db = new FOX_db();
     // Get the column values for the current key
     // =========================================
     // Trap missing $key_id
     if (!$data["key_id"]) {
         return false;
     }
     $columns = array("mode" => "exclude", "col" => array("key_id"));
     $ctrl = array("format" => "row");
     try {
         $old = $db->runSelectQueryCol(self::$struct, "key_id", "=", $data["key_id"], $columns, $ctrl);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "DB select exception", 'data' => array("col" => "key_id", "op" => "=", "val" => $data["key_id"], "columns" => $columns, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // If the key's $tree, $branch, or $name fields are being modified, check that a
     // key with the new combination does not already exist in the database
     // ===================================================================================
     $args = array();
     // Tree
     // ==========
     if ($data["tree"] && $old["tree"] != $data["tree"]) {
         $args[] = array("col" => "tree", "op" => "=", "val" => $data["tree"]);
         $dupe_check_required = true;
     } else {
         $args[] = array("col" => "tree", "op" => "=", "val" => $old["tree"]);
     }
     // Branch
     // ==========
     if ($data["branch"] && $old["branch"] != $data["branch"]) {
         $args[] = array("col" => "branch", "op" => "=", "val" => $data["branch"]);
         $dupe_check_required = true;
     } else {
         $args[] = array("col" => "branch", "op" => "=", "val" => $old["branch"]);
     }
     // Name
     // ==========
     if ($data["name"] && $old["name"] != $data["name"]) {
         $args[] = array("col" => "name", "op" => "=", "val" => $data["name"]);
         $dupe_check_required = true;
     } else {
         $args[] = array("col" => "name", "op" => "=", "val" => $old["name"]);
     }
     if ($dupe_check_required) {
         $ctrl = array("count" => true, "format" => "var");
         try {
             $key_exists = $db->runSelectQuery(self::$struct, $args, $columns = null, $ctrl);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 2, 'text' => "DB select exception", 'data' => array("args" => $args, "ctrl" => $ctrl), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
     }
     // If possible, update the key in the database
     // ==========================================================
     if ($key_exists) {
         // Changes to the "Tree", "Branch" or "Name" fields would create a
         // collision with a key that already exists in the database.
         throw new FOX_exception(array('numeric' => 3, 'text' => "FOX_uKeyType::editKey - Attempted to rename a key to a value that would create a collission with an existing key.", 'data' => array("tree" => $data['tree'], "branch" => $data['branch'], "key" => $data['name']), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
     } else {
         // Add the key to the db and persistent cache
         $args = array(array("col" => "key_id", "op" => "=", "val" => $data["key_id"]));
         $columns = array("mode" => "exclude", "col" => array("key_id"));
         try {
             $result = $db->runUpdateQuery(self::$struct, $data, $args, $columns);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 4, 'text' => "DB update exception", 'data' => array("data" => $data, "args" => $args, "columns" => $columns), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
         if ($result) {
             // 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 old class cache entry
             unset($this->cache["keys"][$old["tree"]][$old["branch"]][$old["name"]]);
             // Set the new class cache entry
             $this->cache["keys"][$data["tree"]][$data["branch"]][$data["name"]] = $data["key_id"];
             // 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;
         }
     }
 }
 /**
  * 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;
 }
 /**
  * Edits a a module's table entry
  *
  * @version 1.0
  * @since 1.0
  *
  * @param array $data |
  *	=> 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.
  *
  * @return int | Exception on failure. Int number of rows changed on success.
  */
 public function edit($data)
 {
     $db = new FOX_db();
     $struct = self::_struct();
     // Trap bad input
     // =============================================================
     if (!$data["module_id"]) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "Must supply module_id", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
     }
     if (!$data["target"] && !$data["tab_title"] && !$data["tab_position"]) {
         throw new FOX_exception(array('numeric' => 2, 'text' => "Must supply at least 1 field to change", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
     }
     // Trap nonexistent module_id
     // =============================================================
     try {
         $db_record = self::getID($data["module_id"]);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 3, 'text' => "Error in self::getID()", 'data' => $data["module_id"], 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     if (!$db_record) {
         throw new FOX_exception(array('numeric' => 4, 'text' => "Supplied module_id doesn't have a database record", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
     }
     $update = array();
     // CASE 1: Changing the location
     // =============================================================
     if ($data["location"] && $data["location"] != $db_record["location"]) {
         // CASE 1A: Changing from slug to page
         // ------------------------------------------
         if ($data["location"] == "page") {
             if (!$data["target"]) {
                 throw new FOX_exception(array('numeric' => 5, 'text' => "Target must be set when changing location from a slug to a page", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
             if (!is_int($data["target"])) {
                 throw new FOX_exception(array('numeric' => 6, 'text' => "Attempted to use non-int value as a page target", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
             $update["location"] = $data["location"];
             $update["target"] = (int) $data["target"];
             $update["tab_title"] = null;
             $update["tab_position"] = null;
         } elseif ($db_record["location"] == "page") {
             if (!$data["target"]) {
                 throw new FOX_exception(array('numeric' => 7, 'text' => "Target must be set when changing location from a page to a slug", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
             if (!$data["tab_title"] || !$data["tab_position"]) {
                 throw new FOX_exception(array('numeric' => 8, 'text' => "The tab_title and tab_position fields must be set when changing location from a page to a slug", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
             if (!is_int($data["tab_position"])) {
                 throw new FOX_exception(array('numeric' => 9, 'text' => "Attempted to use non-int value as a tab position", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
             }
             $update["location"] = $data["location"];
             $update["target"] = (string) $data["target"];
             $update["tab_title"] = (string) $data["tab_title"];
             $update["tab_position"] = (int) $data["tab_position"];
         } else {
             $update["location"] = $data["location"];
             if ($data["target"]) {
                 $update["target"] = (string) $data["target"];
             }
             if ($data["tab_title"]) {
                 $update["tab_title"] = (string) $data["tab_title"];
             }
             if ($data["tab_position"]) {
                 if (!is_int($data["tab_position"])) {
                     throw new FOX_exception(array('numeric' => 10, 'text' => "Attempted to use non-int value as a tab position", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
                 }
                 $update["tab_position"] = (int) $data["tab_position"];
             }
         }
     } else {
         // CASE 2A: Editing a page
         // ------------------------------------------
         if ($db_record["location"] == "page") {
             if ($data["target"]) {
                 if (!is_int($data["target"])) {
                     throw new FOX_exception(array('numeric' => 11, 'text' => "Attempted to use non-int value as a page target", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
                 }
                 $update["target"] = (int) $data["target"];
             }
         } else {
             if ($data["target"]) {
                 $update["target"] = (string) $data["target"];
             }
             if ($data["tab_title"]) {
                 $update["tab_title"] = (string) $data["tab_title"];
             }
             if ($data["tab_position"]) {
                 if (!is_int($data["tab_position"])) {
                     throw new FOX_exception(array('numeric' => 12, 'text' => "Attempted to use non-int value as a tab position", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
                 }
                 $update["tab_position"] = (int) $data["tab_position"];
             }
         }
     }
     // Lock the cache
     // ===========================================================
     try {
         $cache_image = self::lockCache();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 13, 'text' => "Error locking cache", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Update the database
     // ===========================================================
     $args = array(array("col" => "module_id", "op" => "=", "val" => $data["module_id"]));
     $columns = array("mode" => "exclude", "col" => array("module_id"));
     try {
         $rows_changed = $db->runUpdateQuery($struct, $update, $args, $columns);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 14, 'text' => "Error writing to database", 'data' => array('update' => $update, 'args' => $args, 'columns' => $columns), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Update the persistent cache
     // ===========================================================
     if (!$rows_changed) {
         // If no rows were changed, we can just write-back the
         // cache image to release our lock
         try {
             self::writeCache($cache_image);
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 15, 'text' => "Error writing to cache", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
     } else {
         // If rows were changed, we have to flush the entire cache
         try {
             self::flushCache();
         } catch (FOX_exception $child) {
             throw new FOX_exception(array('numeric' => 16, 'text' => "Error flushing cache", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
         }
     }
     return (int) $rows_changed;
 }
 /**
  * Edits a module_slug table entry
  *
  * @version 1.0
  * @since 1.0
  *
  * @param array $data |
  *	=> 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 page module
  *	=> VAL @param int $module_slug | module module_slug
  *
  * @return int | Exception on failure. Int number of rows changed on success.
  */
 public function edit($data)
 {
     $db = new FOX_db();
     $struct = self::_struct();
     // Trap bad input
     // =============================================================
     if (!$data["module_type"] || !$data["module_id"]) {
         throw new FOX_exception(array('numeric' => 1, 'text' => "Must supply module_type and module_id", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
     }
     if (!$data["php_class"] && !$data["module_slug"]) {
         throw new FOX_exception(array('numeric' => 2, 'text' => "Must specify either php_class or module_slug", 'data' => $data, 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => null));
     }
     // Lock the cache
     // ===========================================================
     try {
         self::lockCache();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 3, 'text' => "Error locking cache", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Update the database
     // ===========================================================
     $update = array();
     if ($data["module_slug"]) {
         $update["module_slug"] = $data["module_slug"];
     }
     if ($data["php_class"]) {
         $update["php_class"] = $data["php_class"];
     }
     $args = array(array("col" => "module_type", "op" => "=", "val" => $data["module_type"]), array("col" => "module_id", "op" => "=", "val" => $data["module_id"]));
     $columns = array("mode" => "exclude", "col" => array("module_type", "module_id"));
     try {
         $rows_changed = $db->runUpdateQuery($struct, $update, $args, $columns);
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 4, 'text' => "Error updating database", 'data' => array('update' => $update, 'args' => $args, 'columns' => $columns), 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     // Flush the cache
     // =============================================================
     // NOTE: this is a case where it's not practical to rebuild the cache. We'd have to run an
     // additional query to fetch the old module_slug and php_class values to clear them from the cache
     try {
         self::flushCache();
     } catch (FOX_exception $child) {
         throw new FOX_exception(array('numeric' => 5, 'text' => "Error flushing cache", 'file' => __FILE__, 'line' => __LINE__, 'method' => __METHOD__, 'child' => $child));
     }
     return (int) $rows_changed;
 }