diff --git a/core/permissions.php b/core/permissions.php index a1b7d0d14..7a0814be1 100644 --- a/core/permissions.php +++ b/core/permissions.php @@ -25,6 +25,7 @@ abstract class Permissions /** enable or disable extensions */ public const MANAGE_EXTENSION_LIST = "manage_extension_list"; + public const MANAGE_PERMISSION_LIST = "manage_permission_list"; public const MANAGE_ALIAS_LIST = "manage_alias_list"; public const MANAGE_AUTO_TAG = "manage_auto_tag"; public const MASS_TAG_EDIT = "mass_tag_edit"; diff --git a/core/testcase.php b/core/testcase.php index d28bcdceb..e1e420ab3 100644 --- a/core/testcase.php +++ b/core/testcase.php @@ -46,6 +46,8 @@ public function setUp(): void foreach ($database->get_col("SELECT id FROM images") as $image_id) { send_event(new ImageDeletionEvent(Image::by_id_ex((int)$image_id), true)); } + // Reload users from the database in case they were modified + UserClass::loadClasses(); $page = new Page(); $_tracer->end(); # setUp diff --git a/core/tests/UserClassTest.php b/core/tests/UserClassTest.php index 84f06aee8..2f83ab5e4 100644 --- a/core/tests/UserClassTest.php +++ b/core/tests/UserClassTest.php @@ -10,17 +10,6 @@ class UserClassTest extends ShimmiePHPUnitTestCase { - public function test_new_class(): void - { - $cls = new UserClass("user2", "user", [ - Permissions::CREATE_COMMENT => true, - Permissions::BIG_SEARCH => false, - ]); - $this->assertEquals("user2", $cls->name); - $this->assertTrue($cls->can(Permissions::CREATE_COMMENT)); - $this->assertFalse($cls->can(Permissions::BIG_SEARCH)); - } - public function test_not_found(): void { $cls = UserClass::$known_classes['user']; diff --git a/core/user.php b/core/user.php index bb26bc9e2..65ac3ec61 100644 --- a/core/user.php +++ b/core/user.php @@ -53,7 +53,7 @@ public function __construct(array $row) $this->email = $row['email']; $this->join_date = $row['joindate']; $this->passhash = $row['pass']; - + //var_dump(array_keys(UserClass::$known_classes)); if (array_key_exists($row["class"], UserClass::$known_classes)) { $this->class = UserClass::$known_classes[$row["class"]]; } else { diff --git a/core/userclass.php b/core/userclass.php index 2f64fe8d7..b1cdda78f 100644 --- a/core/userclass.php +++ b/core/userclass.php @@ -4,6 +4,8 @@ namespace Shimmie2; +use FFSPHP\PDO; + use GQLA\Type; use GQLA\Field; @@ -19,21 +21,29 @@ class UserClass #[Field] public string $name; public ?UserClass $parent = null; + public bool $core = false; /** @var array */ public array $abilities = []; - /** - * @param array $abilities - */ - public function __construct(string $name, ?string $parent = null, array $abilities = []) + public function __construct(string $name) { + global $database; + $this->name = $name; - $this->abilities = $abilities; + $class = $database->execute("SELECT * FROM permissions WHERE class = :class", ["class" => $name])->fetch(PDO::FETCH_ASSOC); - if (!is_null($parent)) { - $this->parent = static::$known_classes[$parent]; + if (!is_null($class["parent"])) { + $this->parent = static::$known_classes[$class["parent"]]; } + $this->core = (bool)$class["core"]; + + unset($class["id"]); + unset($class["class"]); + unset($class["parent"]); + unset($class["core"]); + + $this->abilities = $class; static::$known_classes[$name] = $this; } @@ -58,10 +68,16 @@ public function permissions(): array */ public function can(string $ability): bool { - if (array_key_exists($ability, $this->abilities)) { - return $this->abilities[$ability]; + // hellbanned is a snowflake, it isn't really a "permission" so much as + // "a special behaviour which applies to one particular user class" + if ($this->name == "admin" && $ability != "hellbanned") { + return true; + } elseif (array_key_exists($ability, $this->abilities) && $this->abilities[$ability]) { + return true; } elseif (!is_null($this->parent)) { return $this->parent->can($ability); + } elseif (array_key_exists($ability, $this->abilities)) { + return false; } else { $min_dist = 9999; $min_ability = null; @@ -75,63 +91,20 @@ public function can(string $ability): bool throw new ServerError("Unknown ability '$ability'. Did the developer mean '$min_ability'?"); } } -} -$_all_false = []; -$_all_true = []; -foreach ((new \ReflectionClass(Permissions::class))->getConstants() as $k => $v) { - assert(is_string($v)); - $_all_false[$v] = false; - $_all_true[$v] = true; + // clear and load classes from the database. + public static function loadClasses(): void + { + global $database; + + // clear any existing classes to avoid complications with parent classes + foreach(static::$known_classes as $k => $v) { + unset(static::$known_classes[$k]); + } + + $classes = $database->get_col("SELECT class FROM permissions WHERE 1=1 ORDER BY id"); + foreach($classes as $class) { + new UserClass($class); + } + } } -// hellbanned is a snowflake, it isn't really a "permission" so much as -// "a special behaviour which applies to one particular user class" -$_all_true[Permissions::HELLBANNED] = false; -new UserClass("base", null, $_all_false); -new UserClass("admin", null, $_all_true); -unset($_all_true); -unset($_all_false); - -// Ghost users can't do anything -new UserClass("ghost", "base", [ - Permissions::READ_PM => true, -]); - -// Anonymous users can't do anything by default, but -// the admin might grant them some permissions -new UserClass("anonymous", "base", [ - Permissions::CREATE_USER => true, -]); - -new UserClass("user", "base", [ - Permissions::BIG_SEARCH => true, - Permissions::CREATE_IMAGE => true, - Permissions::CREATE_COMMENT => true, - Permissions::EDIT_IMAGE_TAG => true, - Permissions::EDIT_IMAGE_SOURCE => true, - Permissions::EDIT_IMAGE_TITLE => true, - Permissions::EDIT_IMAGE_RELATIONSHIPS => true, - Permissions::EDIT_IMAGE_ARTIST => true, - Permissions::CREATE_IMAGE_REPORT => true, - Permissions::EDIT_IMAGE_RATING => true, - Permissions::EDIT_FAVOURITES => true, - Permissions::CREATE_VOTE => true, - Permissions::SEND_PM => true, - Permissions::READ_PM => true, - Permissions::SET_PRIVATE_IMAGE => true, - Permissions::PERFORM_BULK_ACTIONS => true, - Permissions::BULK_DOWNLOAD => true, - Permissions::CHANGE_USER_SETTING => true, - Permissions::FORUM_CREATE => true, - Permissions::NOTES_CREATE => true, - Permissions::NOTES_EDIT => true, - Permissions::NOTES_REQUEST => true, - Permissions::POOLS_CREATE => true, - Permissions::POOLS_UPDATE => true, -]); - -new UserClass("hellbanned", "user", [ - Permissions::HELLBANNED => true, -]); - -@include_once "data/config/user-classes.conf.php"; diff --git a/ext/perm_manager/info.php b/ext/perm_manager/info.php new file mode 100644 index 000000000..c31834700 --- /dev/null +++ b/ext/perm_manager/info.php @@ -0,0 +1,20 @@ +get_version("ext_perm_version") < 1) { + $this->set_version("ext_perm_version", 1); + UserClass::loadClasses(); + } + } + + public function __construct() + { + parent::__construct(); + if($this->get_version("ext_perm_version") >= 1) { + UserClass::loadClasses(); + } + } + + public function onPageRequest(PageRequestEvent $event): void + { + global $page, $user; + if ($event->page_matches("perm_manager/{class}/set_parent", method: "POST", permission: Permissions::MANAGE_PERMISSION_LIST)) { + $class = $event->get_arg('class'); + $parent = only_strings($event->POST)["parent"]; + $this->set_parent($class, $parent); + $log = "Changed parent of \"$class\" to \"$parent\""; + log_warning("perm_manager", $log, $log); + $page->set_mode(PageMode::REDIRECT); + $page->set_redirect(make_link("perm_manager")); + } elseif ($event->page_matches("perm_manager/{class}/set_perms", method: "POST", permission: Permissions::MANAGE_PERMISSION_LIST)) { + $class = $event->get_arg('class'); + $this->set_permissions($class, only_strings($event->POST)); + $log = "Updated permissions of \"$class\""; + log_warning("perm_manager", $log, $log); + $page->set_mode(PageMode::REDIRECT); + $page->set_redirect(make_link("perm_manager")); + } elseif ($event->page_matches("perm_manager/{class}/delete", method: "POST", permission: Permissions::MANAGE_PERMISSION_LIST)) { + $class = $event->get_arg('class'); + if ($class == only_strings($event->POST)["name"]) { + $success = $this->remove_class($class); + if ($success) { + $log = "Removed class \"$class\""; + log_warning("perm_manager", $log, $log); + $page->set_mode(PageMode::REDIRECT); + $page->set_redirect(make_link("perm_manager")); + } else { + $this->theme->display_error( + 422, + "Class deletion failed", + "Cannot delete a core class or a class which is a parent." + ); + } + } else { + $this->theme->display_error( + 400, + "Class deletion failed", + "The class name did not match." + ); + + } + } elseif ($event->page_matches("perm_manager/new", method: "POST", permission: Permissions::MANAGE_PERMISSION_LIST)) { + $POST = only_strings($event->POST); + $name = $POST["new_name"]; + $parent = $POST["new_parent"]; + $success = $this->create_class($name, $parent); + if ($success) { + $log = "Created class \"$name\""; + log_warning("perm_manager", $log, $log); + $page->set_mode(PageMode::REDIRECT); + $page->set_redirect(make_link("perm_manager/$name")); + } else { + $this->theme->display_error( + 422, + "Class creation failed", + "The class name invalid." + ); + + } + } elseif ($event->page_matches("perm_manager/{class}", method: "GET", permission: Permissions::MANAGE_PERMISSION_LIST)) { + $class = $event->get_arg('class'); + $parent_options = $this->get_parent_options($class); + $permissions = array_keys($this->get_class($class)->abilities); + $this->theme->display_table($page, $this->is_parent($class), $parent_options, $permissions, $this->get_class($class)); + } elseif ($event->page_matches("perm_manager", method: "GET", permission: Permissions::MANAGE_PERMISSION_LIST)) { + $this->theme->display_list($page, UserClass::$known_classes, $this->get_parent_options("")); + } + } + + public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void + { + global $user; + if ($event->parent === "system") { + if ($user->can(Permissions::MANAGE_PERMISSION_LIST)) { + $event->add_nav_link("perm_manager", new Link('perm_manager'), "Permission Manager"); + } + } + } + + public function onUserBlockBuilding(UserBlockBuildingEvent $event): void + { + global $user; + if ($user->can(Permissions::MANAGE_EXTENSION_LIST)) { + $event->add_link("Permission Manager", make_link("perm_manager")); + } + } + + /** + * @return string[] + */ + private function get_parent_options(string $class): array + { + $classes = array_keys(UserClass::$known_classes); + // remove options greater or equal id to the current class + // this prevents a situation on startup where this class trys to load a higher parent class before it exists + if ($class) { + while (array_pop($classes) != $class) { + // do nothing + } + } + $parent_options = []; + foreach($classes as $c) { + $parent_options[$c] = $c; + } + unset($parent_options["admin"]); + return $parent_options; + } + + private function is_parent(string $class): bool + { + foreach(UserClass::$known_classes as $c) { + if ($c->parent && $c->parent->name == $class) { + return true; + } + } + return false; + } + + private function get_class(string $class): UserClass + { + return UserClass::$known_classes[$class]; + } + + private function set_parent(string $class, string $parent): void + { + global $database; + if (in_array($parent, $this->get_parent_options($class))) { + $database->execute("UPDATE permissions SET parent=:parent WHERE class = :class", ["parent" => $parent, "class" => $class]); + // reload all classes from database + UserClass::loadClasses(); + } + } + + /** + * @param array $POST + */ + private function set_permissions(string $class, array $POST): void + { + global $database; + + $perm_list = array_keys(UserClass::$known_classes[$class]->abilities); + $new_perms = []; + foreach($perm_list as $k) { + $new_perms[$k] = false; + } + + foreach($POST as $k => $v) { + if (str_starts_with($k, 'perm_') && array_key_exists(substr($k, 5), $new_perms)) { + $new_perms[substr($k, 5)] = true; + } + } + $perm_query = ""; + $first = true; + foreach($perm_list as $k) { + if (!$first) { + $perm_query .= ","; + } else { + $first = false; + } + $perm_query .= "$k=:$k"; + } + + $new_perms["class"] = $class; + $database->execute("UPDATE permissions SET ".$perm_query." WHERE class = :class", $new_perms); + // reload all classes from database + UserClass::loadClasses(); + } + + private function create_class(string $name, string $parent): bool + { + global $database; + if ($name == "" || in_array($name, array_keys(UserClass::$known_classes))) { + return false; + } + if (in_array($parent, $this->get_parent_options(""))) { + $database->execute("INSERT INTO permissions (class, parent) VALUES (:class, :parent)", ["class" => $name, "parent" => $parent]); + // reload all classes from database + UserClass::loadClasses(); + return true; + } + return false; + } + + private function remove_class(string $name): bool + { + global $database; + // check if parent or a core class + if (!$this->get_class($name)->core && !$this->is_parent($name)) { + $database->execute("DELETE FROM permissions WHERE class=:class", ["class" => $name]); + // reload all classes from database + UserClass::loadClasses(); + return true; + } + return false; + } +} diff --git a/ext/perm_manager/test.php b/ext/perm_manager/test.php new file mode 100644 index 000000000..cec8ded06 --- /dev/null +++ b/ext/perm_manager/test.php @@ -0,0 +1,97 @@ +log_in_as_admin(); + $this->get_page('perm_manager'); + $this->assert_title("User Classes"); + //$this->assert_text("SimpleTest integration"); // FIXME: something which still exists + } + + public function test_new_class(): void + { + global $database; + + $this->log_in_as_admin(); + $this->assertFalse(in_array("test1", UserClass::$known_classes)); + $page = $this->post_page("perm_manager/new", ["new_name" => "test1", "new_parent" => "base"]); + $this->assert_response(302); + $this->assertEquals(1, $database->get_one("SELECT COUNT(*) FROM permissions WHERE class = :class AND parent = :parent", ["class" => "test1", "parent" => "base"])); + $this->assertTrue(in_array("test1", array_keys(UserClass::$known_classes))); + } + + public function test_duplicate_class(): void + { + global $database; + + $this->log_in_as_admin(); + $page = $this->post_page("perm_manager/new", ["new_name" => "user", "new_parent" => "base"]); + $this->assert_response(422); + $this->assertEquals(1, $database->get_one("SELECT COUNT(*) FROM permissions WHERE class = :class", ["class" => "user"])); + } + + public function test_modify_class(): void + { + $this->log_in_as_admin(); + $page = $this->post_page("perm_manager/new", ["new_name" => "test1", "new_parent" => "base"]); + + // add comment permissions, add bypass permission + $page = $this->post_page("perm_manager/test1/set_perms", ["perm_bypass_image_approval" => "on", "perm_create_comment" => "on"]); + $cls = UserClass::$known_classes["test1"]; + $this->assertTrue($cls->can(Permissions::BYPASS_IMAGE_APPROVAL)); + $this->assertTrue($cls->can(Permissions::CREATE_COMMENT)); + $this->assertFalse($cls->can(Permissions::BIG_SEARCH)); + + // remove comment permission + $page = $this->post_page("perm_manager/test1/set_perms", ["perm_bypass_image_approval" => "on"]); + $cls = UserClass::$known_classes["test1"]; + $this->assertTrue($cls->can(Permissions::BYPASS_IMAGE_APPROVAL)); + $this->assertFalse($cls->can(Permissions::CREATE_COMMENT)); + $this->assertFalse($cls->can(Permissions::BIG_SEARCH)); + + // grant all user permissions, retain bypass permission + $page = $this->post_page("perm_manager/test1/set_parent", ["parent" => "user"]); + $cls = UserClass::$known_classes["test1"]; + $this->assertTrue($cls->can(Permissions::BYPASS_IMAGE_APPROVAL)); + $this->assertTrue($cls->can(Permissions::CREATE_COMMENT)); + $this->assertTrue($cls->can(Permissions::BIG_SEARCH)); + } + + public function test_delete_class(): void + { + global $database; + + $this->log_in_as_admin(); + $page = $this->post_page("perm_manager/new", ["new_name" => "test1", "new_parent" => "base"]); + + // incorrect verification name + $page = $this->post_page("perm_manager/test1/delete", ["name" => "testone"]); + $this->assert_response(400); + $this->assertEquals(1, $database->get_one("SELECT COUNT(*) FROM permissions WHERE class = :class", ["class" => "test1"])); + + // is a core class + $page = $this->post_page("perm_manager/ghost/delete", ["name" => "ghost"]); + $this->assert_response(422); + $this->assertEquals(1, $database->get_one("SELECT COUNT(*) FROM permissions WHERE class = :class", ["class" => "ghost"])); + + // has a child class + $page = $this->post_page("perm_manager/new", ["new_name" => "test2", "new_parent" => "test1"]); + $page = $this->post_page("perm_manager/test1/delete", ["name" => "test1"]); + $this->assert_response(422); + $this->assertEquals(1, $database->get_one("SELECT COUNT(*) FROM permissions WHERE class = :class", ["class" => "test1"])); + + $page = $this->post_page("perm_manager/test2/delete", ["name" => "test2"]); + $this->assert_response(302); + $this->assertEquals(0, $database->get_one("SELECT COUNT(*) FROM permissions WHERE class = :class", ["class" => "test2"])); + $page = $this->post_page("perm_manager/test1/delete", ["name" => "test1"]); + $this->assert_response(302); + $this->assertEquals(0, $database->get_one("SELECT COUNT(*) FROM permissions WHERE class = :class", ["class" => "test1"])); + $this->assertFalse(in_array("test1", UserClass::$known_classes)); + } +} diff --git a/ext/perm_manager/theme.php b/ext/perm_manager/theme.php new file mode 100644 index 000000000..60ef1e172 --- /dev/null +++ b/ext/perm_manager/theme.php @@ -0,0 +1,151 @@ +parent); + + $parent_form = $editable ? SHM_SIMPLE_FORM( + "perm_manager/{$class->name}/set_parent", + LABEL(["for" => "parent"], "Parent class: "), + SHM_SELECT("parent", $parent_options, required: true, selected_options: $class->parent ? [$class->parent->name] : ["base"]), + INPUT(["type" => 'submit', "value" => 'Set Parent']) + ) : null; + + $tbody = TBODY(); + + $perm_form = SHM_SIMPLE_FORM( + "perm_manager/{$class->name}/set_perms", + $is_parent ? B("WARNING: This class is a parent to another class. Modifying this class will do the same for any child classes.") : null, + TABLE( + ["id" => 'permissions', "class" => 'zebra'], + THEAD(TR( + TH("Enabled"), + TH("Name"), + )), + $tbody, + $editable ? TFOOT(TR(TD(["colspan" => '5'], INPUT(["type" => 'submit', "value" => 'Set Permissions'])))) : null + ) + ); + + foreach ($permissions as $permission) { + $tbody->appendChild(TR( + ["data-ext" => $permission], + TD(INPUT([ + "type" => 'checkbox', + "name" => "perm_{$permission}", + "id" => "perm_{$permission}", + "checked" => $class->can($permission), + "disabled" => !$editable || ($class->parent && $class->parent->can($permission)) + ])), + TD(LABEL( + ["for" => "perm_{$permission}"], + ( + $permission + ) + )), + )); + } + + $delete_form = $editable && !$is_parent && !$class->core ? SHM_SIMPLE_FORM( + "perm_manager/{$class->name}/delete", + TABLE( + ["id" => 'delete_class'], + TBODY( + TR(TD( + LABEL(["for" => "name"], "Type class name to confirm: "), + INPUT(["type" => 'text', "name" => "name", "placeholder" => $class->name, "required" => ""]), + )), + TR(TD( + INPUT(["type" => 'submit', "value" => 'Delete class']) + )) + ) + ) + ) : null; + + $title = ($editable ? "Edit" : "View") . " Class - " . $class->name; + + $page->set_title($title); + $page->set_heading("Permissions"); + $page->add_block(new Block($title, "")); + if ($editable) { + $page->add_block(new Block("Parent", $parent_form)); + } + $page->add_block(new Block("Permissions", $perm_form)); + if ($editable && !$is_parent && !$class->core) { + $page->add_block(new Block("Delete class", $delete_form)); + } + } + + /** + * @param UserClass[] $classes + * @param string[] $parent_options + */ + public function display_list(Page $page, array $classes, array $parent_options): void + { + $tbody = TBODY(); + + $table = TABLE( + ["id" => 'permissions', "class" => 'zebra'], + THEAD(TR( + TH("Name"), + TH("Details"), + )), + $tbody + ); + foreach ($classes as $class) { + $tbody->appendChild(TR( + TD($class->name), + TD( + A(["href" => "perm_manager/{$class->name}"], $class->parent ? "Edit" : "View") + ), + )); + } + + $new_form = SHM_SIMPLE_FORM( + "perm_manager/new", + TABLE( + ["id" => 'new_class'], + TBODY( + TR(TD( + LABEL(["for" => "new_name"], "Class name: "), + INPUT(["type" => 'text', "name" => "new_name", "required" => ""]), + )), + TR(TD( + LABEL(["for" => "new_parent"], "Parent class: "), + SHM_SELECT("new_parent", $parent_options, required: true, selected_options: ["base"]), + )), + TR(TD( + INPUT(["type" => 'submit', "value" => 'Add class']) + )) + ) + ) + ); + + $page->set_title("User Classes"); + $page->set_heading("User Classes"); + $page->add_block(new Block("User Classes", $table)); + $page->add_block(new Block("New Class", $new_form)); + } +} diff --git a/ext/upgrade/main.php b/ext/upgrade/main.php index 9d7cd8cda..b354fae4a 100644 --- a/ext/upgrade/main.php +++ b/ext/upgrade/main.php @@ -208,6 +208,45 @@ public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void $this->set_version("db_version", 21); $database->begin_transaction(); } + + if ($this->get_version("db_version") < 22) { + log_info("upgrade", "Adding permissions table"); + $permissions = ["change_setting", "override_config", "change_user_setting", "change_other_user_setting", "big_search", "manage_extension_list", "manage_permission_list", "manage_alias_list", "manage_auto_tag", "mass_tag_edit", "view_ip", "ban_ip", "create_user", "create_other_user", "edit_user_name", "edit_user_password", "edit_user_info", "edit_user_class", "delete_user", "create_comment", "delete_comment", "bypass_comment_checks", "replace_image", "create_image", "edit_image_tag", "edit_image_source", "edit_image_owner", "edit_image_lock", "edit_image_title", "edit_image_relationships", "edit_image_artist", "bulk_edit_image_tag", "bulk_edit_image_source", "delete_image", "ban_image", "view_eventlog", "ignore_downtime", "view_registrations", "create_image_report", "view_image_report", "wiki_admin", "edit_wiki_page", "delete_wiki_page", "manage_blocks", "manage_admintools", "send_pm", "read_pm", "view_other_pms", "edit_feature", "create_vote", "bulk_edit_vote", "edit_other_vote", "view_sysinfo", "hellbanned", "view_hellbanned", "protected", "edit_image_rating", "bulk_edit_image_rating", "view_trash", "perform_bulk_actions", "bulk_add", "edit_files", "edit_tag_categories", "rescan_media", "see_image_view_counts", "edit_favourites", "artists_admin", "blotter_admin", "tips_admin", "cron_admin", "approve_image", "approve_comment", "bypass_image_approval", "forum_admin", "forum_create", "notes_admin", "notes_create", "notes_edit", "notes_request", "pools_admin", "pools_create", "pools_update", "set_private_image", "set_others_private_images", "cron_run", "bulk_import", "bulk_export", "bulk_download", "bulk_parent_child"]; + $perms_query = implode(" BOOLEAN,\n", $permissions); + $perms_query .= " BOOLEAN,\n"; + + + // id is needed to keep dependencies in order when loading + $database->create_table("permissions", " + id SCORE_AIPK, + class VARCHAR(32) NOT NULL UNIQUE, + parent VARCHAR(32) NULL, + core BOOLEAN, + {$perms_query} + "); + $database->execute("CREATE INDEX permissions_class_idx ON permissions(class)", []); + + $database->standardise_boolean("permissions", "core"); + foreach($permissions as $p) { + $database->standardise_boolean("permissions", $p); + } + + // add default classes + $database->execute("INSERT INTO permissions (class, core) VALUES ('base', TRUE)"); + // admin is a placeholder class which is overridden in UserClass->can() + $database->execute("INSERT INTO permissions (class, core) VALUES ('admin', TRUE)"); + $database->execute("INSERT INTO permissions (class, parent, core, read_pm) VALUES ('ghost', 'base', TRUE, TRUE)"); + $database->execute("INSERT INTO permissions (class, parent, core, create_user) VALUES ('anonymous', 'base', TRUE, TRUE)"); + $database->execute("INSERT INTO permissions (class, parent, core, big_search, create_image, create_comment, edit_image_tag, edit_image_source, edit_image_title, edit_image_relationships, edit_image_artist, create_image_report, edit_image_rating, edit_favourites, create_vote, send_pm, read_pm, set_private_image, perform_bulk_actions, bulk_download, change_user_setting, forum_create, notes_create, notes_edit, notes_request, pools_create, pools_update) VALUES ('user', 'base', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE)"); + $database->execute("INSERT INTO permissions (class, parent, core, hellbanned) VALUES ('hellbanned', 'user', TRUE, TRUE)"); + + // All user's classes must exist in the permissions table. prevent deletion of a class if any users have that class. + // This code is optional and SQLite doesn't support altering tables to add foreign keys. + if ($database->get_driver_id() != DatabaseDriverID::SQLITE) { + $database->execute("ALTER TABLE users ADD FOREIGN KEY (class) REFERENCES permissions(class) ON DELETE RESTRICT"); + } + $this->set_version("db_version", 22); + } } public function get_priority(): int