From 02806a24297e22763fb47f509e17576dc08ad980 Mon Sep 17 00:00:00 2001 From: ProfessorXZ Date: Thu, 21 Sep 2017 10:51:37 +0200 Subject: [PATCH] Added support for renaming groups. Fixes #1420 --- CHANGELOG.md | 3 +- TShockAPI/Commands.cs | 24 ++++++ TShockAPI/DB/GroupManager.cs | 160 +++++++++++++++++++++++++++++++---- 3 files changed, 168 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e455dab..8e59d2ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin ## Upcoming Changes * API: Added hooks for item, projectile and tile bans (@deadsurgeon42) +* API: Changed `PlayerHooks` permission hook mechanisms to allow negation from hooks (@deadsurgeon42) * API: New WorldGrassSpread hook which shold allow corruption/crimson/hallow creep config options to work (@DeathCradle) * Fixed saving when one player is one the server and another one joins (@MarioE) * Fixed /spawnmob not spawning negative IDs (@MarioE) @@ -11,9 +12,9 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin * Updated to new stat tracking system with more data so we can actually make informed software decisions (Jordan Coulam) * Fixed /time display at the end of Terraria hours (@koneko-nyan) * Added a warning notifying users of the minimum memory required to run TShock (@bartico6) +* Added /group rename to allow changing group names (@ColinBohn, @ProfessorXZ) ## TShock 4.3.24 -* API: Changed `PlayerHooks` permission hook mechanisms to allow negation from hooks (@deadsurgeon42) * Updated OpenTerraria API to 1.3.5.3 (@DeathCradle) * Updated Terraria Server API to 1.3.5.3 (@WhiteXZ, @hakusaro) * Updated TShock core components to 1.3.5.3 (@hakusaro) diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index fbc6c061..6fd3ca93 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -2865,6 +2865,7 @@ namespace TShockAPI "add - Adds a new group.", "addperm - Adds permissions to a group.", "color - Changes a group's chat color.", + "rename - Changes a group's name.", "del - Deletes a group.", "delperm - Removes permissions from a group.", "list [page] - Lists groups.", @@ -3074,6 +3075,29 @@ namespace TShockAPI } #endregion return; + case "rename": + #region Rename group + { + if (args.Parameters.Count != 3) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}group rename ", Specifier); + return; + } + + string group = args.Parameters[1]; + string newName = args.Parameters[2]; + try + { + string response = TShock.Groups.RenameGroup(group, newName); + args.Player.SendSuccessMessage(response); + } + catch (GroupManagerException ex) + { + args.Player.SendErrorMessage(ex.Message); + } + } + #endregion + return; case "del": #region Delete group { diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs index 62bad891..0332379f 100644 --- a/TShockAPI/DB/GroupManager.cs +++ b/TShockAPI/DB/GroupManager.cs @@ -26,6 +26,9 @@ using MySql.Data.MySqlClient; namespace TShockAPI.DB { + /// + /// Represents the GroupManager, which is in charge of group management. + /// public class GroupManager : IEnumerable { private IDbConnection database; @@ -36,17 +39,17 @@ namespace TShockAPI.DB database = db; var table = new SqlTable("GroupList", - new SqlColumn("GroupName", MySqlDbType.VarChar, 32) {Primary = true}, - new SqlColumn("Parent", MySqlDbType.VarChar, 32), - new SqlColumn("Commands", MySqlDbType.Text), - new SqlColumn("ChatColor", MySqlDbType.Text), - new SqlColumn("Prefix", MySqlDbType.Text), - new SqlColumn("Suffix", MySqlDbType.Text) - ); + new SqlColumn("GroupName", MySqlDbType.VarChar, 32) { Primary = true }, + new SqlColumn("Parent", MySqlDbType.VarChar, 32), + new SqlColumn("Commands", MySqlDbType.Text), + new SqlColumn("ChatColor", MySqlDbType.Text), + new SqlColumn("Prefix", MySqlDbType.Text), + new SqlColumn("Suffix", MySqlDbType.Text) + ); var creator = new SqlTableCreator(db, - db.GetSqlType() == SqlType.Sqlite - ? (IQueryBuilder) new SqliteQueryCreator() - : new MysqlQueryCreator()); + db.GetSqlType() == SqlType.Sqlite + ? (IQueryBuilder)new SqliteQueryCreator() + : new MysqlQueryCreator()); if (creator.EnsureTableStructure(table)) { // Add default groups if they don't exist @@ -85,7 +88,11 @@ namespace TShockAPI.DB AddGroup(name, parent, permissions, Group.defaultChatColor); } - + /// + /// Determines whether the given group exists. + /// + /// The group. + /// true if it does; otherwise, false. public bool GroupExists(string group) { if (group == "superadmin") @@ -99,11 +106,20 @@ namespace TShockAPI.DB return GetEnumerator(); } + /// + /// Gets the enumerator. + /// + /// The enumerator. public IEnumerator GetEnumerator() { return groups.GetEnumerator(); } + /// + /// Gets the group matching the specified name. + /// + /// The name. + /// The group. public Group GetGroupByName(string name) { var ret = groups.Where(g => g.Name == name); @@ -139,8 +155,8 @@ namespace TShockAPI.DB } string query = (TShock.Config.StorageType.ToLower() == "sqlite") - ? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);" - : "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3"; + ? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);" + : "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3"; if (database.Query(query, name, parentname, permissions, chatcolor) == 1) { groups.Add(group); @@ -200,6 +216,70 @@ namespace TShockAPI.DB group.Suffix = suffix; } + /// + /// Renames the specified group. + /// + /// The group's name. + /// The new name. + /// The response. + public String RenameGroup(string name, string newName) + { + if (!GroupExists(name)) + { + throw new GroupNotExistException(name); + } + + if (GroupExists(newName)) + { + throw new GroupExistsException(newName); + } + + if (database.Query("UPDATE GroupList SET GroupName = @0 WHERE GroupName = @1", newName, name) == 1) + { + var oldGroup = GetGroupByName(name); + var newGroup = new Group(newName, oldGroup.Parent, oldGroup.ChatColor, oldGroup.Permissions) + { + Prefix = oldGroup.Prefix, + Suffix = oldGroup.Suffix + }; + + groups.Remove(oldGroup); + groups.Add(newGroup); + // We need to check if the old group has been referenced as a parent and update those references accordingly + database.Query("UPDATE GroupList SET Parent = @0 WHERE Parent = @1", newName, name); + foreach (var group in groups.Where(g => g.Parent != null && g.Parent == oldGroup)) + { + group.Parent = newGroup; + } + + if (TShock.Config.DefaultGuestGroupName == oldGroup.Name) + { + TShock.Config.DefaultGuestGroupName = newGroup.Name; + Group.DefaultGroup = newGroup; + } + if (TShock.Config.DefaultRegistrationGroupName == oldGroup.Name) + { + TShock.Config.DefaultRegistrationGroupName = newGroup.Name; + } + + TShock.Config.Write(FileTools.ConfigPath); + database.Query("UPDATE Users SET Usergroup = @0 WHERE Usergroup = @1", newName, name); + foreach (var player in TShock.Players.Where(p => p?.Group == oldGroup)) + { + player.Group = newGroup; + } + return $"Group \"{name}\" has been renamed to \"{newName}\"."; + } + + throw new GroupManagerException($"Failed to rename group \"{name}\"."); + } + + /// + /// Deletes the specified group. + /// + /// The group's name. + /// Whether exceptions will be thrown in case something goes wrong. + /// public String DeleteGroup(String name, bool exceptions = false) { if (!GroupExists(name)) @@ -214,12 +294,18 @@ namespace TShockAPI.DB groups.Remove(TShock.Utils.GetGroup(name)); return "Group " + name + " has been deleted successfully."; } - else if (exceptions) - throw new GroupManagerException("Failed to delete group '" + name + ".'"); - return ""; + if (exceptions) + throw new GroupManagerException("Failed to delete group '" + name + ".'"); + return "Failed to delete group '" + name + ".'"; } + /// + /// Enumerates the given permission list and adds permissions for the specified group accordingly. + /// + /// The group name. + /// The permission list. + /// public String AddPermissions(String name, List permissions) { if (!GroupExists(name)) @@ -237,6 +323,12 @@ namespace TShockAPI.DB return ""; } + /// + /// Enumerates the given permission list and removes valid permissions for the specified group accordingly. + /// + /// The group name. + /// The permission list. + /// public String DeletePermissions(String name, List permissions) { if (!GroupExists(name)) @@ -254,12 +346,15 @@ namespace TShockAPI.DB return ""; } + /// + /// Enumerates the group list and loads permissions for each group appropriately. + /// public void LoadPermisions() { try { List newGroups = new List(groups.Count); - Dictionary newGroupParents = new Dictionary(groups.Count); + Dictionary newGroupParents = new Dictionary(groups.Count); using (var reader = database.QueryReader("SELECT * FROM GroupList")) { while (reader.Read()) @@ -271,7 +366,8 @@ namespace TShockAPI.DB continue; } - newGroups.Add(new Group(groupName, null, reader.Get("ChatColor"), reader.Get("Commands")) { + newGroups.Add(new Group(groupName, null, reader.Get("ChatColor"), reader.Get("Commands")) + { Prefix = reader.Get("Prefix"), Suffix = reader.Get("Suffix"), }); @@ -360,32 +456,60 @@ namespace TShockAPI.DB } } + /// + /// Represents the base GroupManager exception. + /// [Serializable] public class GroupManagerException : Exception { + /// + /// Initializes a new instance of the with the specified message. + /// + /// The message. public GroupManagerException(string message) : base(message) { } + /// + /// Initializes a new instance of the with the specified message and inner exception. + /// + /// The message. + /// The inner exception. public GroupManagerException(string message, Exception inner) : base(message, inner) { } } + /// + /// Represents the GroupExists exception. + /// This exception is thrown whenever an attempt to add an existing group into the database is made. + /// [Serializable] public class GroupExistsException : GroupManagerException { + /// + /// Initializes a new instance of the with the specified group name. + /// + /// The group name. public GroupExistsException(string name) : base("Group '" + name + "' already exists") { } } + /// + /// Represents the GroupNotExist exception. + /// This exception is thrown whenever we try to access a group that does not exist. + /// [Serializable] public class GroupNotExistException : GroupManagerException { + /// + /// Initializes a new instance of the with the specified group name. + /// + /// The group name. public GroupNotExistException(string name) : base("Group '" + name + "' does not exist") {