diff options
author | Bharat Mediratta <bharat@menalto.com> | 2008-12-01 08:50:00 +0000 |
---|---|---|
committer | Bharat Mediratta <bharat@menalto.com> | 2008-12-01 08:50:00 +0000 |
commit | 91c4bda1ec6640abb8b1a585e1fd1f8955d53fd1 (patch) | |
tree | 42f8f79c6d356a04d0e8365a0921d7257f12c64d | |
parent | ab0fcb7453db7d93c9dc1dfd38e6d6f84a5b16b5 (diff) |
Prototype access control model. There's much left to do, but it's a
working implementation.
-rw-r--r-- | core/controllers/items.php | 3 | ||||
-rw-r--r-- | core/controllers/welcome.php | 38 | ||||
-rw-r--r-- | core/helpers/access.php | 384 | ||||
-rw-r--r-- | core/helpers/core_event.php | 45 | ||||
-rw-r--r-- | core/helpers/core_installer.php | 29 | ||||
-rw-r--r-- | core/models/access_cache.php | 21 | ||||
-rw-r--r-- | core/models/access_intent.php | 21 | ||||
-rw-r--r-- | core/models/permission.php (renamed from core/models/access.php) | 2 | ||||
-rw-r--r-- | core/views/welcome.html.php | 18 | ||||
-rw-r--r-- | modules/user/helpers/group.php | 8 | ||||
-rw-r--r-- | modules/user/helpers/user_installer.php | 13 |
11 files changed, 550 insertions, 32 deletions
diff --git a/core/controllers/items.php b/core/controllers/items.php index 26b55492..6cf27fbf 100644 --- a/core/controllers/items.php +++ b/core/controllers/items.php @@ -99,11 +99,10 @@ class Items_Controller extends REST_Controller { // 1) Add security checks $parent = $item->parent(); if ($parent->id) { + module::event("{$item->type}_before_delete", $item); $item->delete(); } - module::event("{$item->type}_deleted", $item); - url::redirect("{$parent->type}s/{$parent->id}"); } diff --git a/core/controllers/welcome.php b/core/controllers/welcome.php index c29a5aaf..cabaf0a9 100644 --- a/core/controllers/welcome.php +++ b/core/controllers/welcome.php @@ -71,16 +71,33 @@ class Welcome_Controller extends Template_Controller { } function uninstall($module_name) { + $clean = true; if ($module_name == "core") { // We have to uninstall all other modules first, else their tables, etc don't // get cleaned up. - foreach (ORM::factory("module")->find_all() as $module) { - if ($module->name != "core" && $module->version) { - call_user_func(array("{$module->name}_installer", "uninstall")); + try { + foreach (ORM::factory("module")->find_all() as $module) { + if ($module->name != "core" && $module->version) { + try { + call_user_func(array("{$module->name}_installer", "uninstall")); + } catch (Exception $e) { + $clean = false; + } + } } - } + } catch (Exception $e) { } } call_user_func(array("{$module_name}_installer", "uninstall")); + + $clean = false; + if (!$clean) { + // Since we're in a state of flux, it's possible that other stuff went wrong with the + // uninstall, so back off and nuke things from orbit. + $db = Database::instance(); + foreach ($db->list_tables() as $table) { + $db->query("DROP TABLE `$table`"); + } + } url::redirect("welcome"); } @@ -442,7 +459,7 @@ class Welcome_Controller extends Template_Controller { url::redirect("welcome"); } - public function _load_album_tree() { + private function _load_album_tree() { $tree = array(); foreach (ORM::factory("item")->where("type", "album")->find_all() as $album) { if ($album->parent_id) { @@ -451,7 +468,16 @@ class Welcome_Controller extends Template_Controller { $tree[$album->id]->album = $album; $tree[$album->id]->children = array(); } - return $tree; } + + public function add_perm($group_id, $perm, $item_id) { + access::allow($group_id, $perm, $item_id); + url::redirect("welcome"); + } + + public function deny_perm($group_id, $perm, $item_id) { + access::deny($group_id, $perm, $item_id); + url::redirect("welcome"); + } } diff --git a/core/helpers/access.php b/core/helpers/access.php new file mode 100644 index 00000000..9ca54779 --- /dev/null +++ b/core/helpers/access.php @@ -0,0 +1,384 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * API for Gallery Access control. + * + * Permissions are hierarchical, and apply only to groups and albums. They cascade down from the + * top of the Gallery to the bottom, so if you set a permission in the root album, that permission + * applies for any sub-album unless the sub-album overrides it. Likewise, any permission applied + * to an album applies to any photos inside the album. Overrides can be applied at any level of + * the hierarchy for any permission other than View permissions. + * + * View permissions are an exceptional case. In the case of viewability, we want to ensure that + * if an album's parent is inaccessible, then this album must be inaccessible also. So while view + * permissions cascade downwards and you're free to set the ALLOW permission on any album, that + * ALLOW permission will be ignored unless all that album's parents are marked ALLOW also. + * + * Implementatation Notes: + * + * Notes refer to this example album hierarchy: + * A1 + * / \ + * A2 A3 + * / \ + * A4 A5 + * + * o We have the concept of "intents". A user can specify that he intends for A3 to be + * inaccessible (ie: a DENY on the "view" permission to the EVERYBODY group). Once A3 is + * inaccessible, A5 can never be displayed to that group. If A1 is made inaccessible, then the + * entire tree is hidden. If subsequently A1 is made accessible, then the whole tree is + * available again *except* A3 and below since the user's preference for A3 is maintained. + * + * o Intents are specified as <group_id, perm, item_id> tuples. It would be inefficient to check + * these tuples every time we want to do a lookup, so we use these intents to create an entire + * table of permissions for easy lookup in the Access_Cache_Model. There's a 1:1 mapping + * between Item_Model and Access_Cache_Model entries. + * + * o For efficiency, we create columns in Access_Intent_Model and Access_Cache_Model for each of + * the possible Group_Model and Permission_Model combinations. This may lead to performance + * issues for very large Gallery installs, but for small to medium sized ones (5-10 groups, 5-10 + * permissions) it's especially efficient because there's a single field value for each + * group/permission/item combination. + * + * o If at any time the Access_Cache_Model becomes invalid, we can rebuild the entire table from + * the Access_Intent_Model + * + * TODO + * o In the near future, we'll be moving the "view" columns out of Access_Intent_Model and + * directly into Item_Model. By doing this, we'll be able to find viewable items (the most + * common permission access) without doing table joins. + */ +class access_Core { + const DENY = 0; + const ALLOW = 1; + const UNKNOWN = 2; + + /** + * Can this group have this permission on this item? + * + * @param integer $group_id + * @param string $perm_name + * @param integer $item_id + * @return boolean + */ + public static function can($group_id, $perm_name, $item_id) { + $access = ORM::factory("access_cache")->where("item_id", $item_id)->find(); + if (!$access) { + throw new Exception("@todo MISSING_ACCESS for $item_id"); + } + + return $access->__get("{$perm_name}_{$group_id}") == self::ALLOW; + } + + /** + * Internal method to set a permission + * + * @param integer $group_id + * @param string $perm_name + * @param integer $item_id + * @param boolean $value + * @return boolean + */ + private static function _set($group_id, $perm_name, $item_id, $value) { + $access = ORM::factory("access_intent")->where("item_id", $item_id)->find(); + if (!$access->loaded) { + throw new Exception("@todo MISSING_ACCESS for $item_id"); + } + + $access->__set("{$perm_name}_{$group_id}", $value); + $access->save(); + + self::_update_access_cache($group_id, $perm_name, $item_id); + } + + /** + * Allow a group to have a permission on an item. + * + * @param integer $group_id + * @param string $perm_name + * @param integer $item_id + * @return boolean + */ + public static function allow($group_id, $perm_name, $item_id) { + self::_set($group_id, $perm_name, $item_id, self::ALLOW); + } + + /** + * Deny a group the given permission on an item. + * + * @param integer $group_id + * @param string $perm_name + * @param integer $item_id + * @return boolean + */ + public static function deny($group_id, $perm_name, $item_id) { + self::_set($group_id, $perm_name, $item_id, self::DENY); + } + + /** + * Register a permission so that modules can use it. + * + * @param string $perm_name + * @return void + */ + public static function register_permission($perm_name) { + $permission = ORM::factory("permission", $perm_name); + if ($permission->loaded) { + throw new Exception("@todo PERMISSION_ALREADY_EXISTS $name"); + } + $permission->name = $perm_name; + $permission->save(); + + foreach (self::_get_all_groups() as $group) { + self::_add_columns($perm_name, $group->id); + } + self::_add_columns($perm_name, group::EVERYBODY); + } + + /** + * Delete a permission. + * + * @param string $perm_name + * @return void + */ + public static function delete_permission($name) { + foreach (self::_get_all_groups() as $group) { + self::_drop_columns($name, $group->id); + } + self::_drop_columns($name, group::EVERYBODY); + ORM::factory("permission", $name)->delete(); + } + + /** + * Add the appropriate columns for a new group + * + * @param Group_Model $group + * @return void + */ + public static function add_group($group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + self::_add_columns($perm->name, $group->id); + } + } + + /** + * Remove a group's permission columns (usually when it's deleted) + * + * @param Group_Model $group + * @return void + */ + public static function remove_group($group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + self::_drop_columns($perm->name, $group->id); + } + } + + /** + * Add new access rows when a new item is added. + * + * @param Item_Model $item + * @return void + */ + public static function add_item($item) { + $access_intent = ORM::factory("access_intent"); + $access_intent->item_id = $item->id; + $access_intent->save(); + + // Create a new access cache entry and copy the parents values. + $parent_access_cache = + ORM::factory("access_cache")->where("item_id", $item->parent()->id)->find(); + $access_cache = ORM::factory("access_cache"); + $access_cache->item_id = $item->id; + foreach (ORM::factory("permission")->find_all() as $perm) { + foreach (self::_get_all_groups() as $group) { + $field = "{$perm->name}_{$group->id}"; + $access_cache->$field = $parent_access_cache->$field; + } + $field = "{$perm->name}_0"; + $access_cache->$field = $parent_access_cache->$field; + } + $access_cache->save(); + } + + /** + * Delete appropriate access rows when an item is deleted. + * + * @param Item_Model $item + * @return void + */ + public static function remove_item($item) { + ORM::factory("access_intent")->where("item_id", $item->id)->delete(); + ORM::factory("access_cache")->where("item_id", $item->id)->delete(); + } + + /** + * Internal method to get all available groups. + * + * @return ORM_Iterator + */ + private static function _get_all_groups() { + try { + return ORM::factory("group")->find_all(); + } catch (Kohana_Database_Exception $e) { + return array(); + } + } + + /** + * Internal method to remove Permission/Group columns + * + * @param integer $group_id + * @param string $perm_name + * @return void + */ + private static function _drop_columns($perm_name, $group_id) { + $db = Database::instance(); + $field = "{$perm_name}_{$group_id}"; + $db->query("ALTER TABLE `access_caches` DROP `$field`"); + $db->query("ALTER TABLE `access_intents` DROP `$field`"); + } + + /** + * Internal method to add Permission/Group columns + * + * @param integer $group_id + * @param string $perm_name + * @return void + */ + private static function _add_columns($perm_name, $group_id) { + $db = Database::instance(); + $field = "{$perm_name}_{$group_id}"; + $db->query("ALTER TABLE `access_caches` ADD `$field` TINYINT(2) NOT NULL DEFAULT 0"); + $db->query("ALTER TABLE `access_intents` ADD `$field` BOOLEAN DEFAULT NULL"); + } + + /** + * Update the Access_Cache model based on information from the Access_Intent model. This + * creates a fast-lookup table for permissions based on the rules that the user has specified in + * the intent model. + * + * @todo: use database locking + * + * @param integer $group_id + * @param string $perm_name + * @param integer $item_id + * @return void + */ + public static function _update_access_cache($group_id, $perm_name, $item_id) { + $item = ORM::factory("item", $item_id); + if (!$item->loaded) { + throw new Exception("@todo MISSING_ITEM for $item_id"); + } + $access = ORM::factory("access_intent")->where("item_id", $item_id)->find(); + + $db = Database::instance(); + $field = "{$perm_name}_{$group_id}"; + + if ($perm_name == "view") { + // With view permissions, deny values in the parent can override allow values in the child, + // so start from the bottom of the tree and work upwards overlaying negative on top of + // positive. + // + // If the item's intent is ALLOW or DEFAULT, it's possible that some ancestor has specified + // DENY and this ALLOW cannot be obeyed. So in that case, back up the tree and find any + // non-DEFAULT and non-ALLOW parent and propagate from there. If we can't find a matching + // item, then its safe to propagate from here. + if ($access->$field != self::DENY) { + $tmp_item = ORM::factory("item") + ->join("access_intents", "items.id", "access_intents.item_id") + ->where("left <", $item->left) + ->where("right >", $item->right) + ->where($field, self::DENY) + ->orderby("left", "DESC") + ->limit(1) + ->find(); + if ($tmp_item->loaded) { + $item = $tmp_item; + } + } + + // We will have a problem if we're trying to change a DENY to an ALLOW because the + // access_caches table will already contain DENY values and we won't be able to overwrite + // them according the rule above. So mark every permission below this level as UNKNOWN so + // that we can tell which permissions have been changed, and which ones need to be updated. + // + // Potential problem: if $item_id's intent is unspecified then we have to back up the tree to + // find the nearest non-default parent and update the map starting from there. That can't + // happen currently, but if it does, then the symptom will be that we have a branch of + // access_caches in the UNKNOWN state. + $db->query("UPDATE `access_caches` SET `$field` = ? " . + "WHERE `item_id` IN " . + " (SELECT `id` FROM `items` " . + " WHERE `left` >= $item->left " . + " AND `right` <= $item->right)", + array(self::UNKNOWN)); + + $query = $db->query( + "SELECT `access_intents`.`$field`, `items`.`left`, `items`.`right`, `items`.`id` " . + "FROM `access_intents` JOIN (`items`) ON (`access_intents`.`item_id` = `items`.`id`) " . + "WHERE `left` >= $item->left " . + "AND `right` <= $item->right " . + "AND `type` = 'album' " . + "AND `$field` IS NOT NULL " . + "ORDER BY `level` DESC "); + foreach ($query as $row) { + if ($row->$field == self::ALLOW) { + // Propagate ALLOW for any row that is still UNKNOWN. + $db->query( + "UPDATE `access_caches` SET `$field` = {$row->$field} " . + "WHERE `$field` = ? " . + "AND `item_id` IN " . + " (SELECT `id` FROM `items` " . + " WHERE `left` >= $row->left " . + " AND `right` <= $row->right)", + array(self::UNKNOWN)); + } else if ($row->$field == self::DENY) { + // DENY overwrites everything below it + $db->query( + "UPDATE `access_caches` SET `$field` = {$row->$field} " . + "WHERE `item_id` IN " . + " (SELECT `id` FROM `items` " . + " WHERE `left` >= $row->left " . + " AND `right` <= $row->right)"); + } + } + } else { + // With non-view permissions, each level can override any permissions that came above it + // so start at the top and work downwards, overlaying permissions as we go. + $query = $db->query( + "SELECT `access_intents`.`$field`, `items`.`left`, `items`.`right` " . + "FROM `access_intents` JOIN (`items`) ON (`access_intents`.`item_id` = `items`.`id`) " . + "WHERE `left` >= ? " . + "AND `right` <= ? " . + "AND `type` = 'album' " . + "AND `$field` IS NOT NULL " . + "ORDER BY `level` ASC ", + array($item->left, $item->right)); + foreach ($query as $row) { + $db->query( + "UPDATE `access_caches` SET `$field` = {$row->$field} " . + "WHERE `item_id` IN " . + " (SELECT `id` FROM `items` " . + " WHERE `left` >= $row->left " . + " AND `right` <= $row->right)"); + } + } + } +} diff --git a/core/helpers/core_event.php b/core/helpers/core_event.php new file mode 100644 index 00000000..3cf9f12f --- /dev/null +++ b/core/helpers/core_event.php @@ -0,0 +1,45 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ + +class core_event_Core { + public static function group_created($group) { + access::add_group($group); + } + + public static function group_before_delete($group) { + access::remove_group($group); + } + + public static function photo_created($photo) { + access::add_item($photo); + } + + public static function photo_before_delete($photo) { + access::remove_item($photo); + } + + public static function album_created($album) { + access::add_item($album); + } + + public static function album_before_delete($album) { + access::remove_item($album); + } +} diff --git a/core/helpers/core_installer.php b/core/helpers/core_installer.php index 792798ea..d1e359c7 100644 --- a/core/helpers/core_installer.php +++ b/core/helpers/core_installer.php @@ -31,11 +31,15 @@ class core_installer { } if ($version == 0) { - $db->query("CREATE TABLE `access` ( + $db->query("CREATE TABLE `access_caches` ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9), + PRIMARY KEY (`id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE `access_intents` ( `id` int(9) NOT NULL auto_increment, `item_id` int(9), - `group_id` int(9) NOT NULL, - `perm` char(255) default NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); @@ -70,10 +74,21 @@ class core_installer { UNIQUE KEY(`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + $db->query("CREATE TABLE `permissions` ( + `id` int(9) NOT NULL auto_increment, + `name` char(255) default NULL, + `version` int(9) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + foreach (array("albums", "resizes") as $dir) { @mkdir(VARPATH . $dir); } + access::register_permission("view"); + access::register_permission("edit"); + $root = ORM::factory("item"); $root->type = 'album'; $root->title = "Gallery"; @@ -85,13 +100,19 @@ class core_installer { $root->set_thumbnail(DOCROOT . "core/tests/test.jpg", 200, 150) ->save(); + access::add_item($root); + access::allow(0, "view", $root->id); + access::deny(0, "edit", $root->id); + module::set_version("core", 1); } } public static function uninstall() { $db = Database::instance(); - $db->query("DROP TABLE IF EXISTS `access`;"); + $db->query("DROP TABLE IF EXISTS `access_cache`;"); + $db->query("DROP TABLE IF EXISTS `access_intent`;"); + $db->query("DROP TABLE IF EXISTS `permissions`;"); $db->query("DROP TABLE IF EXISTS `items`;"); $db->query("DROP TABLE IF EXISTS `modules`;"); system("/bin/rm -rf " . VARPATH . "albums"); diff --git a/core/models/access_cache.php b/core/models/access_cache.php new file mode 100644 index 00000000..aa530993 --- /dev/null +++ b/core/models/access_cache.php @@ -0,0 +1,21 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Access_Cache_Model extends ORM { +} diff --git a/core/models/access_intent.php b/core/models/access_intent.php new file mode 100644 index 00000000..b7d68508 --- /dev/null +++ b/core/models/access_intent.php @@ -0,0 +1,21 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2008 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Access_Intent_Model extends ORM { +} diff --git a/core/models/access.php b/core/models/permission.php index f85e152d..e2080828 100644 --- a/core/models/access.php +++ b/core/models/permission.php @@ -17,5 +17,5 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -class Access_Model extends ORM { +class Permission_Model extends ORM { } diff --git a/core/views/welcome.html.php b/core/views/welcome.html.php index e2390e9e..1cfd6d7c 100644 --- a/core/views/welcome.html.php +++ b/core/views/welcome.html.php @@ -128,6 +128,11 @@ a:hover { text-decoration: underline; } + + span.understate { + font-size: 70%; + font-style: italic; + } </style> <?= html::script("lib/jquery.js") ?> <?= html::script("lib/jquery.cookie.js") ?> @@ -334,15 +339,14 @@ <? $current = $album_tree[$current]; ?> <ul> <li> + <span class="understate">(<?= $current->album->id ?>)</span> <?= html::anchor("albums/{$current->album->id}", $current->album->title) ?> - <? foreach (ORM::factory("item")->list_fields("items") as $field => $info): ?> - <? if (!strncmp($field, "view_", 5)): ?> - [<?= $field ?>=<?= $current->album->$field ?>] + access: + <? if (access::can(group::EVERYBODY, "view", $current->album->id)): ?> + access: <?= html::anchor("welcome/deny_perm/0/view/{$current->album->id}", "yes") ?> + <? else: ?> + access: <?= html::anchor("welcome/add_perm/0/view/{$current->album->id}", "no") ?> <? endif ?> - <? endforeach ?> - <? foreach (ORM::factory("access")->where("item_id", $current->album->id)->find() as $access): ?> - [ <?= $access->id ?> ] - <? endforeach ?> <? $stack[] = "CLOSE"; ?> <? if ($current->children): ?> <? $stack = array_merge($stack, $current->children) ?> diff --git a/modules/user/helpers/group.php b/modules/user/helpers/group.php index 2e6a3962..12118432 100644 --- a/modules/user/helpers/group.php +++ b/modules/user/helpers/group.php @@ -24,6 +24,7 @@ * Note: by design, this class does not do any permission checking. */ class group_Core { + const EVERYBODY = 0; const REGISTERED_USERS = 1; /** @@ -41,9 +42,7 @@ class group_Core { $group->name = $name; $group->save(); - // Create the view column for this group in the items table. - Database::instance()->query("ALTER TABLE `items` ADD `view_{$group->id}` BOOLEAN DEFAULT 0"); - + module::event("group_created", $group); return $group; } @@ -56,8 +55,7 @@ class group_Core { $group = ORM::factory("group", $id); if ($group->loaded) { - // Drop the view column for this group in the items table. - Database::instance()->query("ALTER TABLE `items` DROP `view_{$group->id}`"); + module::event("group_before_delete", $group); $group->delete(); } } diff --git a/modules/user/helpers/user_installer.php b/modules/user/helpers/user_installer.php index 0f030d0e..58066acd 100644 --- a/modules/user/helpers/user_installer.php +++ b/modules/user/helpers/user_installer.php @@ -66,14 +66,13 @@ class user_installer { } public static function uninstall() { - // Remove all our groups so that we clean up the items table + // Delete all users and groups so that we give other modules an opportunity to clean up + foreach (ORM::factory("user")->find_all() as $user) { + user::delete($user->id); + } + foreach (ORM::factory("group")->find_all() as $group) { - try { - group::delete($group->id); - } catch (Kohana_Database_Exception $e) { - // We may get errors when we try to remove the view columns from the items table. - // Ignore those for now. - } + group::delete($group->id); } try { |